diff --git a/.storybook/storybook.css b/.storybook/storybook.css
index ef88592e72..6cdf7c8c4c 100644
--- a/.storybook/storybook.css
+++ b/.storybook/storybook.css
@@ -85,6 +85,14 @@
p:not(.sb-anchor, .sb-unstyled, .sb-unstyled p) {
font-weight: 300;
}
+
+ ul {
+ list-style: circle;
+ }
+
+ ol {
+ list-style: decimal;
+ }
}
}
diff --git a/resources/js/components/ui/Listing/Listing.vue b/resources/js/components/ui/Listing/Listing.vue
index 798c517433..2b7218abe3 100644
--- a/resources/js/components/ui/Listing/Listing.vue
+++ b/resources/js/components/ui/Listing/Listing.vue
@@ -5,7 +5,11 @@ export const [injectListingContext, provideListingContext] = createContext('List
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/stories/Listing.stories.ts b/resources/js/stories/Listing.stories.ts
index 81cec4d013..5bb3c9f0bd 100644
--- a/resources/js/stories/Listing.stories.ts
+++ b/resources/js/stories/Listing.stories.ts
@@ -14,7 +14,9 @@ import {
ListingTable,
ListingTableBody,
ListingTableHead,
- ListingToggleAll
+ ListingToggleAll,
+ Panel,
+ PanelFooter,
} from '@ui';
const meta = {
@@ -208,4 +210,65 @@ export const _WithActions: Story = {
components: { Listing, Badge, DropdownItem },
template: actionsCode,
}),
+};
+
+const customLayoutCode = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+export const _CustomLayout: Story = {
+ tags: ['!dev'],
+ parameters: {
+ docs: {
+ source: { code: customLayoutCode }
+ }
+ },
+ render: () => ({
+ components: {
+ Listing,
+ ListingPresets,
+ ListingSearch,
+ ListingFilters,
+ ListingCustomizeColumns,
+ ListingTable,
+ ListingPagination,
+ Panel,
+ PanelFooter,
+ Badge,
+ DropdownItem,
+ },
+ template: customLayoutCode,
+ }),
};
\ No newline at end of file
diff --git a/resources/js/stories/docs/Listing.mdx b/resources/js/stories/docs/Listing.mdx
index dd2fe0d3c1..d044fd9c3a 100644
--- a/resources/js/stories/docs/Listing.mdx
+++ b/resources/js/stories/docs/Listing.mdx
@@ -9,14 +9,157 @@ A full-featured data listing component with sorting, searching, pagination, colu
## Customizing the cells
Customize how individual cells are rendered using cell slots. Use the pattern `#cell-{fieldName}` to target specific columns.
+
+The cell slots expose a few props:
+- `row` - the current row's data
+- `value` - the current column's value
+- `isColumnVisible(column)` - determine whether a column is currently visible
+
## With Actions
You can add your own dropdown items via the `prepended-row-actions` slot.
+## Customizing the layout
+The `Listing` component includes a sensible default layout, but you can fully control the structure by providing your own markup in the default slot.
+
+Inside the slot, you can compose your layout using the provided `Listing` sub-components, which automatically connect to the parent `Listing` via shared context.
+
+If you need to customize table columns or row actions, you can do so inside the `ListingTable` component's slots.
+
+
+
+### Available components
+You can use the following components to build your own layout:
+
+- `ListingCustomizeColumns`
+- `ListingFilters`
+- `ListingPagination`
+- `ListingPresets`
+- `ListingSearch`
+- `ListingTable`
+
+### Slot props
+The default slot exposes a few props:
+
+- `items` - the items for the current page
+- `isColumnVisible(column)` - determine whether a column is currently visible
+- `loading` - whether results are being fetched from the server
+
## Using a JSON endpoint
-The `Listing` component can pull data from a JSON endpoint. Simply specify a `url` and return an [API Resource](https://laravel.com/docs/12.x/eloquent-resources#main-content) from your controller.
+The `Listing` component can pull data from a JSON endpoint.
+
+1. You'll need to make a controller to query your data. The Listing component will provide `page`, `perPage`, `sort`, `order`, `search`, `columns` and `filters` as query parameters.
+
+ You may also want to make a [JSON resource](https://laravel.com/docs/master/eloquent-resources#main-content) to have more control over the response.
+
+ ```php
+ use Illuminate\Http\Request;
+
+ public function json(Request $request)
+ {
+ $query = TeamMember::query();
+
+ if ($search $request->input('search')) {
+ $query->where('name', 'like', "%{$search}%");
+ }
+
+ $query->orderBy(
+ $request->input('sort', 'name'), // column
+ $request->input('order', 'asc') // direction
+ );
+
+ return TeamMemberResource::collection($query->paginate($request->input('perPage')));
+ }
+ ```
+
+2. Create a route and provide the `url` prop instead of `items`:
+
+ ```php
+ Route::get('team-members/json', [TeamMemberController::class, 'json']);
+ ```
+
+ ```vue
+
+ ```
+
+### Actions
+To enable [actions](https://statamic.dev/backend-apis/actions), you'll need to make another controller. It should extend Statamic's `ActionController`.
+
+The `$key` should match what's returned by [action's `visibleTo` method](https://statamic.dev/backend-apis/actions#filtering-actions). Inside `getSelectedItems`, you should lookup items via their IDs:
+
+```php
+map(function ($item) {
+ return TeamMember::find($item);
+ });
+ }
+}
+```
+
+Next, add two routes pointing to the controller:
+
+```php
+Route::get('team-members/actions', [TeamMemberActionController::class, 'run']);
+Route::get('team-members/actions/list', [TeamMemberActionController::class, 'bulkActions']);
+```
+
+Finally, pass the URL for the first route to the `actionUrl` prop:
+
+```vue
+
+```
+
+### Filtering
+To enable filters on your listing, you'll need to call `Scope::filters()` in your controller and pass it to the `Listing` component on your page.
+
+The `Scope::filters()` method expects a key (which should match what's returned by the [filter's `visibleTo` method](https://statamic.dev/backend-apis/query-scopes-and-filters#filters) along with any additional context you wish to provide.
+
+```php
+return Interia::render('team-members/Index', [
+ 'filters' => Scope::filters('team-members', [
+ 'company' => 'Statamic',
+ ]),
+ // ...
+]);
+```
+
+```vue
+
+
+
+
+
+```
## Arguments