@modufolio/panel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/Builder/ImageNodeView.d.ts +33 -0
- package/dist/Builder/document.d.ts +17 -0
- package/dist/Builder/dragHandle.d.ts +13 -0
- package/dist/Builder/editing.d.ts +53 -0
- package/dist/Builder/plugins.d.ts +27 -0
- package/dist/Builder/schema.d.ts +20 -0
- package/dist/Components/Actions/Action.vue.d.ts +116 -0
- package/dist/Components/Actions/ActionGroup.vue.d.ts +25 -0
- package/dist/Components/Actions/ActionGroupItem.vue.d.ts +48 -0
- package/dist/Components/Auth/ForgotPassword.vue.d.ts +17 -0
- package/dist/Components/Auth/Login.vue.d.ts +33 -0
- package/dist/Components/Auth/ResetPassword.vue.d.ts +27 -0
- package/dist/Components/Auth/TwoFactor.vue.d.ts +32 -0
- package/dist/Components/Auth/TwoFactorVerify.vue.d.ts +31 -0
- package/dist/Components/Columns/BadgeColumn.vue.d.ts +34 -0
- package/dist/Components/Columns/BooleanColumn.vue.d.ts +78 -0
- package/dist/Components/Columns/CellActions.vue.d.ts +34 -0
- package/dist/Components/Columns/ColorColumn.vue.d.ts +84 -0
- package/dist/Components/Columns/CopyButton.vue.d.ts +15 -0
- package/dist/Components/Columns/DateColumn.vue.d.ts +60 -0
- package/dist/Components/Columns/IconColumn.vue.d.ts +56 -0
- package/dist/Components/Columns/ImageColumn.vue.d.ts +46 -0
- package/dist/Components/Columns/SelectColumn.vue.d.ts +71 -0
- package/dist/Components/Columns/TextColumn.vue.d.ts +69 -0
- package/dist/Components/Columns/TextInputColumn.vue.d.ts +75 -0
- package/dist/Components/Columns/ToggleColumn.vue.d.ts +91 -0
- package/dist/Components/Columns/TreeColumn.vue.d.ts +60 -0
- package/dist/Components/Columns/columnRegistry.d.ts +6 -0
- package/dist/Components/Composables/useInlineEdit.d.ts +27 -0
- package/dist/Components/Composables/useRelationship.d.ts +38 -0
- package/dist/Components/Core/Badge.vue.d.ts +47 -0
- package/dist/Components/Core/Button.vue.d.ts +131 -0
- package/dist/Components/Core/Dropdown.vue.d.ts +24 -0
- package/dist/Components/Core/Empty.vue.d.ts +54 -0
- package/dist/Components/Core/ErrorBoundary.vue.d.ts +24 -0
- package/dist/Components/Core/FlashMessages.vue.d.ts +7 -0
- package/dist/Components/Core/Icon.vue.d.ts +23 -0
- package/dist/Components/Core/Icons/DocumentLinesIcon.vue.d.ts +3 -0
- package/dist/Components/Core/Icons/SitemapIcon.vue.d.ts +3 -0
- package/dist/Components/Core/Label.vue.d.ts +52 -0
- package/dist/Components/Core/LoadingButton.vue.d.ts +18 -0
- package/dist/Components/Core/Modal.vue.d.ts +26 -0
- package/dist/Components/Core/Pagination.vue.d.ts +10 -0
- package/dist/Components/Core/SelectInput.vue.d.ts +26 -0
- package/dist/Components/Core/Tag.vue.d.ts +65 -0
- package/dist/Components/Core/TextInput.vue.d.ts +25 -0
- package/dist/Components/Core/iconRegistry.d.ts +3 -0
- package/dist/Components/Dialogs/ConfirmDialog.vue.d.ts +16 -0
- package/dist/Components/Dialogs/DeleteConfirmDialog.vue.d.ts +30 -0
- package/dist/Components/Dialogs/Dialog.vue.d.ts +81 -0
- package/dist/Components/Drawer/Drawer.vue.d.ts +130 -0
- package/dist/Components/Drawer/DrawerFieldGrid.vue.d.ts +39 -0
- package/dist/Components/Drawer/DrawerLink.vue.d.ts +96 -0
- package/dist/Components/Drawer/DrawerRelationList.vue.d.ts +81 -0
- package/dist/Components/Drawer/DrawerStack.vue.d.ts +93 -0
- package/dist/Components/Drawer/DrawerTabs.vue.d.ts +42 -0
- package/dist/Components/Drawer/NestedDrawerForm.vue.d.ts +55 -0
- package/dist/Components/Drawer/drawerFieldGrid.d.ts +25 -0
- package/dist/Components/Drawer/drawerTabs.d.ts +16 -0
- package/dist/Components/Drawer/useDrawerPage.d.ts +34 -0
- package/dist/Components/Drawer/useDrawerStack.d.ts +93 -0
- package/dist/Components/Drawer/useFocusedStackRow.d.ts +22 -0
- package/dist/Components/Drawer/useIsDrawer.d.ts +66 -0
- package/dist/Components/Drawer/visitDrawer.d.ts +48 -0
- package/dist/Components/Fields/BelongsToSelect.vue.d.ts +247 -0
- package/dist/Components/Fields/BlueprintForm.vue.d.ts +96 -0
- package/dist/Components/Fields/Calendar/CalendarPanel.vue.d.ts +63 -0
- package/dist/Components/Fields/Calendar/MonthGrid.vue.d.ts +82 -0
- package/dist/Components/Fields/CheckboxField.vue.d.ts +92 -0
- package/dist/Components/Fields/ColorPickerField.vue.d.ts +105 -0
- package/dist/Components/Fields/DatePickerField.vue.d.ts +102 -0
- package/dist/Components/Fields/DateRangePickerField.vue.d.ts +138 -0
- package/dist/Components/Fields/DateTimePickerField.vue.d.ts +134 -0
- package/dist/Components/Fields/FieldGrid.vue.d.ts +13 -0
- package/dist/Components/Fields/FileUploadField.vue.d.ts +117 -0
- package/dist/Components/Fields/MultiSelectField.vue.d.ts +129 -0
- package/dist/Components/Fields/ProseMirrorBuilderField.vue.d.ts +71 -0
- package/dist/Components/Fields/RangeField.vue.d.ts +122 -0
- package/dist/Components/Fields/RepeaterField.vue.d.ts +174 -0
- package/dist/Components/Fields/RichTextEditorField.vue.d.ts +118 -0
- package/dist/Components/Fields/SelectField.vue.d.ts +102 -0
- package/dist/Components/Fields/TagsField.vue.d.ts +102 -0
- package/dist/Components/Fields/TextField.vue.d.ts +138 -0
- package/dist/Components/Fields/TextareaField.vue.d.ts +111 -0
- package/dist/Components/Fields/TimePickerField.vue.d.ts +115 -0
- package/dist/Components/Fields/ToggleButtonsField.vue.d.ts +93 -0
- package/dist/Components/Fields/ToggleField.vue.d.ts +95 -0
- package/dist/Components/Fields/fieldRegistry.d.ts +25 -0
- package/dist/Components/Fields/fieldsFromSpec.d.ts +10 -0
- package/dist/Components/Fields/useBlueprint.d.ts +94 -0
- package/dist/Components/Fields/useFieldWidth.d.ts +10 -0
- package/dist/Components/Fields/validation.d.ts +43 -0
- package/dist/Components/Filters/DateRangeFilter.vue.d.ts +92 -0
- package/dist/Components/Filters/FacetedFilter.vue.d.ts +41 -0
- package/dist/Components/Filters/FilterIndicators.vue.d.ts +40 -0
- package/dist/Components/Filters/FilterPopover.vue.d.ts +38 -0
- package/dist/Components/Filters/MultiSelectFilter.vue.d.ts +51 -0
- package/dist/Components/Filters/NumberFilter.vue.d.ts +61 -0
- package/dist/Components/Filters/QueryBuilder.vue.d.ts +32 -0
- package/dist/Components/Filters/SelectFilter.vue.d.ts +61 -0
- package/dist/Components/Filters/TernaryFilter.vue.d.ts +73 -0
- package/dist/Components/Layout/AppLayout.vue.d.ts +137 -0
- package/dist/Components/Layout/Breadcrumbs.vue.d.ts +44 -0
- package/dist/Components/Layout/Cluster.vue.d.ts +88 -0
- package/dist/Components/Layout/Container.vue.d.ts +69 -0
- package/dist/Components/Layout/Grid.vue.d.ts +110 -0
- package/dist/Components/Layout/PageHeader.vue.d.ts +62 -0
- package/dist/Components/Layout/Sidebar.vue.d.ts +59 -0
- package/dist/Components/Layout/Stack.vue.d.ts +69 -0
- package/dist/Components/Layout/TopNavigation.vue.d.ts +124 -0
- package/dist/Components/Media/MediaPickerDialog.vue.d.ts +20 -0
- package/dist/Components/Media/mediaEndpoints.d.ts +18 -0
- package/dist/Components/Notifications/Toast.vue.d.ts +17 -0
- package/dist/Components/Notifications/useToast.d.ts +43 -0
- package/dist/Components/Relations/RelationManager.vue.d.ts +232 -0
- package/dist/Components/Sections/FieldsSection.vue.d.ts +56 -0
- package/dist/Components/Sections/FilesSection.vue.d.ts +120 -0
- package/dist/Components/Sections/FormSection.vue.d.ts +111 -0
- package/dist/Components/Sections/InfoSection.vue.d.ts +77 -0
- package/dist/Components/Sections/Section.vue.d.ts +67 -0
- package/dist/Components/Table/ColumnToggle.vue.d.ts +42 -0
- package/dist/Components/Table/ExportButton.vue.d.ts +93 -0
- package/dist/Components/Table/SchemaCell.d.ts +50 -0
- package/dist/Components/Table/SchemaFilterPanel.vue.d.ts +64 -0
- package/dist/Components/Table/SchemaTable.vue.d.ts +255 -0
- package/dist/Components/Table/Table.vue.d.ts +232 -0
- package/dist/Components/Table/TableEmptyState.vue.d.ts +24 -0
- package/dist/Components/Table/TablePagination.vue.d.ts +71 -0
- package/dist/Components/Table/TableSkeletonRows.vue.d.ts +52 -0
- package/dist/Components/Table/TableSortButton.vue.d.ts +38 -0
- package/dist/Components/Table/TableToolbar.vue.d.ts +62 -0
- package/dist/Components/Table/schemaLinks.d.ts +16 -0
- package/dist/Components/Table/tableSchema.d.ts +243 -0
- package/dist/Components/Table/tableTypes.d.ts +13 -0
- package/dist/Components/Table/useSchemaActions.d.ts +82 -0
- package/dist/Components/Table/useSchemaFilters.d.ts +28 -0
- package/dist/Components/Table/useTableExport.d.ts +16 -0
- package/dist/Components/Table/useTableGrouping.d.ts +9 -0
- package/dist/Components/Table/useTableRowFocus.d.ts +27 -0
- package/dist/Components/Table/useTableSelection.d.ts +15 -0
- package/dist/Components/Table/useTableTree.d.ts +23 -0
- package/dist/Components/Widgets/StatCard.vue.d.ts +81 -0
- package/dist/Components/Widgets/StatsWidget.vue.d.ts +27 -0
- package/dist/Components/Wizard/Wizard.vue.d.ts +99 -0
- package/dist/Composables/useAsyncData.d.ts +48 -0
- package/dist/Composables/useDeleteConfirmation.d.ts +86 -0
- package/dist/Composables/useDragReorder.d.ts +12 -0
- package/dist/Composables/useFieldSaver.d.ts +15 -0
- package/dist/Composables/useFocusTrap.d.ts +5 -0
- package/dist/Composables/useLocalStoragePersistence.d.ts +18 -0
- package/dist/Composables/useNestedDrawerForm.d.ts +58 -0
- package/dist/Composables/usePagination.d.ts +23 -0
- package/dist/Composables/usePendingKeys.d.ts +30 -0
- package/dist/Composables/useQuery.d.ts +61 -0
- package/dist/Composables/useReconciled.d.ts +22 -0
- package/dist/Composables/useUnsavedChangesWarning.d.ts +16 -0
- package/dist/Primitives/hideOthers.d.ts +12 -0
- package/dist/Primitives/teleportTarget.d.ts +2 -0
- package/dist/Primitives/useAnchoredPosition.d.ts +32 -0
- package/dist/Primitives/useArrowNavigation.d.ts +30 -0
- package/dist/Primitives/useBodyScrollLock.d.ts +9 -0
- package/dist/Primitives/useDismissableLayer.d.ts +46 -0
- package/dist/Primitives/useId.d.ts +13 -0
- package/dist/Primitives/useTypeahead.d.ts +11 -0
- package/dist/Utils/apiFetch.d.ts +28 -0
- package/dist/Utils/csrf.d.ts +2 -0
- package/dist/Utils/dates.d.ts +50 -0
- package/dist/Utils/moduleSingleton.d.ts +21 -0
- package/dist/Utils/optimistic.d.ts +33 -0
- package/dist/Utils/reconcile.d.ts +17 -0
- package/dist/Utils/tagsChanged.d.ts +16 -0
- package/dist/Utils/url.d.ts +40 -0
- package/dist/Utils/writeGate.d.ts +82 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +157 -0
- package/dist/index.js +27193 -0
- package/dist/index.js.map +1 -0
- package/dist/injectionKeys.d.ts +10 -0
- package/dist/plugin.d.ts +36 -0
- package/dist/types/menu.d.ts +11 -0
- package/docs/async-writes.md +159 -0
- package/docs/custom-fields.md +71 -0
- package/docs/drawer-protocol.md +130 -0
- package/docs/overlays.md +106 -0
- package/docs/relation-fields.md +112 -0
- package/docs/roadmap.md +38 -0
- package/docs/table-schema.md +371 -0
- package/package.json +150 -0
- package/styles/buttons.css +31 -0
- package/styles/form.css +74 -0
- package/styles/index.css +15 -0
- package/styles/tokens.css +79 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# Server-driven table schema
|
|
2
|
+
|
|
3
|
+
`SchemaTable` renders a table from a description the **server** sends as an
|
|
4
|
+
Inertia prop, instead of a `columns` array maintained in the page component.
|
|
5
|
+
|
|
6
|
+
It wraps `Table` rather than replacing it — pages using `Table` directly are
|
|
7
|
+
unaffected.
|
|
8
|
+
|
|
9
|
+
```vue
|
|
10
|
+
<SchemaTable
|
|
11
|
+
:schema="table"
|
|
12
|
+
:records="organizations.data"
|
|
13
|
+
:summaries="organizations.meta?.summaries ?? {}"
|
|
14
|
+
:filter-values="form"
|
|
15
|
+
:query-params="computedParams"
|
|
16
|
+
:sort-column="computedSortColumn"
|
|
17
|
+
:sort-direction="computedSortDirection"
|
|
18
|
+
@update:search="updateSearch"
|
|
19
|
+
@sort="handleSort"
|
|
20
|
+
@update:filter="setFilter"
|
|
21
|
+
/>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The reference backend implementation is appkit-portfolio's `App\Table\TableSchema`,
|
|
25
|
+
but the contract below is plain JSON — any backend can produce it.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Props
|
|
30
|
+
|
|
31
|
+
| Prop | Type | Description |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `schema` | `TableSchema` | Required. The server-authored description |
|
|
34
|
+
| `records` | `object[]` | Row data |
|
|
35
|
+
| `summaries` | `Record<string, Summary[]>` | Column aggregates, from `meta.summaries` |
|
|
36
|
+
| `filterValues` | `Record<string, unknown>` | Current filter form values |
|
|
37
|
+
| `cellHandlers` | `Record<string, fn>` | Save callbacks for `editable` columns |
|
|
38
|
+
| `queryParams` | `object` | Appended to record links so a drawer keeps list state |
|
|
39
|
+
| `rowActionHandlers` | `Record<string, fn>` | Services `behaviour: 'handler'` row actions, by name |
|
|
40
|
+
| `bulkActionHandlers` | `Record<string, fn>` | The same for bulk actions |
|
|
41
|
+
| `visibleColumns` | `string[] \| null` | Which columns to render. `null` shows them all — `hiddenByDefault` is only honoured when the page seeds this, see [Column visibility](#column-visibility) |
|
|
42
|
+
| `stack` | `StackItem[]` | The page's drawer stack. When given, the highlighted row follows the record the top drawer shows (arrow-key record pagination changes the stack without a click) |
|
|
43
|
+
| `drawerType` | `string` | Restricts stack-driven highlighting to top items of this drawer type. Omitted = match any top item by id (safe with UUIDs) |
|
|
44
|
+
| `search`, `sortColumn`, `sortDirection`, `loading`, `externalFocusedRowIndex` | | Forwarded to `Table`; an explicit `externalFocusedRowIndex ≥ 0` overrides the stack-derived row |
|
|
45
|
+
|
|
46
|
+
Events: `update:search`, `sort`, `rowClick`, `update:filter (key, value)`,
|
|
47
|
+
`resetFilters`.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## The schema contract
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
interface TableSchema {
|
|
55
|
+
columns: SchemaColumn[]
|
|
56
|
+
filters?: SchemaFilter[]
|
|
57
|
+
groups?: Array<{ value: string; label: string }>
|
|
58
|
+
constraints?: SchemaConstraint[]
|
|
59
|
+
recordUrl?: string | null // '/panel/organizations/{id}'
|
|
60
|
+
emptyStateTitle?: string | null
|
|
61
|
+
emptyStateDescription?: string | null
|
|
62
|
+
searchable?: boolean
|
|
63
|
+
bulkActions?: boolean
|
|
64
|
+
stickyHeader?: boolean
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Columns
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
interface SchemaColumn {
|
|
72
|
+
key: string
|
|
73
|
+
name: string
|
|
74
|
+
label: string
|
|
75
|
+
type: 'text' | 'select' | 'money' | 'numeric' | 'badge' | 'date'
|
|
76
|
+
| 'boolean' | 'image' | 'icon' | 'color'
|
|
77
|
+
sortable: boolean // resolved server-side; never hand-declared
|
|
78
|
+
valueKey?: string // dot path, e.g. 'organization.name'
|
|
79
|
+
descriptionKey?: string // second line, read from another field
|
|
80
|
+
linksToRecord?: boolean
|
|
81
|
+
urlTemplate?: string // dot-path placeholders: '{id}', '{organization.id}'
|
|
82
|
+
showArrow?: boolean
|
|
83
|
+
placeholder?: string // default '—'
|
|
84
|
+
toggleable?: boolean
|
|
85
|
+
hiddenByDefault?: boolean
|
|
86
|
+
weight?: 'medium' | 'bold'
|
|
87
|
+
align?: 'left' | 'center' | 'right'
|
|
88
|
+
color?: string
|
|
89
|
+
icon?: string
|
|
90
|
+
limit?: number
|
|
91
|
+
copyable?: boolean
|
|
92
|
+
size?: string // type: 'image': 'sm' | 'md' | 'lg' | 'xl'
|
|
93
|
+
rounded?: string // type: 'image': 'none' | 'sm' | 'md' | 'lg' | 'full'
|
|
94
|
+
currency?: string // type: 'money'
|
|
95
|
+
decimals?: number // type: 'numeric'
|
|
96
|
+
format?: string // type: 'date'
|
|
97
|
+
relative?: boolean
|
|
98
|
+
colors?: Record<string, string>
|
|
99
|
+
options?: Array<{ label: string; value: string; class?: string }>
|
|
100
|
+
editable?: boolean
|
|
101
|
+
disabledWhen?: string
|
|
102
|
+
readOnlyWhen?: string
|
|
103
|
+
summaries?: Array<{ type: string; label: string }>
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Helpers exported alongside: `getPath`, `resolveRecordUrl`, `isEmptyValue`,
|
|
108
|
+
`cellClasses`, `truncate`, `formatValue`, `emptyFilterValue`, `filterDefaults`,
|
|
109
|
+
`visibleColumnDefaults`.
|
|
110
|
+
|
|
111
|
+
### Empty cells never link
|
|
112
|
+
|
|
113
|
+
A cell whose value is empty (`null`, `undefined`, `''`) renders its
|
|
114
|
+
`placeholder` — `'—'` by default — **unlinked**, whatever the column declares.
|
|
115
|
+
`linksToRecord` and `urlTemplate` are both ignored for that row.
|
|
116
|
+
|
|
117
|
+
This is deliberate. A `urlTemplate` such as
|
|
118
|
+
`/panel/contacts/{id}/organization/{organization.id}` has nothing to resolve
|
|
119
|
+
`{organization.id}` to when the contact has no organization, and a link
|
|
120
|
+
labelled `—` that lands somewhere arbitrary is worse than plain text. So a
|
|
121
|
+
contact *with* an organization drills into it and one *without* is inert —
|
|
122
|
+
including for `type: 'image'`, where an absent picture shows the placeholder
|
|
123
|
+
graphic and clicking it does nothing.
|
|
124
|
+
|
|
125
|
+
The consequence to design around: the row itself is not a link, so an empty
|
|
126
|
+
cell is dead space. Put the record's own link on a column that is always
|
|
127
|
+
present (the name or title), which every listing here does.
|
|
128
|
+
|
|
129
|
+
### Image columns
|
|
130
|
+
|
|
131
|
+
`type: 'image'` reads **one URL** from the row — `size` and `rounded` control
|
|
132
|
+
how it renders, and both belong in the schema rather than in a page override,
|
|
133
|
+
so two listings cannot drift apart:
|
|
134
|
+
|
|
135
|
+
```php
|
|
136
|
+
Column::make('cover_url')->label('Cover')->type('image')
|
|
137
|
+
->size('lg')->rounded('none')->linksToRecord()->notSortable(),
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`rounded` defaults to `'full'` (an avatar); artwork and covers usually want
|
|
141
|
+
`'none'` or `'md'`. When the row holds a media *object* rather than a URL,
|
|
142
|
+
point `valueKey` at the field: `->value('cover.thumbnail_url')`. An empty value
|
|
143
|
+
renders the placeholder graphic, not a dash.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Actions
|
|
148
|
+
|
|
149
|
+
`schema.actions` and `schema.bulkActionItems` are rendered by `SchemaTable`
|
|
150
|
+
itself — including the confirmation dialogs — when the page passes no
|
|
151
|
+
`#actions` / `#bulkActions` slot. A page slot always wins.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
interface SchemaRowAction {
|
|
155
|
+
name: string
|
|
156
|
+
behaviour: 'drawer' | 'dialog' | 'visit' | 'delete' | 'handler'
|
|
157
|
+
label: string
|
|
158
|
+
icon?: string; color?: string
|
|
159
|
+
urlTemplate?: string // dot-path placeholders, resolved per row
|
|
160
|
+
previewUrl?: string // 'delete' only: what the deletion would cost
|
|
161
|
+
hiddenWhen?: string; visibleWhen?: string
|
|
162
|
+
confirm?: boolean; confirmMessage?: string
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
interface SchemaBulkAction {
|
|
166
|
+
name: string
|
|
167
|
+
behaviour: 'post' | 'handler'
|
|
168
|
+
label: string
|
|
169
|
+
icon?: string; color?: string; variant?: string
|
|
170
|
+
url?: string // POST { ids: [...] }
|
|
171
|
+
confirm?: boolean
|
|
172
|
+
confirmMessage?: string // '{count}' is replaced with the selection size
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
| Behaviour | What the table does |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `drawer` | Opens the row's `recordUrl` on the drawer stack (`visitDrawer`) |
|
|
179
|
+
| `dialog` | The same navigation, to the action's own `urlTemplate` — the frame is the server's answer, so the table never learns what the dialog does |
|
|
180
|
+
| `visit` | `router.visit(url)` |
|
|
181
|
+
| `delete` | Confirms — with the server's plan when `previewUrl` is set — then `router.delete(url)` |
|
|
182
|
+
| `post` | `router.post(url, { ids })` |
|
|
183
|
+
| `handler` | Calls `rowActionHandlers[name]` / `bulkActionHandlers[name]`; nothing happens if none is registered, because a schema can outlive the page that serviced it |
|
|
184
|
+
|
|
185
|
+
`drawer` and `dialog` are one code path: both are frames on the same stack, and
|
|
186
|
+
the item the server returns carries `presentation` to say which renders. That
|
|
187
|
+
is what keeps the behaviour list from growing per feature — a new interaction
|
|
188
|
+
is a new route, not a new case here. See the
|
|
189
|
+
[drawer protocol](./drawer-protocol.md#two-frames).
|
|
190
|
+
|
|
191
|
+
Two delete flows exist because `useDeleteConfirmation` decides at construction
|
|
192
|
+
whether it has a preview: an action with `previewUrl` asks the server what the
|
|
193
|
+
deletion would cost, one without degrades to a plain confirmation. Handing the
|
|
194
|
+
preview flow an empty URL would report every record as blocked.
|
|
195
|
+
|
|
196
|
+
```vue
|
|
197
|
+
<SchemaTable
|
|
198
|
+
:schema="table"
|
|
199
|
+
:row-action-handlers="{ restore: (record) => router.put(`/panel/x/${record.id}/restore`) }"
|
|
200
|
+
/>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Column visibility
|
|
206
|
+
|
|
207
|
+
Columns marked `toggleable` appear in `ColumnToggle`'s dropdown; ones marked
|
|
208
|
+
`hiddenByDefault` start switched off. Neither flag does anything on its own —
|
|
209
|
+
`Table` filters against the list the page hands it, so the page owns the state:
|
|
210
|
+
|
|
211
|
+
```vue
|
|
212
|
+
<SchemaTable :schema="table" :visible-columns="visibleColumns" …>
|
|
213
|
+
<template #headerActions>
|
|
214
|
+
<ColumnToggle v-model="visibleColumns" :columns="table.columns" />
|
|
215
|
+
</template>
|
|
216
|
+
</SchemaTable>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
const visibleColumns = ref<string[]>(visibleColumnDefaults(props.table))
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`visibleColumnDefaults` is what honours `hiddenByDefault`; seeding the ref with
|
|
224
|
+
every key instead silently ignores the flag. Hiding a column changes nothing
|
|
225
|
+
about the query — it is a client-side concern, and is not reflected in the URL.
|
|
226
|
+
A column left un-`toggleable` cannot be hidden, which is how the identity
|
|
227
|
+
column (name, title) stays on screen.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Slots
|
|
232
|
+
|
|
233
|
+
Everything you pass is forwarded to `Table`; anything you don't is generated.
|
|
234
|
+
|
|
235
|
+
| Slot | Behaviour |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `cell-{key}` | **Overrides** the generated cell for that column |
|
|
238
|
+
| `filters` | **Replaces** the generated filter popover entirely |
|
|
239
|
+
| `headerActions`, `actions`, `bulkActions`, `pagination`, `header` | Forwarded as-is |
|
|
240
|
+
|
|
241
|
+
Overriding one column doesn't opt you out of the others — the component filters
|
|
242
|
+
its generated columns against `useSlots()`.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Filters
|
|
247
|
+
|
|
248
|
+
When `schema.filters` is present, `SchemaTable` renders a `FilterPopover`
|
|
249
|
+
containing the right control per filter type (`select`, `multiSelect`,
|
|
250
|
+
`ternary`, `trashed`, `dateRange`), plus a group-by select and the query
|
|
251
|
+
builder when the schema declares them.
|
|
252
|
+
|
|
253
|
+
Values are **controlled** — the component never mutates `filterValues`. It
|
|
254
|
+
emits `update:filter (key, value)`; the page writes it into its own form state:
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
const { form } = useListFilters('/organizations', props.filters, {
|
|
258
|
+
defaults: filterDefaults(props.table), // keys come from the schema
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
function setFilter(key, value) {
|
|
262
|
+
form[key] = value
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`filterDefaults(schema)` returns the key → empty-value map, including `group`
|
|
267
|
+
and `constraints` when those are declared, so the page never restates filter
|
|
268
|
+
keys.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Editable cells
|
|
273
|
+
|
|
274
|
+
The schema declares *that* a column is editable and *what* it offers; a save
|
|
275
|
+
callback can't be JSON, so the page injects it:
|
|
276
|
+
|
|
277
|
+
```js
|
|
278
|
+
const cellHandlers = {
|
|
279
|
+
role: (record, _column, value) => updateField(record, 'role', value),
|
|
280
|
+
status: (record, _column, value) => updateField(record, 'account_status', value),
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Keyed by **column key** — note `status` above persists to `account_status`,
|
|
285
|
+
which the schema's `valueKey` handles for display and the handler for the write.
|
|
286
|
+
|
|
287
|
+
An editable cell is never wrapped in a record link; the control would navigate
|
|
288
|
+
away on the first click.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Summaries
|
|
293
|
+
|
|
294
|
+
Aggregates arrive per column and render in a `<tfoot>` row via `Table`'s
|
|
295
|
+
`summary` slot:
|
|
296
|
+
|
|
297
|
+
```json
|
|
298
|
+
{ "name": [{ "type": "count", "label": "Organizations", "value": 5 }] }
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
They belong with the data, not the schema — they change with every filter. The
|
|
302
|
+
server should compute them over the **filtered set**, not the current page.
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Grouping
|
|
307
|
+
|
|
308
|
+
`schema.groups` offers the user a grouping; the active key rides in
|
|
309
|
+
`filterValues.group`. The **server** orders rows by the group field so they
|
|
310
|
+
cluster, and `Table`'s `groupBy` prop draws a heading whenever the value
|
|
311
|
+
changes. The client does not re-sort.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Query builder
|
|
316
|
+
|
|
317
|
+
`schema.constraints` drives `QueryBuilder.vue`: the user picks a field, an
|
|
318
|
+
operator from that field's type, and value(s). Each operator carries its
|
|
319
|
+
arity (`0`, `1` or `2`), so the UI knows how many inputs to draw. Conditions
|
|
320
|
+
are ANDed and sent as `constraints[]`.
|
|
321
|
+
|
|
322
|
+
The server must treat both the field and the operator as allowlists — the
|
|
323
|
+
client is a convenience, not a guarantee.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Why no closures
|
|
328
|
+
|
|
329
|
+
The schema is JSON, so it cannot carry callbacks. Anything per-row is either
|
|
330
|
+
computed server-side into a field the schema names (`descriptionKey`), or
|
|
331
|
+
handled by a `#cell-{key}` slot override.
|
|
332
|
+
|
|
333
|
+
## Editable text cells
|
|
334
|
+
|
|
335
|
+
`Column::make('phone')->editable()` on a `text` column renders an inline input:
|
|
336
|
+
Enter or blur saves through the page's handler for that column key, Escape
|
|
337
|
+
reverts, and a rejected save keeps the typed value on screen with the reason
|
|
338
|
+
attached rather than making the user retype it from memory.
|
|
339
|
+
|
|
340
|
+
The page supplies the *how*, as it already does for editable selects and
|
|
341
|
+
toggles — a save closure cannot cross the schema's JSON boundary.
|
|
342
|
+
|
|
343
|
+
## Custom column types
|
|
344
|
+
|
|
345
|
+
Columns have the same extension seam as blueprint fields:
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
import { registerColumnType } from '@modufolio/panel'
|
|
349
|
+
import SparklineColumn from './Columns/SparklineColumn.vue'
|
|
350
|
+
|
|
351
|
+
registerColumnType('sparkline', SparklineColumn)
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
A registered component receives `{ value, record, column, label, onUpdate }`
|
|
355
|
+
and owns its cell completely, including how it renders an empty value. Unlike
|
|
356
|
+
the field registry this resolves synchronously — a column renders once per
|
|
357
|
+
visible cell, and an async loader would leave holes in the grid on first paint.
|
|
358
|
+
Pass `defineAsyncComponent` yourself if you want that behaviour.
|
|
359
|
+
|
|
360
|
+
Registering a built-in type's name replaces it everywhere the schema asks for
|
|
361
|
+
it, which is how an application restyles `badge` without forking the table.
|
|
362
|
+
|
|
363
|
+
## Active filter indicators
|
|
364
|
+
|
|
365
|
+
`SchemaTable` renders a chip per active filter between the toolbar and the
|
|
366
|
+
rows: the filter's label, its value in words (the option's label, not the raw
|
|
367
|
+
query-string value), and an X that clears just that one. Filters live behind a
|
|
368
|
+
popover, so without this the only sign a result set has been narrowed is a
|
|
369
|
+
count badge on a closed button — and "no results" reads as "no such records".
|
|
370
|
+
|
|
371
|
+
Pass a `#filterIndicators` slot to replace the strip entirely.
|
package/package.json
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@modufolio/panel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Schema-driven admin panel components for Inertia.js + Vue 3 — tables, blueprint forms, filters, actions, drawers, widgets.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"author": "Maarten Thiebou",
|
|
8
|
+
"sideEffects": [
|
|
9
|
+
"*.css"
|
|
10
|
+
],
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"styles",
|
|
14
|
+
"docs",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"module": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./styles": "./styles/index.css",
|
|
26
|
+
"./styles/*": "./styles/*"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "vite build",
|
|
30
|
+
"prepublishOnly": "npm run build",
|
|
31
|
+
"test": "vitest --run --config vitest.config.ts",
|
|
32
|
+
"type-check": "vue-tsc --noEmit -p tsconfig.json",
|
|
33
|
+
"lint": "eslint src"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@heroicons/vue": "^2.0.0",
|
|
37
|
+
"@inertiajs/vue3": "^3.0.0",
|
|
38
|
+
"@vueuse/core": ">=10",
|
|
39
|
+
"lodash": "^4.17.0",
|
|
40
|
+
"prosemirror-commands": "^1.7.1",
|
|
41
|
+
"prosemirror-dropcursor": "^1.8.3",
|
|
42
|
+
"prosemirror-gapcursor": "^1.4.1",
|
|
43
|
+
"prosemirror-history": "^1.5.0",
|
|
44
|
+
"prosemirror-inputrules": "^1.5.1",
|
|
45
|
+
"prosemirror-keymap": "^1.2.3",
|
|
46
|
+
"prosemirror-model": "^1.25.11",
|
|
47
|
+
"prosemirror-schema-list": "^1.5.1",
|
|
48
|
+
"prosemirror-state": "^1.4.4",
|
|
49
|
+
"prosemirror-view": "^1.42.2",
|
|
50
|
+
"vue": "^3.5.0",
|
|
51
|
+
"vuedraggable": "^4.1.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"@vueuse/core": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"lodash": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"prosemirror-commands": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"prosemirror-dropcursor": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"prosemirror-gapcursor": {
|
|
67
|
+
"optional": true
|
|
68
|
+
},
|
|
69
|
+
"prosemirror-history": {
|
|
70
|
+
"optional": true
|
|
71
|
+
},
|
|
72
|
+
"prosemirror-inputrules": {
|
|
73
|
+
"optional": true
|
|
74
|
+
},
|
|
75
|
+
"prosemirror-keymap": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"prosemirror-model": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"prosemirror-schema-list": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"prosemirror-state": {
|
|
85
|
+
"optional": true
|
|
86
|
+
},
|
|
87
|
+
"prosemirror-view": {
|
|
88
|
+
"optional": true
|
|
89
|
+
},
|
|
90
|
+
"vuedraggable": {
|
|
91
|
+
"optional": true
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"devDependencies": {
|
|
95
|
+
"@heroicons/vue": "^2.2.0",
|
|
96
|
+
"@inertiajs/vue3": "3.0.0",
|
|
97
|
+
"@types/lodash": "^4.17.24",
|
|
98
|
+
"@typescript-eslint/eslint-plugin": "^8.68.0",
|
|
99
|
+
"@typescript-eslint/parser": "^8.68.0",
|
|
100
|
+
"@vitejs/plugin-vue": "^6.0.1",
|
|
101
|
+
"@vue/test-utils": "^2.4.6",
|
|
102
|
+
"@vueuse/core": "^13.0.0",
|
|
103
|
+
"eslint": "^9.39.5",
|
|
104
|
+
"eslint-plugin-vue": "^10.10.0",
|
|
105
|
+
"happy-dom": "^20.6.1",
|
|
106
|
+
"lodash": "^4.17.21",
|
|
107
|
+
"prosemirror-commands": "^1.7.1",
|
|
108
|
+
"prosemirror-dropcursor": "^1.8.3",
|
|
109
|
+
"prosemirror-gapcursor": "^1.4.1",
|
|
110
|
+
"prosemirror-history": "^1.5.0",
|
|
111
|
+
"prosemirror-inputrules": "^1.5.1",
|
|
112
|
+
"prosemirror-keymap": "^1.2.3",
|
|
113
|
+
"prosemirror-model": "^1.25.11",
|
|
114
|
+
"prosemirror-schema-list": "^1.5.1",
|
|
115
|
+
"prosemirror-state": "^1.4.4",
|
|
116
|
+
"prosemirror-view": "^1.42.2",
|
|
117
|
+
"typescript": "^5.9.3",
|
|
118
|
+
"vite": "^6.4.1",
|
|
119
|
+
"vite-plugin-dts": "^5.0.3",
|
|
120
|
+
"vitest": "^4.0.18",
|
|
121
|
+
"vue": "^3.5.18",
|
|
122
|
+
"vue-eslint-parser": "^10.4.1",
|
|
123
|
+
"vue-tsc": "^3.2.4",
|
|
124
|
+
"vuedraggable": "^4.1.0"
|
|
125
|
+
},
|
|
126
|
+
"repository": {
|
|
127
|
+
"type": "git",
|
|
128
|
+
"url": "git+https://github.com/modufolio/panel.git",
|
|
129
|
+
"directory": "ui"
|
|
130
|
+
},
|
|
131
|
+
"homepage": "https://github.com/modufolio/panel/tree/main/ui#readme",
|
|
132
|
+
"bugs": "https://github.com/modufolio/panel/issues",
|
|
133
|
+
"keywords": [
|
|
134
|
+
"vue",
|
|
135
|
+
"inertia",
|
|
136
|
+
"admin",
|
|
137
|
+
"panel",
|
|
138
|
+
"crud",
|
|
139
|
+
"table",
|
|
140
|
+
"form",
|
|
141
|
+
"blueprint",
|
|
142
|
+
"filament-alternative"
|
|
143
|
+
],
|
|
144
|
+
"publishConfig": {
|
|
145
|
+
"access": "public"
|
|
146
|
+
},
|
|
147
|
+
"dependencies": {
|
|
148
|
+
"@floating-ui/vue": "^2.0.1"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.btn-indigo {
|
|
2
|
+
@apply px-6 py-3 rounded-sm bg-indigo-600 text-white text-sm leading-4 font-bold whitespace-nowrap hover:bg-orange-400 focus:bg-orange-400;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.btn-spinner,
|
|
6
|
+
.btn-spinner:after {
|
|
7
|
+
border-radius: 50%;
|
|
8
|
+
width: 1.5em;
|
|
9
|
+
height: 1.5em;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.btn-spinner {
|
|
13
|
+
font-size: 10px;
|
|
14
|
+
position: relative;
|
|
15
|
+
text-indent: -9999em;
|
|
16
|
+
border-top: 0.2em solid white;
|
|
17
|
+
border-right: 0.2em solid white;
|
|
18
|
+
border-bottom: 0.2em solid white;
|
|
19
|
+
border-left: 0.2em solid transparent;
|
|
20
|
+
transform: translateZ(0);
|
|
21
|
+
animation: spinning 1s infinite linear;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@keyframes spinning {
|
|
25
|
+
0% {
|
|
26
|
+
transform: rotate(0deg);
|
|
27
|
+
}
|
|
28
|
+
100% {
|
|
29
|
+
transform: rotate(360deg);
|
|
30
|
+
}
|
|
31
|
+
}
|
package/styles/form.css
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
.form-label {
|
|
2
|
+
@apply mb-2 block text-gray-700 select-none;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.form-input,
|
|
6
|
+
.form-textarea,
|
|
7
|
+
.form-select {
|
|
8
|
+
@apply p-2 leading-normal block w-full border text-gray-700 bg-white font-sans rounded-sm text-left appearance-none relative focus:border-indigo-400 focus:ring-3;
|
|
9
|
+
|
|
10
|
+
&::placeholder {
|
|
11
|
+
@apply text-gray-500 opacity-100;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.form-select {
|
|
16
|
+
@apply pr-6;
|
|
17
|
+
|
|
18
|
+
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAQCAYAAAAMJL+VAAAABGdBTUEAALGPC/xhBQAAAQtJREFUOBG1lEEOgjAQRalbGj2OG9caOACn4ALGtfEuHACiazceR1PWOH/CNA3aMiTaBDpt/7zPdBKy7M/DCL9pGkvxxVp7KsvyJftL5rZt1865M+Ucq6pyyF3hNcI7Cuu+728QYn/JQA5yKaempxuZmQngOwEaYx55nu+1lQh8GIatMGi+01NwBcEmhxBqK4nAPZJ78K0KKFAJmR3oPp8+Iwgob0Oa6+TLoeCvRx+mTUYf/FVBGTPRwDkfLxnaSrRwcH0FWhNOmrkWYbE2XEicqgSa1J0LQ+aPCuQgZiLnwewbGuz5MGoAhcIkCQcjaTBjMgtXGURMVHC1wcQEy0J+Zlj8bKAnY1/UzDe2dbAVqfXn6wAAAABJRU5ErkJggg==');
|
|
19
|
+
background-size: 0.7rem;
|
|
20
|
+
background-repeat: no-repeat;
|
|
21
|
+
background-position: right 0.7rem center;
|
|
22
|
+
|
|
23
|
+
&::-ms-expand {
|
|
24
|
+
@apply opacity-0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.form-input.error,
|
|
29
|
+
.form-textarea.error,
|
|
30
|
+
.form-select.error {
|
|
31
|
+
@apply border-red-500 focus:ring-3 focus:ring-red-200;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.form-error {
|
|
35
|
+
@apply text-red-700 mt-2 text-sm;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Standard bordered form control: inputs, selects, textareas, and any custom
|
|
40
|
+
* control that should read as a form field (contenteditable editors, tag and
|
|
41
|
+
* multi-select wrappers) — matching by role rather than by tag name, so a
|
|
42
|
+
* div-based editor cannot drift away from a plain <input>.
|
|
43
|
+
*
|
|
44
|
+
* Skin and default padding only. Layout (`w-full`, `flex-1`, explicit widths),
|
|
45
|
+
* padding overrides and behaviour (`resize-y`, `appearance-none`) stay on the
|
|
46
|
+
* element: Tailwind's utilities layer wins over this components layer, so a
|
|
47
|
+
* local utility always overrides the default.
|
|
48
|
+
*/
|
|
49
|
+
.ui-input {
|
|
50
|
+
/* focus-within, not focus: an <input> matches its own :focus-within, so the
|
|
51
|
+
same rule covers both plain controls and div-based wrappers. */
|
|
52
|
+
@apply border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 shadow-sm transition-colors
|
|
53
|
+
focus-within:border-primary-600 focus-within:ring-2 focus-within:ring-primary-600/20
|
|
54
|
+
disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-500;
|
|
55
|
+
|
|
56
|
+
border-radius: var(--radius-input);
|
|
57
|
+
min-height: var(--input-height);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Single-line controls get an exact height so they line up regardless of
|
|
61
|
+
border and line-height; textareas and div-based wrappers only get a floor. */
|
|
62
|
+
input.ui-input,
|
|
63
|
+
select.ui-input {
|
|
64
|
+
height: var(--input-height);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Opt out for controls that are deliberately compact (filter bars, inline
|
|
68
|
+
editors) and size themselves from their own padding instead. */
|
|
69
|
+
.ui-input-auto,
|
|
70
|
+
input.ui-input-auto,
|
|
71
|
+
select.ui-input-auto {
|
|
72
|
+
height: auto;
|
|
73
|
+
min-height: 0;
|
|
74
|
+
}
|
package/styles/index.css
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @modufolio/panel styles entry.
|
|
3
|
+
*
|
|
4
|
+
* Import from the consumer's Tailwind CSS entry, inside the components layer:
|
|
5
|
+
*
|
|
6
|
+
* @import '@modufolio/panel/styles' layer(components);
|
|
7
|
+
*
|
|
8
|
+
* and add the package to Tailwind's content scanning so component classes
|
|
9
|
+
* survive the build:
|
|
10
|
+
*
|
|
11
|
+
* @source "../node_modules/@modufolio/panel";
|
|
12
|
+
*/
|
|
13
|
+
@import './tokens.css';
|
|
14
|
+
@import './buttons.css';
|
|
15
|
+
@import './form.css';
|