@alfakit/react 0.1.1 → 0.1.6
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/CHANGELOG.md +16 -0
- package/dist/components/actions/action-button.d.ts.map +1 -1
- package/dist/components/actions/action-button.js +5 -4
- package/dist/components/actions/action-button.js.map +1 -1
- package/dist/components/actions/action-modals.d.ts.map +1 -1
- package/dist/components/actions/action-modals.js +6 -5
- package/dist/components/actions/action-modals.js.map +1 -1
- package/dist/components/ai/copilot.d.ts +3 -1
- package/dist/components/ai/copilot.d.ts.map +1 -1
- package/dist/components/ai/copilot.js +4 -1
- package/dist/components/ai/copilot.js.map +1 -1
- package/dist/components/cms/live-preview.js +3 -2
- package/dist/components/cms/live-preview.js.map +1 -1
- package/dist/components/cms/page.js +1 -1
- package/dist/components/fields/builder.js +15 -6
- package/dist/components/fields/builder.js.map +1 -1
- package/dist/components/fields/color-picker.js +1 -0
- package/dist/components/fields/color-picker.js.map +1 -1
- package/dist/components/fields/file-upload.js +11 -10
- package/dist/components/fields/file-upload.js.map +1 -1
- package/dist/components/fields/image-editor-dialog.js +4 -2
- package/dist/components/fields/image-editor-dialog.js.map +1 -1
- package/dist/components/fields/item-actions.js +10 -10
- package/dist/components/fields/item-actions.js.map +1 -1
- package/dist/components/fields/key-value.d.ts.map +1 -1
- package/dist/components/fields/key-value.js +3 -3
- package/dist/components/fields/key-value.js.map +1 -1
- package/dist/components/fields/modal-table-select.js +6 -5
- package/dist/components/fields/modal-table-select.js.map +1 -1
- package/dist/components/fields/one-time-code-input.js +1 -1
- package/dist/components/fields/one-time-code-input.js.map +1 -1
- package/dist/components/fields/repeater-table.js +12 -1
- package/dist/components/fields/repeater-table.js.map +1 -1
- package/dist/components/fields/repeater.js +2 -2
- package/dist/components/fields/repeater.js.map +1 -1
- package/dist/components/fields/rich-editor.js +2 -1
- package/dist/components/fields/rich-editor.js.map +1 -1
- package/dist/components/fields/select.js +3 -3
- package/dist/components/fields/select.js.map +1 -1
- package/dist/components/file-manager/file-manager.js +1 -1
- package/dist/components/file-manager/file-manager.js.map +1 -1
- package/dist/components/form/form-block.d.ts.map +1 -1
- package/dist/components/form/form-block.js +14 -2
- package/dist/components/form/form-block.js.map +1 -1
- package/dist/components/form/form-context.js +7 -3
- package/dist/components/form/form-context.js.map +1 -1
- package/dist/components/kanban/kanban.js +4 -3
- package/dist/components/kanban/kanban.js.map +1 -1
- package/dist/components/layout/fused-group.js +1 -0
- package/dist/components/layout/fused-group.js.map +1 -1
- package/dist/components/layout/section.js +2 -2
- package/dist/components/layout/section.js.map +1 -1
- package/dist/components/layout/tabs.d.ts.map +1 -1
- package/dist/components/layout/tabs.js +54 -15
- package/dist/components/layout/tabs.js.map +1 -1
- package/dist/components/panel/app-sidebar.d.ts +2 -3
- package/dist/components/panel/app-sidebar.d.ts.map +1 -1
- package/dist/components/panel/app-sidebar.js +1 -24
- package/dist/components/panel/app-sidebar.js.map +1 -1
- package/dist/components/panel/brand.d.ts +10 -0
- package/dist/components/panel/brand.d.ts.map +1 -0
- package/dist/components/panel/brand.js +37 -0
- package/dist/components/panel/brand.js.map +1 -0
- package/dist/components/panel/global-search.js +1 -0
- package/dist/components/panel/global-search.js.map +1 -1
- package/dist/components/panel/index.d.ts +2 -1
- package/dist/components/panel/shortcut-keys.js +1 -0
- package/dist/components/panel/shortcut-keys.js.map +1 -1
- package/dist/components/panel/tenant-menu.js +2 -2
- package/dist/components/panel/tenant-menu.js.map +1 -1
- package/dist/components/panel/topbar.js +1 -1
- package/dist/components/realtime/live-records-pill.d.ts.map +1 -1
- package/dist/components/realtime/live-records-pill.js +6 -3
- package/dist/components/realtime/live-records-pill.js.map +1 -1
- package/dist/components/table/cells.d.ts +6 -0
- package/dist/components/table/cells.d.ts.map +1 -1
- package/dist/components/table/cells.js +15 -3
- package/dist/components/table/cells.js.map +1 -1
- package/dist/components/table/column-manager.js +8 -7
- package/dist/components/table/column-manager.js.map +1 -1
- package/dist/components/table/content-layout.js +2 -1
- package/dist/components/table/content-layout.js.map +1 -1
- package/dist/components/table/grouping.js +11 -1
- package/dist/components/table/grouping.js.map +1 -1
- package/dist/components/table/pagination.js +16 -13
- package/dist/components/table/pagination.js.map +1 -1
- package/dist/components/table/table-block.js +14 -4
- package/dist/components/table/table-block.js.map +1 -1
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog.js +3 -1
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/popover.d.ts.map +1 -1
- package/dist/components/ui/popover.js +3 -1
- package/dist/components/ui/popover.js.map +1 -1
- package/dist/components/ui/sheet.d.ts.map +1 -1
- package/dist/components/ui/sheet.js +3 -1
- package/dist/components/ui/sheet.js.map +1 -1
- package/dist/components/ui/tooltip.js +3 -1
- package/dist/components/ui/tooltip.js.map +1 -1
- package/dist/components.css +347 -0
- package/dist/context.d.ts +5 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +5 -1
- package/dist/context.js.map +1 -1
- package/dist/create-alfakit.d.ts.map +1 -1
- package/dist/create-alfakit.js +8 -5
- package/dist/create-alfakit.js.map +1 -1
- package/dist/eject-manifest.json +16 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +24 -21
- package/dist/layouts/app-layout.d.ts.map +1 -1
- package/dist/layouts/app-layout.js +2 -29
- package/dist/layouts/app-layout.js.map +1 -1
- package/dist/layouts/auth-layout.js +2 -2
- package/dist/layouts/auth-layout.js.map +1 -1
- package/dist/layouts/fullscreen-layout.js +1 -1
- package/dist/layouts/fullscreen-layout.js.map +1 -1
- package/dist/layouts/lazy-layouts.js +15 -0
- package/dist/layouts/lazy-layouts.js.map +1 -0
- package/dist/layouts/max-widths.js +34 -0
- package/dist/layouts/max-widths.js.map +1 -0
- package/dist/layouts/panel-layouts.d.ts +10 -0
- package/dist/layouts/panel-layouts.d.ts.map +1 -0
- package/dist/layouts/panel-layouts.js +20 -0
- package/dist/layouts/panel-layouts.js.map +1 -0
- package/dist/layouts/top-nav-layout.js +2 -2
- package/dist/layouts/top-nav-layout.js.map +1 -1
- package/dist/pages/page.d.ts.map +1 -1
- package/dist/pages/page.js +12 -7
- package/dist/pages/page.js.map +1 -1
- package/dist/standalone/alfakit-component.js +11 -6
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone/alfakit-toaster.d.ts.map +1 -1
- package/dist/standalone/alfakit-toaster.js +8 -1
- package/dist/standalone/alfakit-toaster.js.map +1 -1
- package/dist/standalone.css +54 -0
- package/dist/starter-kit/dashboard-widgets.d.ts +33 -0
- package/dist/starter-kit/dashboard-widgets.d.ts.map +1 -0
- package/dist/starter-kit/dashboard-widgets.js +53 -0
- package/dist/starter-kit/dashboard-widgets.js.map +1 -0
- package/dist/starter-kit/impersonation-banner.js +34 -0
- package/dist/starter-kit/impersonation-banner.js.map +1 -0
- package/dist/starter-kit/index.d.ts +3 -2
- package/dist/starter-kit/index.js +3 -2
- package/dist/starter-kit/starter-kit.d.ts +6 -0
- package/dist/starter-kit/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit/starter-kit.js +18 -7
- package/dist/starter-kit/starter-kit.js.map +1 -1
- package/dist/starter-kit/widgets-loaded.js +21 -5
- package/dist/starter-kit/widgets-loaded.js.map +1 -1
- package/dist/starter-kit/widgets-triggers.js +35 -3
- package/dist/starter-kit/widgets-triggers.js.map +1 -1
- package/dist/starter-kit/widgets.d.ts +13 -5
- package/dist/starter-kit/widgets.d.ts.map +1 -1
- package/dist/starter-kit/widgets.js +56 -18
- package/dist/starter-kit/widgets.js.map +1 -1
- package/dist/styles.css +15 -2
- package/dist/themes.css +8 -335
- package/package.json +2 -2
- package/src/components/actions/action-button.tsx +7 -4
- package/src/components/actions/action-modals.tsx +13 -1
- package/src/components/ai/copilot.tsx +14 -1
- package/src/components/cms/live-preview.tsx +5 -2
- package/src/components/fields/builder.tsx +14 -5
- package/src/components/fields/color-picker.tsx +1 -0
- package/src/components/fields/file-upload.tsx +21 -10
- package/src/components/fields/image-editor-dialog.tsx +7 -2
- package/src/components/fields/item-actions.tsx +32 -9
- package/src/components/fields/key-value.tsx +3 -2
- package/src/components/fields/modal-table-select.tsx +8 -3
- package/src/components/fields/one-time-code-input.tsx +1 -1
- package/src/components/fields/repeater-table.tsx +12 -1
- package/src/components/fields/repeater.tsx +2 -3
- package/src/components/fields/rich-editor.tsx +3 -0
- package/src/components/fields/select.tsx +7 -3
- package/src/components/file-manager/file-manager.tsx +1 -1
- package/src/components/form/form-block.tsx +26 -1
- package/src/components/form/form-context.tsx +3 -6
- package/src/components/kanban/kanban.tsx +16 -3
- package/src/components/layout/fused-group.tsx +5 -1
- package/src/components/layout/section.tsx +2 -2
- package/src/components/layout/tabs.tsx +72 -17
- package/src/components/panel/app-sidebar.tsx +2 -38
- package/src/components/panel/brand.tsx +46 -0
- package/src/components/panel/global-search.tsx +1 -1
- package/src/components/panel/shortcut-keys.tsx +4 -1
- package/src/components/panel/tenant-menu.tsx +2 -2
- package/src/components/realtime/live-records-pill.tsx +4 -3
- package/src/components/table/cells.tsx +27 -5
- package/src/components/table/column-manager.tsx +12 -10
- package/src/components/table/content-layout.tsx +5 -1
- package/src/components/table/grouping.tsx +15 -1
- package/src/components/table/pagination.tsx +16 -9
- package/src/components/table/table-block.tsx +25 -12
- package/src/components/ui/dialog.tsx +5 -1
- package/src/components/ui/popover.tsx +5 -1
- package/src/components/ui/sheet.tsx +5 -1
- package/src/components/ui/tooltip.tsx +5 -1
- package/src/context.tsx +10 -0
- package/src/create-alfakit.tsx +11 -2
- package/src/index.ts +2 -0
- package/src/layouts/app-layout.tsx +4 -28
- package/src/layouts/auth-layout.tsx +2 -2
- package/src/layouts/fullscreen-layout.tsx +1 -1
- package/src/layouts/lazy-layouts.ts +14 -0
- package/src/layouts/max-widths.ts +29 -0
- package/src/layouts/panel-layouts.ts +17 -0
- package/src/layouts/top-nav-layout.tsx +2 -2
- package/src/pages/page.tsx +21 -12
- package/src/standalone/alfakit-component.tsx +18 -7
- package/src/standalone/alfakit-toaster.tsx +21 -2
- package/src/starter-kit/dashboard-widgets.tsx +78 -0
- package/src/starter-kit/impersonation-banner.tsx +48 -0
- package/src/starter-kit/index.ts +2 -0
- package/src/starter-kit/starter-kit.tsx +31 -4
- package/src/starter-kit/widgets-loaded.tsx +23 -1
- package/src/starter-kit/widgets-triggers.tsx +37 -2
- package/src/starter-kit/widgets.tsx +95 -20
- package/src/styles.css +15 -2
package/dist/themes.css
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@import './components.css';
|
|
2
|
+
|
|
1
3
|
/*
|
|
2
4
|
* Alfakit themes. Shared by every renderer and imported by their `styles.css`.
|
|
3
5
|
*
|
|
@@ -285,7 +287,7 @@
|
|
|
285
287
|
--secondary: oklch(0.967 0.003 264.542);
|
|
286
288
|
--secondary-foreground: oklch(0.21 0.034 264.665);
|
|
287
289
|
--muted: oklch(0.967 0.003 264.542);
|
|
288
|
-
--muted-foreground: oklch(0.
|
|
290
|
+
--muted-foreground: oklch(0.535 0.027 264.364);
|
|
289
291
|
--accent: oklch(0.967 0.003 264.542);
|
|
290
292
|
--accent-foreground: oklch(0.21 0.034 264.665);
|
|
291
293
|
--border: oklch(0.928 0.006 264.531);
|
|
@@ -676,6 +678,11 @@
|
|
|
676
678
|
color: var(--primary);
|
|
677
679
|
}
|
|
678
680
|
|
|
681
|
+
/* The schema inspector's round button (dev tools) sits above the bar, not on its last item. */
|
|
682
|
+
:root[data-mobile='bottom'] [data-slot='dev-tools-toggle'] {
|
|
683
|
+
bottom: calc(var(--alfakit-bottom-nav-height) + env(safe-area-inset-bottom, 0px) + 1rem);
|
|
684
|
+
}
|
|
685
|
+
|
|
679
686
|
/* Room for the bar, sticky actions sit right above it. */
|
|
680
687
|
:root[data-mobile='bottom'] [data-slot='content'] {
|
|
681
688
|
padding-bottom: calc(
|
|
@@ -1004,337 +1011,3 @@
|
|
|
1004
1011
|
box-shadow: none;
|
|
1005
1012
|
outline: none;
|
|
1006
1013
|
}
|
|
1007
|
-
|
|
1008
|
-
/* ------------------------------------------------------------------ Charts */
|
|
1009
|
-
|
|
1010
|
-
/*
|
|
1011
|
-
* Chart.js paints axes, legends and tooltips on the canvas, so chart widgets read these
|
|
1012
|
-
* properties from <html> (re-read when the theme or dark mode changes). Colours take any
|
|
1013
|
-
* CSS colour; the widths and radius are unitless numbers. Override them in a theme:
|
|
1014
|
-
* :root[data-alfakit-theme~='acme'] { --chart-tooltip-bg: oklch(0.2 0 0); }
|
|
1015
|
-
* Stat sparklines (`[data-slot='stat-chart']`) use the `--stat-chart-*` properties.
|
|
1016
|
-
*/
|
|
1017
|
-
:root {
|
|
1018
|
-
--chart-text: var(--muted-foreground);
|
|
1019
|
-
--chart-grid: var(--border);
|
|
1020
|
-
--chart-legend-foreground: var(--muted-foreground);
|
|
1021
|
-
--chart-tooltip-bg: var(--popover);
|
|
1022
|
-
--chart-tooltip-foreground: var(--popover-foreground);
|
|
1023
|
-
--chart-tooltip-border: var(--border);
|
|
1024
|
-
--chart-tooltip-border-width: 1;
|
|
1025
|
-
--chart-tooltip-radius: 6;
|
|
1026
|
-
--stat-chart-stroke-width: 2;
|
|
1027
|
-
--stat-chart-fill-opacity: 0.12;
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
/*
|
|
1031
|
-
* RichEditor `grid` column blocks and `lead` / `small` text, in the editor and in rendered
|
|
1032
|
-
* content (`RichContentRenderer`). Columns stack on small screens. Stored as
|
|
1033
|
-
* `<div data-type="grid" class="alfakit-grid alfakit-grid-cols-2"><div data-type="gridColumn">`
|
|
1034
|
-
* and `<p class="alfakit-lead">`; copy these rules when showing content outside a panel.
|
|
1035
|
-
*/
|
|
1036
|
-
:where(.alfakit-grid) {
|
|
1037
|
-
display: grid;
|
|
1038
|
-
gap: 1rem;
|
|
1039
|
-
grid-template-columns: minmax(0, 1fr);
|
|
1040
|
-
margin-block: 0.5rem;
|
|
1041
|
-
}
|
|
1042
|
-
:where(.alfakit-grid-column) {
|
|
1043
|
-
min-width: 0;
|
|
1044
|
-
}
|
|
1045
|
-
:where(.alfakit-grid-column) > :first-child {
|
|
1046
|
-
margin-block-start: 0;
|
|
1047
|
-
}
|
|
1048
|
-
@media (min-width: 768px) {
|
|
1049
|
-
:where(.alfakit-grid-cols-2) {
|
|
1050
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1051
|
-
}
|
|
1052
|
-
:where(.alfakit-grid-cols-3) {
|
|
1053
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1054
|
-
}
|
|
1055
|
-
:where(.alfakit-grid-cols-4) {
|
|
1056
|
-
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
1057
|
-
}
|
|
1058
|
-
:where(.alfakit-grid-cols-5) {
|
|
1059
|
-
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
1060
|
-
}
|
|
1061
|
-
:where(.alfakit-grid-cols-6) {
|
|
1062
|
-
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
:where([contenteditable] .alfakit-grid-column) {
|
|
1066
|
-
border: 1px dashed var(--border);
|
|
1067
|
-
border-radius: 0.375rem;
|
|
1068
|
-
padding: 0.25rem 0.5rem;
|
|
1069
|
-
}
|
|
1070
|
-
:where(.alfakit-lead) {
|
|
1071
|
-
font-size: 1.125em;
|
|
1072
|
-
line-height: 1.6;
|
|
1073
|
-
}
|
|
1074
|
-
:where(.alfakit-prose small) {
|
|
1075
|
-
font-size: 0.8125em;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
/* ---------------------------------------------------------- Calendar and tree */
|
|
1079
|
-
|
|
1080
|
-
/*
|
|
1081
|
-
* The calendar widget (alfasix/alfakit-calendar) draws FullCalendar inside
|
|
1082
|
-
* `[data-slot="calendar"]`; FullCalendar reads these `--fc-*` variables, so every theme's
|
|
1083
|
-
* tokens (and dark mode) apply. Override them on the slot to restyle one calendar.
|
|
1084
|
-
*/
|
|
1085
|
-
:where(.fc[data-slot='calendar']) {
|
|
1086
|
-
--fc-small-font-size: 0.75rem;
|
|
1087
|
-
--fc-page-bg-color: var(--card);
|
|
1088
|
-
--fc-neutral-bg-color: var(--muted);
|
|
1089
|
-
--fc-neutral-text-color: var(--muted-foreground);
|
|
1090
|
-
--fc-border-color: var(--border);
|
|
1091
|
-
--fc-button-text-color: var(--foreground);
|
|
1092
|
-
--fc-button-bg-color: var(--background);
|
|
1093
|
-
--fc-button-border-color: var(--border);
|
|
1094
|
-
--fc-button-hover-bg-color: var(--accent);
|
|
1095
|
-
--fc-button-hover-border-color: var(--border);
|
|
1096
|
-
--fc-button-active-bg-color: var(--secondary);
|
|
1097
|
-
--fc-button-active-border-color: var(--border);
|
|
1098
|
-
--fc-event-bg-color: var(--primary);
|
|
1099
|
-
--fc-event-border-color: var(--primary);
|
|
1100
|
-
--fc-event-text-color: var(--primary-foreground);
|
|
1101
|
-
--fc-event-selected-overlay-color: color-mix(in oklab, var(--foreground) 25%, transparent);
|
|
1102
|
-
--fc-more-link-bg-color: var(--muted);
|
|
1103
|
-
--fc-more-link-text-color: var(--foreground);
|
|
1104
|
-
--fc-non-business-color: color-mix(in oklab, var(--muted) 60%, transparent);
|
|
1105
|
-
--fc-bg-event-color: var(--primary);
|
|
1106
|
-
--fc-bg-event-opacity: 0.2;
|
|
1107
|
-
--fc-highlight-color: color-mix(in oklab, var(--primary) 15%, transparent);
|
|
1108
|
-
--fc-today-bg-color: color-mix(in oklab, var(--primary) 6%, transparent);
|
|
1109
|
-
--fc-now-indicator-color: var(--destructive);
|
|
1110
|
-
--fc-list-event-hover-bg-color: var(--accent);
|
|
1111
|
-
color: var(--card-foreground);
|
|
1112
|
-
}
|
|
1113
|
-
/*
|
|
1114
|
-
* FullCalendar puts its `fc` class on the slot element itself and injects its own CSS
|
|
1115
|
-
* (`.fc .fc-button`, two classes), so these rules are plain selectors anchored on
|
|
1116
|
-
* `.fc[data-slot='calendar']` (three) rather than `:where()`, which would always lose.
|
|
1117
|
-
* Restyle one calendar with a more specific selector or with the `--fc-*` variables.
|
|
1118
|
-
*/
|
|
1119
|
-
.fc[data-slot='calendar'] .fc-toolbar-title {
|
|
1120
|
-
font-size: 1.125rem;
|
|
1121
|
-
font-weight: 600;
|
|
1122
|
-
line-height: 1.75rem;
|
|
1123
|
-
color: var(--foreground);
|
|
1124
|
-
}
|
|
1125
|
-
.fc[data-slot='calendar'] .fc-button {
|
|
1126
|
-
display: inline-flex;
|
|
1127
|
-
align-items: center;
|
|
1128
|
-
justify-content: center;
|
|
1129
|
-
min-height: 2.25rem;
|
|
1130
|
-
padding: 0.375rem 0.75rem;
|
|
1131
|
-
border-radius: var(--radius);
|
|
1132
|
-
font-size: 0.875rem;
|
|
1133
|
-
font-weight: 500;
|
|
1134
|
-
line-height: 1.25rem;
|
|
1135
|
-
box-shadow: none;
|
|
1136
|
-
text-transform: capitalize;
|
|
1137
|
-
}
|
|
1138
|
-
.fc[data-slot='calendar'] .fc-button .fc-icon {
|
|
1139
|
-
font-size: 1.125rem;
|
|
1140
|
-
}
|
|
1141
|
-
.fc[data-slot='calendar'] .fc-button-group > .fc-button:not(:first-child) {
|
|
1142
|
-
border-start-start-radius: 0;
|
|
1143
|
-
border-end-start-radius: 0;
|
|
1144
|
-
}
|
|
1145
|
-
.fc[data-slot='calendar'] .fc-button-group > .fc-button:not(:last-child) {
|
|
1146
|
-
border-start-end-radius: 0;
|
|
1147
|
-
border-end-end-radius: 0;
|
|
1148
|
-
}
|
|
1149
|
-
.fc[data-slot='calendar'] .fc-button-primary:disabled {
|
|
1150
|
-
opacity: 0.5;
|
|
1151
|
-
}
|
|
1152
|
-
.fc[data-slot='calendar'] .fc-button-primary:not(:disabled).fc-button-active,
|
|
1153
|
-
.fc[data-slot='calendar'] .fc-button-primary:not(:disabled):active {
|
|
1154
|
-
color: var(--secondary-foreground);
|
|
1155
|
-
}
|
|
1156
|
-
.fc[data-slot='calendar'] .fc-button-primary:focus,
|
|
1157
|
-
.fc[data-slot='calendar'] .fc-button-primary:not(:disabled).fc-button-active:focus,
|
|
1158
|
-
.fc[data-slot='calendar'] .fc-button-primary:not(:disabled):active:focus {
|
|
1159
|
-
box-shadow: none;
|
|
1160
|
-
}
|
|
1161
|
-
.fc[data-slot='calendar'] .fc-button:focus-visible {
|
|
1162
|
-
outline: 2px solid var(--ring);
|
|
1163
|
-
outline-offset: 2px;
|
|
1164
|
-
z-index: 2;
|
|
1165
|
-
}
|
|
1166
|
-
.fc[data-slot='calendar'] a {
|
|
1167
|
-
color: inherit;
|
|
1168
|
-
}
|
|
1169
|
-
.fc[data-slot='calendar'] .fc-daygrid-day-number,
|
|
1170
|
-
.fc[data-slot='calendar'] .fc-col-header-cell-cushion {
|
|
1171
|
-
text-decoration: none;
|
|
1172
|
-
}
|
|
1173
|
-
.fc[data-slot='calendar'] .fc-col-header-cell-cushion {
|
|
1174
|
-
padding-block: 0.375rem;
|
|
1175
|
-
font-size: 0.75rem;
|
|
1176
|
-
font-weight: 500;
|
|
1177
|
-
color: var(--muted-foreground);
|
|
1178
|
-
}
|
|
1179
|
-
.fc[data-slot='calendar'] .fc-daygrid-day-number {
|
|
1180
|
-
font-size: 0.875rem;
|
|
1181
|
-
}
|
|
1182
|
-
.fc[data-slot='calendar'] .fc-day-other .fc-daygrid-day-number {
|
|
1183
|
-
color: var(--muted-foreground);
|
|
1184
|
-
}
|
|
1185
|
-
/* Today: a tinted cell (`--fc-today-bg-color`) and the date in a primary pill. */
|
|
1186
|
-
.fc[data-slot='calendar'] .fc-daygrid-day.fc-day-today .fc-daygrid-day-number {
|
|
1187
|
-
display: inline-flex;
|
|
1188
|
-
align-items: center;
|
|
1189
|
-
justify-content: center;
|
|
1190
|
-
min-width: 1.75rem;
|
|
1191
|
-
height: 1.75rem;
|
|
1192
|
-
margin: 0.25rem;
|
|
1193
|
-
padding: 0 0.375rem;
|
|
1194
|
-
border-radius: 9999px;
|
|
1195
|
-
background: var(--primary);
|
|
1196
|
-
color: var(--primary-foreground);
|
|
1197
|
-
font-weight: 600;
|
|
1198
|
-
}
|
|
1199
|
-
.fc[data-slot='calendar'] .fc-col-header-cell.fc-day-today .fc-col-header-cell-cushion {
|
|
1200
|
-
color: var(--primary);
|
|
1201
|
-
font-weight: 600;
|
|
1202
|
-
}
|
|
1203
|
-
.fc[data-slot='calendar'] .fc-event {
|
|
1204
|
-
border-radius: min(var(--radius), 0.375rem);
|
|
1205
|
-
}
|
|
1206
|
-
.fc[data-slot='calendar'] .fc-daygrid-event,
|
|
1207
|
-
.fc[data-slot='calendar'] .fc-timegrid-event .fc-event-main,
|
|
1208
|
-
.fc[data-slot='calendar'] .fc-list-event td {
|
|
1209
|
-
font-size: 0.75rem;
|
|
1210
|
-
}
|
|
1211
|
-
.fc[data-slot='calendar'] .fc-daygrid-dot-event:hover,
|
|
1212
|
-
.fc[data-slot='calendar'] .fc-daygrid-dot-event.fc-event-mirror {
|
|
1213
|
-
background: var(--accent);
|
|
1214
|
-
}
|
|
1215
|
-
.fc[data-slot='calendar'] .fc-list-day-cushion,
|
|
1216
|
-
.fc[data-slot='calendar'] .fc-popover-header {
|
|
1217
|
-
color: var(--foreground);
|
|
1218
|
-
}
|
|
1219
|
-
.fc[data-slot='calendar'] .fc-popover {
|
|
1220
|
-
border-radius: var(--radius);
|
|
1221
|
-
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1);
|
|
1222
|
-
overflow: hidden;
|
|
1223
|
-
}
|
|
1224
|
-
.fc[data-slot='calendar'] .fc-timegrid-slot-label,
|
|
1225
|
-
.fc[data-slot='calendar'] .fc-timegrid-axis,
|
|
1226
|
-
.fc[data-slot='calendar'] .fc-list-event-time {
|
|
1227
|
-
color: var(--muted-foreground);
|
|
1228
|
-
font-size: 0.75rem;
|
|
1229
|
-
}
|
|
1230
|
-
/*
|
|
1231
|
-
* The list view hides its column headers for screen readers with `left: -10000px`. Right to
|
|
1232
|
-
* left that is scrollable overflow: the whole page gains a 10 000 px horizontal scroll and the
|
|
1233
|
-
* top bar slides under the sidebar. Clip it in place instead (still read by screen readers).
|
|
1234
|
-
*/
|
|
1235
|
-
.fc[data-slot='calendar'] .fc-list-table thead {
|
|
1236
|
-
left: auto;
|
|
1237
|
-
inset-inline-start: 0;
|
|
1238
|
-
width: 1px;
|
|
1239
|
-
height: 1px;
|
|
1240
|
-
overflow: hidden;
|
|
1241
|
-
clip-path: inset(50%);
|
|
1242
|
-
white-space: nowrap;
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
/*
|
|
1246
|
-
* Fields contain what they position. Headless switches, checkboxes and radios render a
|
|
1247
|
-
* visually hidden native input beside the control, absolutely positioned (and shifted by
|
|
1248
|
-
* -100%); without a positioned field it lands at the page edge and, right to left, widens
|
|
1249
|
-
* the page on phones.
|
|
1250
|
-
*/
|
|
1251
|
-
:where([data-slot='field']) {
|
|
1252
|
-
position: relative;
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
/*
|
|
1256
|
-
* Narrow screens: FullCalendar keeps its header toolbar on one row, wider than a phone.
|
|
1257
|
-
* Its chunks (navigation, title, views) wrap instead, and the title shrinks. Flex gaps and
|
|
1258
|
-
* `justify-content` follow the writing direction, so this works right to left too. (Plain
|
|
1259
|
-
* selectors: FullCalendar injects its own CSS, which would beat `:where()`; the slot is
|
|
1260
|
-
* the `.fc` element itself.)
|
|
1261
|
-
*/
|
|
1262
|
-
@media (max-width: 39.99rem) {
|
|
1263
|
-
[data-slot='calendar'] .fc-toolbar.fc-header-toolbar {
|
|
1264
|
-
flex-wrap: wrap;
|
|
1265
|
-
gap: 0.5rem;
|
|
1266
|
-
}
|
|
1267
|
-
[data-slot='calendar'] .fc-toolbar .fc-toolbar-chunk {
|
|
1268
|
-
display: flex;
|
|
1269
|
-
flex-wrap: wrap;
|
|
1270
|
-
align-items: center;
|
|
1271
|
-
gap: 0.25rem;
|
|
1272
|
-
min-width: 0;
|
|
1273
|
-
}
|
|
1274
|
-
[data-slot='calendar'] .fc-toolbar .fc-toolbar-title {
|
|
1275
|
-
font-size: 1rem;
|
|
1276
|
-
overflow-wrap: anywhere;
|
|
1277
|
-
}
|
|
1278
|
-
[data-slot='calendar'] .fc-toolbar .fc-button {
|
|
1279
|
-
font-size: 0.8125rem;
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
/*
|
|
1284
|
-
* The tree (alfasix/alfakit-tree): the drop indicator while dragging. Rows carry
|
|
1285
|
-
* `data-drop="before|after|inside"`; lines are drawn above or below the row, so they
|
|
1286
|
-
* need no direction.
|
|
1287
|
-
*/
|
|
1288
|
-
:where([data-slot='tree-item'][data-drop='before']) {
|
|
1289
|
-
box-shadow: inset 0 2px 0 0 var(--primary);
|
|
1290
|
-
}
|
|
1291
|
-
:where([data-slot='tree-item'][data-drop='after']) {
|
|
1292
|
-
box-shadow: inset 0 -2px 0 0 var(--primary);
|
|
1293
|
-
}
|
|
1294
|
-
:where([data-slot='tree-item'][data-drop='inside']) {
|
|
1295
|
-
background-color: color-mix(in oklab, var(--primary) 10%, transparent);
|
|
1296
|
-
outline: 1px dashed var(--primary);
|
|
1297
|
-
outline-offset: -1px;
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
/*
|
|
1301
|
-
* The Kanban board (alfasix/alfakit-kanban). A card being dragged leaves a dashed
|
|
1302
|
-
* placeholder (`data-picked="pointer"`) while a copy follows the pointer
|
|
1303
|
-
* (`data-slot="kanban-ghost"`); a card picked up with the keyboard is lifted
|
|
1304
|
-
* (`data-picked="keyboard"`). A list the picked card may not enter has `data-refused`.
|
|
1305
|
-
*/
|
|
1306
|
-
:where([data-slot='kanban-card'][data-picked='pointer']) {
|
|
1307
|
-
opacity: 0.45;
|
|
1308
|
-
}
|
|
1309
|
-
:where([data-slot='kanban-card'][data-picked='pointer'] > [data-slot='kanban-card-body']) {
|
|
1310
|
-
border-style: dashed;
|
|
1311
|
-
box-shadow: none;
|
|
1312
|
-
}
|
|
1313
|
-
:where([data-slot='kanban-card'][data-picked='keyboard'] > [data-slot='kanban-card-body']) {
|
|
1314
|
-
outline: 2px solid var(--primary);
|
|
1315
|
-
outline-offset: 2px;
|
|
1316
|
-
box-shadow: 0 10px 24px -12px color-mix(in oklab, var(--foreground) 45%, transparent);
|
|
1317
|
-
}
|
|
1318
|
-
:where([data-slot='kanban-card'][data-saving]) {
|
|
1319
|
-
opacity: 0.7;
|
|
1320
|
-
}
|
|
1321
|
-
:where([data-slot='kanban-ghost']) {
|
|
1322
|
-
cursor: grabbing;
|
|
1323
|
-
list-style: none;
|
|
1324
|
-
box-shadow: 0 18px 40px -16px color-mix(in oklab, var(--foreground) 55%, transparent);
|
|
1325
|
-
}
|
|
1326
|
-
:where([data-slot='kanban-dropzone'][data-refused]) {
|
|
1327
|
-
background-color: color-mix(in oklab, var(--destructive) 8%, transparent);
|
|
1328
|
-
outline: 1px dashed var(--destructive);
|
|
1329
|
-
outline-offset: -1px;
|
|
1330
|
-
}
|
|
1331
|
-
:where(body[data-kanban-dragging]) {
|
|
1332
|
-
cursor: grabbing;
|
|
1333
|
-
user-select: none;
|
|
1334
|
-
-webkit-user-select: none;
|
|
1335
|
-
}
|
|
1336
|
-
@media (prefers-reduced-motion: reduce) {
|
|
1337
|
-
:where([data-slot='kanban-card'] > [data-slot='kanban-card-body']) {
|
|
1338
|
-
transition: none;
|
|
1339
|
-
}
|
|
1340
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfakit/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "React + shadcn/ui renderer for Alfakit, the Laravel admin panel builder by Alfa Six.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alfa Six (https://alfasixbh.com)",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"react-day-picker": "^10.0.2",
|
|
97
97
|
"sonner": "^2.0.8",
|
|
98
98
|
"tailwind-merge": "^3.7.0",
|
|
99
|
-
"@alfakit/core": "0.1.
|
|
99
|
+
"@alfakit/core": "0.1.6"
|
|
100
100
|
},
|
|
101
101
|
"peerDependencies": {
|
|
102
102
|
"@inertiajs/react": "^3.0.0",
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
bindsKeyBindings,
|
|
4
4
|
formatKeyBinding,
|
|
5
5
|
iconButtonSize,
|
|
6
|
+
isActionLoading,
|
|
7
|
+
isActionProcessing,
|
|
6
8
|
registerKeyBindings,
|
|
7
9
|
resolveActionSize,
|
|
8
10
|
} from '@alfakit/core'
|
|
@@ -71,7 +73,7 @@ function buttonClasses(action: ActionSchema, variant: ActionSchema['variant']):
|
|
|
71
73
|
return colorClass(ghostButtonColorClasses, color, 'gray')
|
|
72
74
|
case 'link':
|
|
73
75
|
return cn(
|
|
74
|
-
'h-auto p-0 underline-offset-4 hover:underline',
|
|
76
|
+
'h-auto p-0 underline-offset-4 hover:underline has-[>svg]:px-0',
|
|
75
77
|
colorClass(textColorClasses, color, 'primary'),
|
|
76
78
|
)
|
|
77
79
|
case 'icon-button':
|
|
@@ -95,9 +97,10 @@ export function ActionButton({
|
|
|
95
97
|
}: ActionButtonProps) {
|
|
96
98
|
const runner = useActionRunner()
|
|
97
99
|
const t = useT()
|
|
98
|
-
|
|
100
|
+
// Only this run (this record's button), not the same action in every table row.
|
|
101
|
+
const loading = useStoreSelector(runner.store, (state) => isActionLoading(state, action, context))
|
|
99
102
|
const processing = useStoreSelector(runner.store, (state) =>
|
|
100
|
-
state
|
|
103
|
+
isActionProcessing(state, action, context),
|
|
101
104
|
)
|
|
102
105
|
|
|
103
106
|
const variant =
|
|
@@ -244,7 +247,7 @@ export function ActionButton({
|
|
|
244
247
|
<TooltipContent>
|
|
245
248
|
{tooltip}
|
|
246
249
|
{shortcut && (
|
|
247
|
-
<kbd className="ms-2 rounded border border-white/20 px-1 font-mono text-[10px]">
|
|
250
|
+
<kbd dir="ltr" className="ms-2 rounded border border-white/20 px-1 font-mono text-[10px]">
|
|
248
251
|
{formatKeyBinding(shortcut)}
|
|
249
252
|
</kbd>
|
|
250
253
|
)}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
actionFormParams,
|
|
5
5
|
actionPath,
|
|
6
6
|
confirmModalClose,
|
|
7
|
+
formIdPrefix,
|
|
7
8
|
FormStore,
|
|
8
9
|
guardModalUnload,
|
|
9
10
|
isModalHiddenByChild,
|
|
@@ -156,6 +157,8 @@ function ActionModal({ modal, hidden = false }: { modal: OpenModal; hidden?: boo
|
|
|
156
157
|
return new FormStore(schema.form, {
|
|
157
158
|
token: context.token,
|
|
158
159
|
component: actionFormComponent(action, context),
|
|
160
|
+
// Own field ids: the modal opens over a form whose fields may share names.
|
|
161
|
+
idPrefix: formIdPrefix(actionFormComponent(action, context)),
|
|
159
162
|
record: context.record ?? null,
|
|
160
163
|
refreshUrl: panel.endpoints.schemaRefresh,
|
|
161
164
|
optionsUrl: panel.endpoints.schemaOptions,
|
|
@@ -219,6 +222,16 @@ function ActionModal({ modal, hidden = false }: { modal: OpenModal; hidden?: boo
|
|
|
219
222
|
|
|
220
223
|
const submit = (createAnother = false) => {
|
|
221
224
|
submittedRef.current = true
|
|
225
|
+
|
|
226
|
+
// A click that blurred a live field started a refresh: send the state it returns.
|
|
227
|
+
if (form) {
|
|
228
|
+
form.whenSettled(() => run(createAnother))
|
|
229
|
+
} else {
|
|
230
|
+
run(createAnother)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const run = (createAnother: boolean) => {
|
|
222
235
|
const values = form?.state.values ?? {}
|
|
223
236
|
const options = {
|
|
224
237
|
...(schema.passwordConfirmation ? { password } : {}),
|
|
@@ -472,7 +485,6 @@ function ActionModal({ modal, hidden = false }: { modal: OpenModal; hidden?: boo
|
|
|
472
485
|
{footer}
|
|
473
486
|
</DialogFooter>
|
|
474
487
|
)}
|
|
475
|
-
<span className="sr-only">{t('close', {}, 'Close')}</span>
|
|
476
488
|
</DialogContent>
|
|
477
489
|
</Dialog>
|
|
478
490
|
)
|
|
@@ -29,7 +29,14 @@ export function useCopilotState(controller: CopilotController): CopilotState {
|
|
|
29
29
|
* a chat slide-over. Questions, tools and actions run on the server; proposed actions
|
|
30
30
|
* run only when the user confirms them in the chat.
|
|
31
31
|
*/
|
|
32
|
-
export function AiCopilot({
|
|
32
|
+
export function AiCopilot({
|
|
33
|
+
component,
|
|
34
|
+
autoOpen = false,
|
|
35
|
+
}: {
|
|
36
|
+
component: AiCopilotSchema
|
|
37
|
+
/** Open once mounted (starter kit header: clicked or `mod+j` before its code loaded). */
|
|
38
|
+
autoOpen?: boolean
|
|
39
|
+
}) {
|
|
33
40
|
const t = useT()
|
|
34
41
|
// One controller per panel: Inertia visits pass new props objects.
|
|
35
42
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -42,6 +49,12 @@ export function AiCopilot({ component }: { component: AiCopilotSchema }) {
|
|
|
42
49
|
// The panel mounts the first time the copilot opens (button or shortcut), then stays.
|
|
43
50
|
useEffect(() => controller.subscribe((next) => next.open && setMounted(true)), [controller])
|
|
44
51
|
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (autoOpen && component.canUse !== false) controller.open()
|
|
54
|
+
// Only when it mounts.
|
|
55
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
56
|
+
}, [controller])
|
|
57
|
+
|
|
45
58
|
useEffect(() => {
|
|
46
59
|
const bindings = JSON.parse(keys) as string[]
|
|
47
60
|
|
|
@@ -78,6 +78,9 @@ export function CmsLivePreview({ component }: { component: CmsLivePreviewSchema
|
|
|
78
78
|
const autosaveRef = useRef<CmsAutosave | null>(null)
|
|
79
79
|
const status =
|
|
80
80
|
snapshot.hasChanges && component.changedStatus ? component.changedStatus : component.status
|
|
81
|
+
// "Publish changes" appears as soon as an autosave makes the draft differ.
|
|
82
|
+
const actions =
|
|
83
|
+
snapshot.hasChanges && component.changedActions ? component.changedActions : component.actions
|
|
81
84
|
|
|
82
85
|
// Measured in the browser after mounting: stick right under the panel's topbar when it
|
|
83
86
|
// sticks too, and open the pane beside the form on wide screens (the viewer's last
|
|
@@ -220,9 +223,9 @@ export function CmsLivePreview({ component }: { component: CmsLivePreviewSchema
|
|
|
220
223
|
: t('cms.live_preview', {}, 'Live preview')}
|
|
221
224
|
</Button>
|
|
222
225
|
)}
|
|
223
|
-
{
|
|
226
|
+
{actions && actions.length > 0 && (
|
|
224
227
|
<div onPointerDownCapture={() => void autosaveRef.current?.flush()}>
|
|
225
|
-
<ActionList actions={
|
|
228
|
+
<ActionList actions={actions} context={actionContext} size="sm" />
|
|
226
229
|
</div>
|
|
227
230
|
)}
|
|
228
231
|
</div>
|
|
@@ -89,7 +89,7 @@ function BuilderFieldContent({ component }: { component: BuilderSchema }) {
|
|
|
89
89
|
const context = useFieldActionContext(component)
|
|
90
90
|
const runner = useActionRunner()
|
|
91
91
|
const runItemAction = useRunItemAction(context)
|
|
92
|
-
const fallbacks = useItemActionFallbacks(component.addActionLabel)
|
|
92
|
+
const fallbacks = useItemActionFallbacks(component.addActionLabel, 'builder')
|
|
93
93
|
const resolve = useItemActions(component, fallbacks)
|
|
94
94
|
// Long lists render only the visible blocks (plus a few around them).
|
|
95
95
|
const virtual = useVirtualList(
|
|
@@ -142,6 +142,13 @@ function BuilderFieldContent({ component }: { component: BuilderSchema }) {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
const blockOf = (type: string) => component.blocks.find((block) => block.name === type)
|
|
145
|
+
// Summaries are made on the server: collapsing items whose block has one brings them up
|
|
146
|
+
// to date with the edits.
|
|
147
|
+
const refreshSummaries = (indexes: number[]) => {
|
|
148
|
+
if (indexes.some((index) => blockOf(items[index]?.type ?? '')?.summary)) {
|
|
149
|
+
void form?.refresh(component.statePath)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
145
152
|
const underMax = component.maxItems == null || items.length < component.maxItems
|
|
146
153
|
const canAdd = component.addable && !locked && underMax
|
|
147
154
|
const canDelete =
|
|
@@ -197,9 +204,10 @@ function BuilderFieldContent({ component }: { component: BuilderSchema }) {
|
|
|
197
204
|
<CollapseAllActions
|
|
198
205
|
context={context}
|
|
199
206
|
resolve={(name) => resolve(name)}
|
|
200
|
-
onCollapseAll={() =>
|
|
207
|
+
onCollapseAll={() => {
|
|
201
208
|
setCollapsed(Object.fromEntries(items.map((_, index) => [index, true])))
|
|
202
|
-
|
|
209
|
+
refreshSummaries(items.map((_, index) => index))
|
|
210
|
+
}}
|
|
203
211
|
onExpandAll={() =>
|
|
204
212
|
setCollapsed(Object.fromEntries(items.map((_, index) => [index, false])))
|
|
205
213
|
}
|
|
@@ -317,9 +325,10 @@ function BuilderFieldContent({ component }: { component: BuilderSchema }) {
|
|
|
317
325
|
onMove={moveWithButton}
|
|
318
326
|
onClone={() => cloneItem(index)}
|
|
319
327
|
onDelete={() => removeItem(index)}
|
|
320
|
-
onToggle={() =>
|
|
328
|
+
onToggle={() => {
|
|
321
329
|
setCollapsed((state) => ({ ...state, [index]: !isCollapsed }))
|
|
322
|
-
|
|
330
|
+
if (!isCollapsed) refreshSummaries([index])
|
|
331
|
+
}}
|
|
323
332
|
>
|
|
324
333
|
{hasPreview && !locked && (
|
|
325
334
|
<Button
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
editableImageName,
|
|
7
7
|
fetchEditableImage,
|
|
8
8
|
fieldMemory,
|
|
9
|
+
fileManagerSize,
|
|
9
10
|
filesFromClipboard,
|
|
10
11
|
fileUploadAccept,
|
|
11
12
|
fileUploadPaths,
|
|
12
|
-
formatBytes,
|
|
13
13
|
insertUploadedFile,
|
|
14
14
|
moveRepeaterItem,
|
|
15
15
|
panelUploadOptions,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
XIcon,
|
|
30
30
|
} from 'lucide-react'
|
|
31
31
|
import { useRef, useState, type ClipboardEvent, type DragEvent } from 'react'
|
|
32
|
-
import { useChoice, usePanel, useT } from '../../context'
|
|
32
|
+
import { useChoice, useFormatter, usePanel, useT } from '../../context'
|
|
33
33
|
import { useStore } from '../../hooks/use-store'
|
|
34
34
|
import { cn } from '../../lib/utils'
|
|
35
35
|
import { useField, useFormStore } from '../form/form-context'
|
|
@@ -47,6 +47,7 @@ interface LocalFile extends UploadedFile {
|
|
|
47
47
|
|
|
48
48
|
export function FileUploadField({ component }: { component: FileUploadSchema }) {
|
|
49
49
|
const t = useT()
|
|
50
|
+
const formatter = useFormatter()
|
|
50
51
|
const choice = useChoice()
|
|
51
52
|
const panel = usePanel()
|
|
52
53
|
const { value, setValue, error, id, disabled, readOnly, aria } = useField<
|
|
@@ -375,7 +376,7 @@ export function FileUploadField({ component }: { component: FileUploadSchema })
|
|
|
375
376
|
variant={circle ? 'secondary' : 'ghost'}
|
|
376
377
|
size="icon-xs"
|
|
377
378
|
className={cn(circle && 'rounded-full shadow')}
|
|
378
|
-
aria-label={t('remove', {}, 'Remove')}
|
|
379
|
+
aria-label={t('file_upload.remove', { name: file.name }, 'Remove :name')}
|
|
379
380
|
onClick={() => {
|
|
380
381
|
if (file.error || file.progress !== undefined) {
|
|
381
382
|
releasePreview(file.preview)
|
|
@@ -467,7 +468,7 @@ export function FileUploadField({ component }: { component: FileUploadSchema })
|
|
|
467
468
|
: file.progress !== undefined && !file.error
|
|
468
469
|
? (component.uploadingMessage ?? t('form.uploading', {}, 'Uploading…'))
|
|
469
470
|
: file.size
|
|
470
|
-
?
|
|
471
|
+
? fileManagerSize(file.size, formatter)
|
|
471
472
|
: file.temporary
|
|
472
473
|
? t('form.uploading', {}, 'Pending')
|
|
473
474
|
: '')}
|
|
@@ -486,7 +487,7 @@ export function FileUploadField({ component }: { component: FileUploadSchema })
|
|
|
486
487
|
type="button"
|
|
487
488
|
variant="ghost"
|
|
488
489
|
size="icon-xs"
|
|
489
|
-
aria-label={t('
|
|
490
|
+
aria-label={t('file_upload.move_up', { name: file.name }, 'Move :name up')}
|
|
490
491
|
disabled={index === 0}
|
|
491
492
|
onClick={() => move(index, index - 1)}
|
|
492
493
|
>
|
|
@@ -496,7 +497,11 @@ export function FileUploadField({ component }: { component: FileUploadSchema })
|
|
|
496
497
|
type="button"
|
|
497
498
|
variant="ghost"
|
|
498
499
|
size="icon-xs"
|
|
499
|
-
aria-label={t(
|
|
500
|
+
aria-label={t(
|
|
501
|
+
'file_upload.move_down',
|
|
502
|
+
{ name: file.name },
|
|
503
|
+
'Move :name down',
|
|
504
|
+
)}
|
|
500
505
|
disabled={index === files.length - 1}
|
|
501
506
|
onClick={() => move(index, index + 1)}
|
|
502
507
|
>
|
|
@@ -510,7 +515,7 @@ export function FileUploadField({ component }: { component: FileUploadSchema })
|
|
|
510
515
|
variant={circle ? 'secondary' : 'ghost'}
|
|
511
516
|
size="icon-xs"
|
|
512
517
|
className={cn(circle && 'rounded-full shadow')}
|
|
513
|
-
aria-label={t('
|
|
518
|
+
aria-label={t('file_upload.edit', { name: file.name }, 'Edit :name')}
|
|
514
519
|
title={t('image_editor.title', {}, 'Edit image')}
|
|
515
520
|
data-action="edit-image"
|
|
516
521
|
onClick={() => void editExisting(file)}
|
|
@@ -524,7 +529,7 @@ export function FileUploadField({ component }: { component: FileUploadSchema })
|
|
|
524
529
|
href={uploadedFileLinks(file).open}
|
|
525
530
|
target="_blank"
|
|
526
531
|
rel="noopener noreferrer"
|
|
527
|
-
aria-label={`${t('open', {}, 'Open')} ${t('opens_in_new_tab', {}, '(opens in a new tab)')}`}
|
|
532
|
+
aria-label={`${t('file_upload.open', { name: file.name }, 'Open :name')} ${t('opens_in_new_tab', {}, '(opens in a new tab)')}`}
|
|
528
533
|
>
|
|
529
534
|
<ExternalLinkIcon />
|
|
530
535
|
</a>
|
|
@@ -535,7 +540,11 @@ export function FileUploadField({ component }: { component: FileUploadSchema })
|
|
|
535
540
|
<a
|
|
536
541
|
href={uploadedFileLinks(file).download}
|
|
537
542
|
download
|
|
538
|
-
aria-label={t(
|
|
543
|
+
aria-label={t(
|
|
544
|
+
'file_upload.download',
|
|
545
|
+
{ name: file.name },
|
|
546
|
+
'Download :name',
|
|
547
|
+
)}
|
|
539
548
|
>
|
|
540
549
|
<DownloadIcon />
|
|
541
550
|
</a>
|
|
@@ -596,7 +605,9 @@ export function FileUploadField({ component }: { component: FileUploadSchema })
|
|
|
596
605
|
<span className="text-muted-foreground text-xs">
|
|
597
606
|
{[
|
|
598
607
|
component.acceptedFileTypes?.join(', '),
|
|
599
|
-
component.maxSize
|
|
608
|
+
component.maxSize
|
|
609
|
+
? `≤ ${fileManagerSize(component.maxSize * 1024, formatter)}`
|
|
610
|
+
: null,
|
|
600
611
|
component.minFiles
|
|
601
612
|
? choice(
|
|
602
613
|
'form.min_files',
|