themelia-react-ui 2.0.3 → 2.0.5
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 +185 -0
- package/README.md +168 -36
- package/dist/{accordion-Cjb6sx0G.js → accordion-B9MHG0cT.js} +11 -11
- package/dist/accordion-B9MHG0cT.js.map +1 -0
- package/dist/action-menu-Dr8K-ghE.js +284 -0
- package/dist/action-menu-Dr8K-ghE.js.map +1 -0
- package/dist/{affordance-geometry.generated-tF36bREk.js → affordance-geometry.generated-P-aVJisu.js} +42 -36
- package/dist/affordance-geometry.generated-P-aVJisu.js.map +1 -0
- package/dist/{ai-artifact-DvTwDVyL.js → ai-artifact-C58tQcBy.js} +387 -350
- package/dist/ai-artifact-C58tQcBy.js.map +1 -0
- package/dist/app-shell-B_IuNWOo.js +321 -0
- package/dist/app-shell-B_IuNWOo.js.map +1 -0
- package/dist/{attachment-BxDfL2mw.js → attachment-Dg_69lPS.js} +9 -9
- package/dist/{attachment-BxDfL2mw.js.map → attachment-Dg_69lPS.js.map} +1 -1
- package/dist/{auth-BGua8ouh.js → auth-DTkkshS8.js} +44 -47
- package/dist/auth-DTkkshS8.js.map +1 -0
- package/dist/{avatar-DAPlDvhJ.js → avatar-CeZaFmNH.js} +8 -8
- package/dist/avatar-CeZaFmNH.js.map +1 -0
- package/dist/{badge-HpFqoqSY.js → badge-xIiV_E7f.js} +5 -5
- package/dist/{badge-HpFqoqSY.js.map → badge-xIiV_E7f.js.map} +1 -1
- package/dist/base/accordion.js +1 -1
- package/dist/base/action-menu.js +1 -1
- package/dist/base/attachment.js +1 -1
- package/dist/base/avatar.js +1 -1
- package/dist/base/badge.js +1 -1
- package/dist/base/batch-action-bar.js +1 -1
- package/dist/base/buttons.js +1 -1
- package/dist/base/cards.js +2 -2
- package/dist/base/carousel.js +3 -3
- package/dist/base/cascader.js +2 -372
- package/dist/base/chart.js +1 -1
- package/dist/base/choice-inputs.js +2 -2
- package/dist/base/code-block.js +1 -1
- package/dist/base/combobox.js +2 -2
- package/dist/base/command.js +1 -1
- package/dist/base/copyable.js +1 -1
- package/dist/base/date-pickers.js +2 -2
- package/dist/base/display.js +6 -6
- package/dist/base/feedback.js +3 -3
- package/dist/base/forms-numeric.js +1 -1
- package/dist/base/forms.js +3 -3
- package/dist/base/inline-edit.js +10 -10
- package/dist/base/item.js +1 -1
- package/dist/base/media-gallery.js +1 -1
- package/dist/base/metadata-list.js +1 -1
- package/dist/base/navigation.js +7 -7
- package/dist/base/popover-menu.js +1 -1
- package/dist/base/popover.js +1 -1
- package/dist/base/qr-code.js +1 -1
- package/dist/base/repeaters.js +12 -12
- package/dist/base/repeaters.js.map +1 -1
- package/dist/base/sheet.js +1 -1
- package/dist/base/sidebar.js +2 -2
- package/dist/base/signature-pad.js +7 -7
- package/dist/base/signature-pad.js.map +1 -1
- package/dist/base/skeleton.js +1 -1
- package/dist/base/spinner.js +4 -4
- package/dist/base/structure.js +1 -1
- package/dist/base/table.js +1 -1
- package/dist/base/text-inputs.js +2 -2
- package/dist/base/toaster.js +36 -36
- package/dist/base/toaster.js.map +1 -1
- package/dist/base/tree.js +1 -1
- package/dist/base/upload-queue.js +1 -1
- package/dist/base/upload.js +3 -3
- package/dist/base/value-inputs.js +1 -1
- package/dist/{batch-action-bar-DnUJ21NQ.js → batch-action-bar-Cb8cuvyy.js} +6 -6
- package/dist/batch-action-bar-Cb8cuvyy.js.map +1 -0
- package/dist/blocks/admin.js +23 -23
- package/dist/blocks/admin.js.map +1 -1
- package/dist/blocks/ai.js +230 -216
- package/dist/blocks/ai.js.map +1 -1
- package/dist/blocks/analytics/usage-card.js +1 -1
- package/dist/blocks/analytics.js +326 -307
- package/dist/blocks/analytics.js.map +1 -1
- package/dist/blocks/collaboration.js +1 -1
- package/dist/blocks/commerce/receipt.js +1 -1
- package/dist/blocks/commerce.js +1247 -1169
- package/dist/blocks/commerce.js.map +1 -1
- package/dist/blocks/data-display.js +2 -2
- package/dist/blocks/onboarding.js +6 -6
- package/dist/blocks/onboarding.js.map +1 -1
- package/dist/blocks/planning.js +11 -11
- package/dist/blocks/timelines.js +1 -1
- package/dist/{breadcrumbs-C-oqs1sQ.js → breadcrumbs-J-K2kl4c.js} +6 -6
- package/dist/{breadcrumbs-C-oqs1sQ.js.map → breadcrumbs-J-K2kl4c.js.map} +1 -1
- package/dist/{button-DVzspB_G.js → button-S2Cubiiz.js} +3 -3
- package/dist/button-S2Cubiiz.js.map +1 -0
- package/dist/{buttons-DPZekJFu.js → buttons-BedGfygC.js} +16 -16
- package/dist/buttons-BedGfygC.js.map +1 -0
- package/dist/cards-rNX1M4Xn.js +69 -0
- package/dist/cards-rNX1M4Xn.js.map +1 -0
- package/dist/cascader-B1MiqkBq.js +439 -0
- package/dist/cascader-B1MiqkBq.js.map +1 -0
- package/dist/chart-Darg_0Kv.js +162 -0
- package/dist/chart-Darg_0Kv.js.map +1 -0
- package/dist/{checkbox-AWIhNhmo.js → checkbox-fflqmODR.js} +9 -5
- package/dist/checkbox-fflqmODR.js.map +1 -0
- package/dist/choice-inputs-zUgBZfE4.js +777 -0
- package/dist/choice-inputs-zUgBZfE4.js.map +1 -0
- package/dist/{code-block-DeneBJR_.js → code-block-BzmM9Eig.js} +9 -9
- package/dist/code-block-BzmM9Eig.js.map +1 -0
- package/dist/{collapsible-C-_l4hpy.js → collapsible-BkAI_XD4.js} +5 -5
- package/dist/{collapsible-C-_l4hpy.js.map → collapsible-BkAI_XD4.js.map} +1 -1
- package/dist/{collection-content-By56pLog.js → collection-content-gDh1v6O4.js} +3 -3
- package/dist/{collection-content-By56pLog.js.map → collection-content-gDh1v6O4.js.map} +1 -1
- package/dist/combobox-BEx0vJgF.js +362 -0
- package/dist/combobox-BEx0vJgF.js.map +1 -0
- package/dist/{combobox-Dxo2fAYQ.js → combobox-BwzoXFSG.js} +254 -247
- package/dist/combobox-BwzoXFSG.js.map +1 -0
- package/dist/{command-C25VMRII.js → command-CrlPIL6m.js} +12 -12
- package/dist/command-CrlPIL6m.js.map +1 -0
- package/dist/{comments-DD0DUPwJ.js → comments-Dmpp0YIc.js} +380 -379
- package/dist/comments-Dmpp0YIc.js.map +1 -0
- package/dist/components/base/action-menu/action-menu.d.ts +9 -1
- package/dist/components/base/buttons/button-group.recipes.d.ts +2 -2
- package/dist/components/base/cards/card.recipes.d.ts +16 -7
- package/dist/components/base/cards/partials/card-primitives.d.ts +9 -2
- package/dist/components/base/cards/partials/smart-card-header-row.d.ts +3 -1
- package/dist/components/base/cards/smart-card.types.d.ts +11 -7
- package/dist/components/base/cascader/cascader-panel.d.ts +76 -0
- package/dist/components/base/chart/chart.d.ts +6 -1
- package/dist/components/base/choice-inputs/checkbox.d.ts +7 -2
- package/dist/components/base/choice-inputs/choice-types.d.ts +1 -1
- package/dist/components/base/choice-inputs/switch-card.d.ts +7 -0
- package/dist/components/base/choice-inputs/toggle-field.d.ts +7 -0
- package/dist/components/base/combobox/index.d.ts +4 -2
- package/dist/components/base/date-pickers/datepicker.types.d.ts +7 -0
- package/dist/components/base/feedback/alert.d.ts +1 -1
- package/dist/components/base/forms/controlled-form-field.d.ts +6 -6
- package/dist/components/base/forms/form-field.d.ts +5 -3
- package/dist/components/base/forms/form-sizing.d.ts +7 -7
- package/dist/components/base/forms/index.d.ts +1 -0
- package/dist/components/base/forms/server-errors.d.ts +16 -0
- package/dist/components/base/forms/workflow.d.ts +11 -2
- package/dist/components/base/forms-numeric/coordinates-input.d.ts +4 -0
- package/dist/components/base/metadata-list/metadata-list.d.ts +3 -1
- package/dist/components/base/navigation/dropdown-menu.d.ts +1 -1
- package/dist/components/base/navigation/page-header/page-header-commands.d.ts +14 -7
- package/dist/components/base/navigation/page-header/page-header.d.ts +13 -4
- package/dist/components/base/popover/popover.d.ts +3 -3
- package/dist/components/base/popover/popover.recipes.d.ts +10 -6
- package/dist/components/base/sheet/sheet.d.ts +2 -0
- package/dist/components/base/sidebar/index.d.ts +1 -1
- package/dist/components/base/sidebar/sidebar.d.ts +27 -5
- package/dist/components/base/structure/composition.recipes.d.ts +17 -0
- package/dist/components/base/text-inputs/hooks/use-form-field-state.d.ts +7 -2
- package/dist/components/base/text-inputs/input.d.ts +4 -2
- package/dist/components/base/text-inputs/textarea.d.ts +4 -2
- package/dist/components/base/toaster/index.d.ts +1 -0
- package/dist/components/base/value-inputs/date-time-input.d.ts +2 -1
- package/dist/components/base/value-inputs/slider.d.ts +5 -1
- package/dist/components/base/value-inputs/tags-input.d.ts +5 -1
- package/dist/components/blocks/ai/ai-file-tree/types.d.ts +1 -1
- package/dist/components/blocks/ai/ai-tool-call/types.d.ts +3 -1
- package/dist/components/blocks/ai/ai.strings.d.ts +2 -0
- package/dist/components/blocks/analytics/metric-comparison/metric-comparison.d.ts +2 -0
- package/dist/components/blocks/analytics/usage-card/usage-card.strings.d.ts +2 -0
- package/dist/components/blocks/commerce/cart-summary/cart-summary.d.ts +1 -1
- package/dist/components/blocks/commerce/cart-summary/types.d.ts +3 -1
- package/dist/components/blocks/commerce/discount-stack/discount-stack.d.ts +1 -1
- package/dist/components/blocks/commerce/discount-stack/types.d.ts +3 -1
- package/dist/components/blocks/commerce/fulfillment-group/fulfillment-group.d.ts +1 -1
- package/dist/components/blocks/commerce/fulfillment-group/types.d.ts +11 -2
- package/dist/components/blocks/commerce/index.d.ts +7 -6
- package/dist/components/blocks/commerce/inventory-section/inventory-section.d.ts +1 -1
- package/dist/components/blocks/commerce/inventory-section/types.d.ts +7 -2
- package/dist/components/blocks/commerce/invoice-header/index.d.ts +2 -1
- package/dist/components/blocks/commerce/invoice-header/invoice-header.d.ts +1 -1
- package/dist/components/blocks/commerce/invoice-header/types.d.ts +20 -6
- package/dist/components/blocks/commerce/invoice-line-items/invoice-line-items.d.ts +1 -1
- package/dist/components/blocks/commerce/invoice-line-items/types.d.ts +24 -7
- package/dist/components/blocks/commerce/invoice-mini/index.d.ts +2 -1
- package/dist/components/blocks/commerce/invoice-mini/types.d.ts +7 -1
- package/dist/components/blocks/commerce/order-customer/order-customer.d.ts +1 -1
- package/dist/components/blocks/commerce/order-customer/types.d.ts +9 -1
- package/dist/components/blocks/commerce/refund-status/refund-status.d.ts +1 -1
- package/dist/components/blocks/commerce/refund-status/types.d.ts +3 -1
- package/dist/components/blocks/commerce/shipment-tracking/shipment-tracking.d.ts +1 -1
- package/dist/components/blocks/commerce/shipment-tracking/types.d.ts +3 -1
- package/dist/components/blocks/commerce/tax-breakdown/tax-breakdown.d.ts +1 -1
- package/dist/components/blocks/commerce/tax-breakdown/types.d.ts +3 -1
- package/dist/components/features/activities/activities.types.d.ts +8 -2
- package/dist/components/features/activities/partials/activity-marker.d.ts +7 -3
- package/dist/components/features/activities/partials/activity-row.d.ts +24 -0
- package/dist/components/features/event-calendar/colors.d.ts +2 -2
- package/dist/components/features/event-calendar/event-calendar.types.d.ts +4 -0
- package/dist/components/features/filters/facets/select-facet.d.ts +2 -1
- package/dist/components/features/filters/filters.strings.d.ts +4 -0
- package/dist/components/features/filters/filters.types.d.ts +3 -0
- package/dist/components/features/filters/partials/filter-editor-header.d.ts +16 -0
- package/dist/components/features/filters/partials/filter-popover-content.d.ts +1 -1
- package/dist/components/features/filters/partials/nested-filter-options.d.ts +21 -0
- package/dist/components/features/filters/partials/regular-filter-content.d.ts +2 -1
- package/dist/components/features/filters/utils/filter-utils.d.ts +21 -3
- package/dist/components/features/global-search/global-search.types.d.ts +2 -0
- package/dist/components/features/global-search/partials/global-search-input.d.ts +10 -0
- package/dist/components/features/global-search/partials/global-search-result-row.d.ts +7 -1
- package/dist/components/features/media-library/media-library.strings.d.ts +2 -0
- package/dist/components/features/media-library/media-library.types.d.ts +14 -4
- package/dist/components/features/media-library/partials/media-library-partials.d.ts +1 -1
- package/dist/components/features/products/components/product-options-matrix.d.ts +1 -1
- package/dist/components/features/products/components/product-variant-editor.d.ts +1 -1
- package/dist/components/features/products/components/product-variants-manager.d.ts +1 -1
- package/dist/components/features/products/products.strings.d.ts +9 -1
- package/dist/components/features/products/products.types.d.ts +43 -6
- package/dist/components/features/resource/resource.d.ts +5 -1
- package/dist/components/features/resource/resource.types.d.ts +12 -1
- package/dist/components/features/rich-text-editor/partials/image-uploads.d.ts +1 -1
- package/dist/components/features/rich-text-editor/rich-text-editor.recipes.d.ts +12 -2
- package/dist/components/features/table/partials/resource-cell.d.ts +22 -5
- package/dist/components/features/table/table.types.d.ts +1 -1
- package/dist/components/layout/app-shell/app-shell.types.d.ts +35 -6
- package/dist/components/layout/app-shell/index.d.ts +1 -1
- package/dist/components/layout/app-shell/shell-regions.d.ts +27 -0
- package/dist/components/layout/app-shell/sidebar-inset-layout.d.ts +1 -1
- package/dist/components/layout/app-shell/stacked-layout.d.ts +1 -1
- package/dist/components/layout/app-shell/topbar-sidebar-layout.d.ts +1 -1
- package/dist/components/layout/containers/two-column-layout.d.ts +2 -0
- package/dist/components/layout/header/header.recipes.d.ts +6 -0
- package/dist/components/layout/header/header.types.d.ts +6 -6
- package/dist/components/layout/navigation/section-nav/section-nav.d.ts +5 -0
- package/dist/components/layout/page/index.d.ts +2 -1
- package/dist/components/layout/page/page.d.ts +1 -0
- package/dist/components/layout/page/page.strings.d.ts +11 -0
- package/dist/components/layout/page/page.types.d.ts +69 -17
- package/dist/components/layout/page/partials/page-header.d.ts +1 -1
- package/dist/components/layout/settings/settings.d.ts +9 -2
- package/dist/components/layout/sidebar/app-sidebar.d.ts +1 -1
- package/dist/components/layout/sidebar/components/sidebar-grouped-navigation.d.ts +1 -1
- package/dist/components/layout/sidebar/components/sidebar-navigation.d.ts +1 -1
- package/dist/components/layout/sidebar/index.d.ts +2 -1
- package/dist/components/layout/sidebar/sidebar-navigation-rail.d.ts +36 -0
- package/dist/components/layout/sidebar/sidebar.context.d.ts +5 -7
- package/dist/components/layout/sidebar/sidebar.types.d.ts +48 -10
- package/dist/components/layout/sidebar/sidebar.utils.d.ts +9 -1
- package/dist/components/layout/workspace/workspace.types.d.ts +8 -1
- package/dist/components/typography/text.styles.d.ts +26 -1
- package/dist/components/ui/alert-dialog.d.ts +2 -2
- package/dist/components/ui/combobox.d.ts +1 -1
- package/dist/components/ui/dialog.d.ts +2 -2
- package/dist/components/ui/overlay-layer.d.ts +18 -0
- package/dist/components/ui/sheet.d.ts +5 -1
- package/dist/components/ui/sidebar-state.d.ts +82 -0
- package/dist/components/ui/sidebar.d.ts +5 -17
- package/dist/composition.recipes-COdiFhxB.js +19 -0
- package/dist/composition.recipes-COdiFhxB.js.map +1 -0
- package/dist/containers-CIxwbZt5.js +112 -0
- package/dist/containers-CIxwbZt5.js.map +1 -0
- package/dist/{control-group-recipes-DBXmfh2E.js → control-group-recipes-fMuFo0H7.js} +6 -6
- package/dist/control-group-recipes-fMuFo0H7.js.map +1 -0
- package/dist/{copyable-ClBuk_UK.js → copyable-DIznBqiT.js} +7 -7
- package/dist/{copyable-ClBuk_UK.js.map → copyable-DIznBqiT.js.map} +1 -1
- package/dist/{data-display-DT_khPl_.js → data-display-BdgCAw8T.js} +277 -285
- package/dist/data-display-BdgCAw8T.js.map +1 -0
- package/dist/{date-DCrsDTZc.js → date-Cq8ioE4o.js} +5 -5
- package/dist/{date-DCrsDTZc.js.map → date-Cq8ioE4o.js.map} +1 -1
- package/dist/{date-picker-B2K3ucWn.js → date-picker-RlIzKom_.js} +501 -480
- package/dist/date-picker-RlIzKom_.js.map +1 -0
- package/dist/date-pickers-DY-CpXS1.js +179 -0
- package/dist/date-pickers-DY-CpXS1.js.map +1 -0
- package/dist/{display-hAc1HjpI.js → display-KsZLw9Ov.js} +9 -9
- package/dist/{display-hAc1HjpI.js.map → display-KsZLw9Ov.js.map} +1 -1
- package/dist/dropdown-menu-CLs8Lihj.js +194 -0
- package/dist/dropdown-menu-CLs8Lihj.js.map +1 -0
- package/dist/features/activities.js +302 -253
- package/dist/features/activities.js.map +1 -1
- package/dist/features/ai-chat.js +15 -14
- package/dist/features/ai-chat.js.map +1 -1
- package/dist/features/appointment-calendar.js +222 -220
- package/dist/features/appointment-calendar.js.map +1 -1
- package/dist/features/async-preview.js +11 -10
- package/dist/features/async-preview.js.map +1 -1
- package/dist/features/combobox.js +1 -1
- package/dist/features/comments.js +1 -1
- package/dist/features/data-view.js +13 -13
- package/dist/features/data-view.js.map +1 -1
- package/dist/features/email-composer.js +249 -249
- package/dist/features/email-composer.js.map +1 -1
- package/dist/features/event-calendar.js +264 -269
- package/dist/features/event-calendar.js.map +1 -1
- package/dist/features/filters.js +1 -1
- package/dist/features/global-search.js +167 -138
- package/dist/features/global-search.js.map +1 -1
- package/dist/features/kanban.js +6 -7
- package/dist/features/kanban.js.map +1 -1
- package/dist/features/map.js +269 -260
- package/dist/features/map.js.map +1 -1
- package/dist/features/media-library.js +827 -777
- package/dist/features/media-library.js.map +1 -1
- package/dist/features/mentions.js +1 -1
- package/dist/features/overlays.js +1 -1
- package/dist/features/products.js +1246 -1169
- package/dist/features/products.js.map +1 -1
- package/dist/features/questionnaire.js +13 -13
- package/dist/features/questionnaire.js.map +1 -1
- package/dist/features/resource-assignment.js +8 -8
- package/dist/features/resource.js +127 -123
- package/dist/features/resource.js.map +1 -1
- package/dist/features/rich-text-editor.js +2 -2
- package/dist/features/schema-form.js +254 -253
- package/dist/features/schema-form.js.map +1 -1
- package/dist/features/suggestions.js +3 -3
- package/dist/features/sync.js +3 -3
- package/dist/features/table.js +1 -1
- package/dist/features/theme-tweaker.js +95 -96
- package/dist/features/theme-tweaker.js.map +1 -1
- package/dist/{feedback-BYIhtitP.js → feedback-Dm4izqFn.js} +81 -80
- package/dist/feedback-Dm4izqFn.js.map +1 -0
- package/dist/{field-affordance-recipes-DZhnIL_Y.js → field-affordance-recipes-DWcNcOjh.js} +4 -4
- package/dist/field-affordance-recipes-DWcNcOjh.js.map +1 -0
- package/dist/{filters-DJVAveHA.js → filters-DzgSJpMK.js} +693 -645
- package/dist/filters-DzgSJpMK.js.map +1 -0
- package/dist/{form-field-CwH9y77i.js → form-field-BriWAXyg.js} +14 -8
- package/dist/form-field-BriWAXyg.js.map +1 -0
- package/dist/{form-sizing-BdP_Mf5p.js → form-sizing-Bs2qFUeE.js} +4 -4
- package/dist/form-sizing-Bs2qFUeE.js.map +1 -0
- package/dist/forms-C0RGnJTe.js +216 -0
- package/dist/forms-C0RGnJTe.js.map +1 -0
- package/dist/{forms-numeric-B_jraJaQ.js → forms-numeric-DIhghX1Q.js} +295 -291
- package/dist/forms-numeric-DIhghX1Q.js.map +1 -0
- package/dist/{header-BSfIEWbI.js → header-DMMxG__r.js} +67 -63
- package/dist/header-DMMxG__r.js.map +1 -0
- package/dist/header.recipes-BigdJjw1.js +6 -0
- package/dist/header.recipes-BigdJjw1.js.map +1 -0
- package/dist/hooks/useFormErrors.d.ts +1 -1
- package/dist/{hooks-ClH9thn4.js → hooks-DB6CJ4MR.js} +26 -14
- package/dist/hooks-DB6CJ4MR.js.map +1 -0
- package/dist/{icon-badge-DYKIwvOV.js → icon-badge-DZF-YjQp.js} +4 -4
- package/dist/{icon-badge-DYKIwvOV.js.map → icon-badge-DZF-YjQp.js.map} +1 -1
- package/dist/index.js +4 -5
- package/dist/input-CvyDQlY7.js +289 -0
- package/dist/input-CvyDQlY7.js.map +1 -0
- package/dist/input-DMBwAZmr.js +21 -0
- package/dist/input-DMBwAZmr.js.map +1 -0
- package/dist/item-DjCcM18E.js +277 -0
- package/dist/item-DjCcM18E.js.map +1 -0
- package/dist/layout/app-shell.js +1 -1
- package/dist/layout/auth.js +1 -1
- package/dist/layout/containers.js +1 -1
- package/dist/layout/header.js +1 -1
- package/dist/layout/hooks.js +1 -1
- package/dist/layout/navigation.js +1 -1
- package/dist/layout/page.js +2 -2
- package/dist/layout/settings.js +1 -1
- package/dist/layout/sidebar.js +3 -3
- package/dist/layout/workspace.js +1 -1
- package/dist/layout.js +11 -11
- package/dist/lib/affordance-geometry.generated.d.ts +41 -35
- package/dist/lib/control-group-recipes.d.ts +5 -5
- package/dist/lib/field-affordance-recipes.d.ts +2 -2
- package/dist/lib/motion-recipes.d.ts +3 -0
- package/dist/lib/navigation-row-recipes.d.ts +7 -7
- package/dist/lib/overlay-anatomy-recipes.d.ts +3 -3
- package/dist/lib/portal-recipes.d.ts +3 -0
- package/dist/lib/token-merge.generated.d.ts +13 -10
- package/dist/lib/ui-provider/types.d.ts +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/map-demo-tile.svg +1 -1
- package/dist/{media-gallery-3zIVLMWH.js → media-gallery-C-vChPmE.js} +6 -6
- package/dist/{media-gallery-3zIVLMWH.js.map → media-gallery-C-vChPmE.js.map} +1 -1
- package/dist/{mentions-CGlEJ15c.js → mentions-0_4nWuI-.js} +10 -10
- package/dist/{mentions-CGlEJ15c.js.map → mentions-0_4nWuI-.js.map} +1 -1
- package/dist/{message-DZ_ruyUV.js → message-g8p7eVtN.js} +7 -7
- package/dist/message-g8p7eVtN.js.map +1 -0
- package/dist/metadata-list-C3MkU-Om.js +443 -0
- package/dist/metadata-list-C3MkU-Om.js.map +1 -0
- package/dist/{motion-recipes-ChBg4HBq.js → motion-recipes-DitYv6P2.js} +10 -2
- package/dist/motion-recipes-DitYv6P2.js.map +1 -0
- package/dist/navigation-C90-oQFq.js +255 -0
- package/dist/navigation-C90-oQFq.js.map +1 -0
- package/dist/{navigation-BzhHI3lo.js → navigation-DaRjthVk.js} +101 -82
- package/dist/navigation-DaRjthVk.js.map +1 -0
- package/dist/{navigation-row-recipes-BFLCk_Aq.js → navigation-row-recipes-BGVLvU_O.js} +9 -9
- package/dist/navigation-row-recipes-BGVLvU_O.js.map +1 -0
- package/dist/{number-DbV1Dija.js → number-CRU3kGuk.js} +5 -5
- package/dist/{number-DbV1Dija.js.map → number-CRU3kGuk.js.map} +1 -1
- package/dist/overflow-tab-bar-IVFVQFV6.js +172 -0
- package/dist/overflow-tab-bar-IVFVQFV6.js.map +1 -0
- package/dist/overlays-BSgCtM2v.js +1013 -0
- package/dist/overlays-BSgCtM2v.js.map +1 -0
- package/dist/page-DUUKDAjo.js +331 -0
- package/dist/page-DUUKDAjo.js.map +1 -0
- package/dist/page-header-commands-7P3sfE9u.js +193 -0
- package/dist/page-header-commands-7P3sfE9u.js.map +1 -0
- package/dist/{pagination-BL25hu2s.js → pagination-CgCjElfu.js} +7 -7
- package/dist/{pagination-BL25hu2s.js.map → pagination-CgCjElfu.js.map} +1 -1
- package/dist/popover-B85RenIl.js +201 -0
- package/dist/popover-B85RenIl.js.map +1 -0
- package/dist/{popover-menu-Cn9rUkdc.js → popover-menu-DlnQ2zlR.js} +7 -7
- package/dist/{popover-menu-Cn9rUkdc.js.map → popover-menu-DlnQ2zlR.js.map} +1 -1
- package/dist/{popover.recipes-D3j8R8Yp.js → popover.recipes-DdiEp0rd.js} +18 -11
- package/dist/popover.recipes-DdiEp0rd.js.map +1 -0
- package/dist/portal-recipes-BpsV_iRw.js +9 -0
- package/dist/portal-recipes-BpsV_iRw.js.map +1 -0
- package/dist/{primitives-BFK08r9M.js → primitives-ByWgGUgt.js} +6 -6
- package/dist/{primitives-BFK08r9M.js.map → primitives-ByWgGUgt.js.map} +1 -1
- package/dist/primitives.js +4 -4
- package/dist/{progress-B1VnCT0K.js → progress-BJhQP9WX.js} +9 -9
- package/dist/{progress-B1VnCT0K.js.map → progress-BJhQP9WX.js.map} +1 -1
- package/dist/{receipt-BVSOH3Xo.js → receipt-tOc7B3hw.js} +7 -7
- package/dist/receipt-tOc7B3hw.js.map +1 -0
- package/dist/{rich-text-GOUyKQ-s.js → rich-text-Dth5E882.js} +4 -4
- package/dist/{rich-text-GOUyKQ-s.js.map → rich-text-Dth5E882.js.map} +1 -1
- package/dist/{rich-text-editor-cRLC-eBK.js → rich-text-editor-C1fOjBTd.js} +429 -430
- package/dist/rich-text-editor-C1fOjBTd.js.map +1 -0
- package/dist/{rich-text-editor-toolbar-DfXg0-VC.js → rich-text-editor-toolbar-laT3tpBT.js} +33 -29
- package/dist/rich-text-editor-toolbar-laT3tpBT.js.map +1 -0
- package/dist/{scroll-area-DcI-M1UT.js → scroll-area-B0dB2cBi.js} +2 -2
- package/dist/{scroll-area-DcI-M1UT.js.map → scroll-area-B0dB2cBi.js.map} +1 -1
- package/dist/{separator-Bom7NsGO.js → separator-Bb17I4pu.js} +2 -2
- package/dist/{separator-Bom7NsGO.js.map → separator-Bb17I4pu.js.map} +1 -1
- package/dist/{separator-BDV-Y648.js → separator-Ct_WSWTJ.js} +3 -3
- package/dist/{separator-BDV-Y648.js.map → separator-Ct_WSWTJ.js.map} +1 -1
- package/dist/services/actions.js +1 -1
- package/dist/settings-Bar5wiSo.js +167 -0
- package/dist/settings-Bar5wiSo.js.map +1 -0
- package/dist/sheet-BGPERY55.js +250 -0
- package/dist/sheet-BGPERY55.js.map +1 -0
- package/dist/sidebar-D-JYMMbY.js +686 -0
- package/dist/sidebar-D-JYMMbY.js.map +1 -0
- package/dist/sidebar-f9gRWXIR.js +1001 -0
- package/dist/sidebar-f9gRWXIR.js.map +1 -0
- package/dist/skeleton-BapcJd9C.js +265 -0
- package/dist/skeleton-BapcJd9C.js.map +1 -0
- package/dist/smart-card-CW0hjetE.js +617 -0
- package/dist/smart-card-CW0hjetE.js.map +1 -0
- package/dist/{structure-BmisBK-3.js → structure-DuxKl954.js} +3 -3
- package/dist/{structure-BmisBK-3.js.map → structure-DuxKl954.js.map} +1 -1
- package/dist/{structure.recipes-EPz60cU8.js → structure.recipes-BB4-n58V.js} +2 -2
- package/dist/{structure.recipes-EPz60cU8.js.map → structure.recipes-BB4-n58V.js.map} +1 -1
- package/dist/style.css +1 -1
- package/dist/{table-DsfzoUia.js → table-B4TE7iOV.js} +3 -3
- package/dist/{table-DsfzoUia.js.map → table-B4TE7iOV.js.map} +1 -1
- package/dist/{table-BweFy_P9.js → table-CFOWY9pQ.js} +394 -388
- package/dist/table-CFOWY9pQ.js.map +1 -0
- package/dist/{tabs-CDocW5D1.js → tabs-e7ko9S2o.js} +11 -11
- package/dist/tabs-e7ko9S2o.js.map +1 -0
- package/dist/{text-inputs-f9Ia-aCi.js → text-inputs-B1culiSK.js} +107 -104
- package/dist/text-inputs-B1culiSK.js.map +1 -0
- package/dist/{text.styles-g4He6UIW.js → text.styles-B64My8RZ.js} +25 -10
- package/dist/text.styles-B64My8RZ.js.map +1 -0
- package/dist/theme.css +192 -149
- package/dist/{timelines-CwM_9IhD.js → timelines-D3Sl9ueq.js} +13 -12
- package/dist/timelines-D3Sl9ueq.js.map +1 -0
- package/dist/tooltip-8wz5S_Zr.js +113 -0
- package/dist/tooltip-8wz5S_Zr.js.map +1 -0
- package/dist/{tree-WnKQzwbq.js → tree-CzUJHnFs.js} +9 -9
- package/dist/{tree-WnKQzwbq.js.map → tree-CzUJHnFs.js.map} +1 -1
- package/dist/typography/rich-text.js +1 -1
- package/dist/typography-BQnine1k.js +117 -0
- package/dist/typography-BQnine1k.js.map +1 -0
- package/dist/typography.js +1 -1
- package/dist/ui-provider-C-74NVLc.js +178 -0
- package/dist/ui-provider-C-74NVLc.js.map +1 -0
- package/dist/ui-provider.js +2 -3
- package/dist/{upload-CXyxmQBP.js → upload-BOW6U2tI.js} +25 -24
- package/dist/upload-BOW6U2tI.js.map +1 -0
- package/dist/{upload-queue-Boscubn9.js → upload-queue-qUXn1ykN.js} +16 -14
- package/dist/upload-queue-qUXn1ykN.js.map +1 -0
- package/dist/{usage-card-BhO5eqkX.js → usage-card-BeSLY0i0.js} +145 -137
- package/dist/usage-card-BeSLY0i0.js.map +1 -0
- package/dist/{utils-BRhFDwuU.js → utils-B6kKb8jB.js} +303 -144
- package/dist/utils-B6kKb8jB.js.map +1 -0
- package/dist/value-inputs-skdVB2zh.js +980 -0
- package/dist/value-inputs-skdVB2zh.js.map +1 -0
- package/dist/{value-typography-BP8OG6h5.js → value-typography-uAmWUAL1.js} +2 -2
- package/dist/{value-typography-BP8OG6h5.js.map → value-typography-uAmWUAL1.js.map} +1 -1
- package/dist/workspace-DHeHv0qw.js +411 -0
- package/dist/workspace-DHeHv0qw.js.map +1 -0
- package/docs/consumer-skill/SKILL.md +15 -129
- package/docs/consumer-skill/agents/themelia-ui-designer.md +96 -0
- package/docs/consumer-skill/examples/app-layout.tsx +74 -13
- package/docs/consumer-skill/examples/authentication.tsx +77 -0
- package/docs/consumer-skill/examples/commerce-lines.tsx +271 -0
- package/docs/consumer-skill/examples/custom-composition.tsx +21 -0
- package/docs/consumer-skill/examples/detail.tsx +5 -4
- package/docs/consumer-skill/examples/edit.tsx +30 -38
- package/docs/consumer-skill/examples/editable-sections.tsx +38 -0
- package/docs/consumer-skill/examples/index-table.tsx +4 -4
- package/docs/consumer-skill/examples/manifest.json +88 -1
- package/docs/consumer-skill/examples/product-media.tsx +41 -0
- package/docs/consumer-skill/examples/product-parts.tsx +81 -0
- package/docs/consumer-skill/examples/rhf-bindings.tsx +86 -0
- package/docs/consumer-skill/examples/rhf-edit-page.tsx +141 -0
- package/docs/consumer-skill/examples/rhf-overlay-form.tsx +105 -0
- package/docs/consumer-skill/examples/rhf-transport.tsx +28 -0
- package/docs/consumer-skill/examples/token-domain-structure.tsx +68 -0
- package/docs/consumer-skill/references/agents-md.md +23 -11
- package/docs/consumer-skill/references/analytics.md +18 -1
- package/docs/consumer-skill/references/authentication.md +42 -0
- package/docs/consumer-skill/references/commerce-composition.md +97 -0
- package/docs/consumer-skill/references/component-contracts.json +325201 -0
- package/docs/consumer-skill/references/composition-mapping.md +106 -0
- package/docs/consumer-skill/references/composition.md +198 -46
- package/docs/consumer-skill/references/critique-rubric.md +98 -0
- package/docs/consumer-skill/references/custom-composition.md +261 -0
- package/docs/consumer-skill/references/data-reading.md +79 -0
- package/docs/consumer-skill/references/design-contract.md +43 -9
- package/docs/consumer-skill/references/discovery.md +109 -8
- package/docs/consumer-skill/references/domain-structures.md +124 -0
- package/docs/consumer-skill/references/installation.md +51 -36
- package/docs/consumer-skill/references/integration.md +41 -2
- package/docs/consumer-skill/references/product-composition.md +94 -0
- package/docs/consumer-skill/references/public-api.json +4390 -475
- package/docs/consumer-skill/references/review.md +47 -5
- package/docs/consumer-skill/references/rhf-workflows.md +93 -0
- package/docs/consumer-skill/references/screen-archetypes.md +34 -0
- package/docs/consumer-skill/references/screen-decisions.md +126 -0
- package/docs/consumer-skill/references/theming.md +25 -27
- package/docs/consumer-skill/references/tokens.json +20 -11
- package/docs/consumer-skill/references/view-craft.md +153 -0
- package/docs/consumer-skill/references/view-spec.md +73 -0
- package/docs/consumer-skill/references/workflows.md +9 -2
- package/docs/consumer-skill/scripts/composition-policy.mjs +39 -3
- package/docs/consumer-skill/scripts/composition-schema.d.mts +2 -0
- package/docs/consumer-skill/scripts/composition-schema.mjs +32 -0
- package/docs/consumer-skill/scripts/composition.mjs +254 -0
- package/docs/consumer-skill/scripts/discovery.mjs +96 -10
- package/docs/consumer-skill/scripts/inspect-component.mjs +11 -0
- package/docs/consumer-skill/scripts/inspect-tokens.mjs +11 -0
- package/docs/consumer-skill/scripts/inspection.mjs +193 -0
- package/docs/consumer-skill/scripts/map-composition.mjs +12 -0
- package/docs/consumer-skill/scripts/setup-skills.mjs +111 -0
- package/docs/consumer-skill/scripts/surface-policy.mjs +5 -5
- package/docs/consumer-skills/themelia-components/SKILL.md +31 -0
- package/docs/consumer-skills/themelia-composition/SKILL.md +34 -0
- package/docs/consumer-skills/themelia-design/SKILL.md +55 -0
- package/docs/consumer-skills/themelia-forms/SKILL.md +12 -0
- package/docs/consumer-skills/themelia-foundation/SKILL.md +15 -0
- package/docs/consumer-skills/themelia-tokenization/SKILL.md +42 -0
- package/package.json +4 -2
- package/templates/README.md +39 -0
- package/templates/admin-shell-template.tsx +382 -0
- package/templates/analytics-operations-template.tsx +376 -0
- package/templates/customers-template.tsx +509 -0
- package/templates/index.ts +29 -0
- package/templates/orders-template.tsx +1102 -0
- package/templates/products-template.tsx +834 -0
- package/templates/user-permissions-template.tsx +450 -0
- package/dist/accordion-Cjb6sx0G.js.map +0 -1
- package/dist/action-menu-CllU52Mm.js +0 -280
- package/dist/action-menu-CllU52Mm.js.map +0 -1
- package/dist/affordance-geometry.generated-tF36bREk.js.map +0 -1
- package/dist/ai-artifact-DvTwDVyL.js.map +0 -1
- package/dist/app-shell-C4FhvCqF.js +0 -236
- package/dist/app-shell-C4FhvCqF.js.map +0 -1
- package/dist/auth-BGua8ouh.js.map +0 -1
- package/dist/avatar-DAPlDvhJ.js.map +0 -1
- package/dist/base/cascader.js.map +0 -1
- package/dist/batch-action-bar-DnUJ21NQ.js.map +0 -1
- package/dist/button-DVzspB_G.js.map +0 -1
- package/dist/buttons-DPZekJFu.js.map +0 -1
- package/dist/cards-BaBZR-AU.js +0 -68
- package/dist/cards-BaBZR-AU.js.map +0 -1
- package/dist/chart-ZOYyK2xG.js +0 -155
- package/dist/chart-ZOYyK2xG.js.map +0 -1
- package/dist/checkbox-AWIhNhmo.js.map +0 -1
- package/dist/choice-inputs-CNHvARCJ.js +0 -770
- package/dist/choice-inputs-CNHvARCJ.js.map +0 -1
- package/dist/code-block-DeneBJR_.js.map +0 -1
- package/dist/combobox-DaHESuiL.js +0 -355
- package/dist/combobox-DaHESuiL.js.map +0 -1
- package/dist/combobox-Dxo2fAYQ.js.map +0 -1
- package/dist/command-C25VMRII.js.map +0 -1
- package/dist/comments-DD0DUPwJ.js.map +0 -1
- package/dist/containers-Mu1LMGV1.js +0 -103
- package/dist/containers-Mu1LMGV1.js.map +0 -1
- package/dist/control-group-recipes-DBXmfh2E.js.map +0 -1
- package/dist/data-display-DT_khPl_.js.map +0 -1
- package/dist/date-picker-B2K3ucWn.js.map +0 -1
- package/dist/date-pickers-Ds8gLaoP.js +0 -175
- package/dist/date-pickers-Ds8gLaoP.js.map +0 -1
- package/dist/dropdown-menu-Cl4X7L5S.js +0 -193
- package/dist/dropdown-menu-Cl4X7L5S.js.map +0 -1
- package/dist/feedback-BYIhtitP.js.map +0 -1
- package/dist/field-affordance-recipes-DZhnIL_Y.js.map +0 -1
- package/dist/filters-DJVAveHA.js.map +0 -1
- package/dist/form-field-CwH9y77i.js.map +0 -1
- package/dist/form-sizing-BdP_Mf5p.js.map +0 -1
- package/dist/forms-B9G_xwzi.js +0 -194
- package/dist/forms-B9G_xwzi.js.map +0 -1
- package/dist/forms-numeric-B_jraJaQ.js.map +0 -1
- package/dist/header-BSfIEWbI.js.map +0 -1
- package/dist/hooks-ClH9thn4.js.map +0 -1
- package/dist/input-CZw8iU99.js +0 -21
- package/dist/input-CZw8iU99.js.map +0 -1
- package/dist/input-DQdEd0YD.js +0 -263
- package/dist/input-DQdEd0YD.js.map +0 -1
- package/dist/item-K5_ghvze.js +0 -275
- package/dist/item-K5_ghvze.js.map +0 -1
- package/dist/message-DZ_ruyUV.js.map +0 -1
- package/dist/metadata-list-8N16ZMW_.js +0 -405
- package/dist/metadata-list-8N16ZMW_.js.map +0 -1
- package/dist/motion-recipes-ChBg4HBq.js.map +0 -1
- package/dist/navigation-Bnotn4AQ.js +0 -145
- package/dist/navigation-Bnotn4AQ.js.map +0 -1
- package/dist/navigation-BzhHI3lo.js.map +0 -1
- package/dist/navigation-row-recipes-BFLCk_Aq.js.map +0 -1
- package/dist/overflow-tab-bar-CWY7Nogn.js +0 -282
- package/dist/overflow-tab-bar-CWY7Nogn.js.map +0 -1
- package/dist/overlays-tMkF-VAN.js +0 -1003
- package/dist/overlays-tMkF-VAN.js.map +0 -1
- package/dist/page-CL9YeH_V.js +0 -224
- package/dist/page-CL9YeH_V.js.map +0 -1
- package/dist/page-header-commands-DOb6WsfD.js +0 -126
- package/dist/page-header-commands-DOb6WsfD.js.map +0 -1
- package/dist/popover-gnTOhbFM.js +0 -198
- package/dist/popover-gnTOhbFM.js.map +0 -1
- package/dist/popover.recipes-D3j8R8Yp.js.map +0 -1
- package/dist/receipt-BVSOH3Xo.js.map +0 -1
- package/dist/rich-text-editor-cRLC-eBK.js.map +0 -1
- package/dist/rich-text-editor-toolbar-DfXg0-VC.js.map +0 -1
- package/dist/settings-ybnjVwbd.js +0 -171
- package/dist/settings-ybnjVwbd.js.map +0 -1
- package/dist/sheet-ijlsjq-n.js +0 -213
- package/dist/sheet-ijlsjq-n.js.map +0 -1
- package/dist/sidebar-Db6gkh3U.js +0 -619
- package/dist/sidebar-Db6gkh3U.js.map +0 -1
- package/dist/sidebar-DkTO4VTO.js +0 -512
- package/dist/sidebar-DkTO4VTO.js.map +0 -1
- package/dist/skeleton-Byjnt7qr.js +0 -264
- package/dist/skeleton-Byjnt7qr.js.map +0 -1
- package/dist/smart-card-DiC5aac3.js +0 -604
- package/dist/smart-card-DiC5aac3.js.map +0 -1
- package/dist/table-BweFy_P9.js.map +0 -1
- package/dist/tabs-CDocW5D1.js.map +0 -1
- package/dist/text-inputs-f9Ia-aCi.js.map +0 -1
- package/dist/text.styles-g4He6UIW.js.map +0 -1
- package/dist/theme-scope-BJ6eE2xt.js +0 -50
- package/dist/theme-scope-BJ6eE2xt.js.map +0 -1
- package/dist/timelines-CwM_9IhD.js.map +0 -1
- package/dist/tooltip-yiRJUFjp.js +0 -111
- package/dist/tooltip-yiRJUFjp.js.map +0 -1
- package/dist/typography-UTnveOEy.js +0 -117
- package/dist/typography-UTnveOEy.js.map +0 -1
- package/dist/ui-provider-C_U0Xma4.js +0 -136
- package/dist/ui-provider-C_U0Xma4.js.map +0 -1
- package/dist/upload-CXyxmQBP.js.map +0 -1
- package/dist/upload-queue-Boscubn9.js.map +0 -1
- package/dist/usage-card-BhO5eqkX.js.map +0 -1
- package/dist/use-mobile-vaLKwgyf.js +0 -15
- package/dist/use-mobile-vaLKwgyf.js.map +0 -1
- package/dist/utils-BRhFDwuU.js.map +0 -1
- package/dist/value-inputs-TaX4OjNk.js +0 -963
- package/dist/value-inputs-TaX4OjNk.js.map +0 -1
- package/dist/workspace-B-OEm3Kf.js +0 -408
- package/dist/workspace-B-OEm3Kf.js.map +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
import { useForm } from 'react-hook-form';
|
|
2
3
|
import { Button } from 'themelia-react-ui/base/buttons';
|
|
3
4
|
import { Card } from 'themelia-react-ui/base/cards';
|
|
4
|
-
import {
|
|
5
|
+
import { ControlledFormField, DirtyStateBanner, ErrorSummary, FormActionsBar, SubmitStateButton } from 'themelia-react-ui/base/forms';
|
|
5
6
|
import { Stack } from 'themelia-react-ui/base/structure';
|
|
6
7
|
import { Input } from 'themelia-react-ui/base/text-inputs';
|
|
7
8
|
import { Text } from 'themelia-react-ui/typography';
|
|
@@ -9,52 +10,43 @@ import { Text } from 'themelia-react-ui/typography';
|
|
|
9
10
|
export interface AccountEditValue { id: string; name: string; }
|
|
10
11
|
export interface AccountEditProps {
|
|
11
12
|
initial: AccountEditValue;
|
|
12
|
-
// Resolve with the authoritative saved value; reject if persistence failed.
|
|
13
13
|
onSave: (value: AccountEditValue) => Promise<AccountEditValue>;
|
|
14
14
|
onCancel: () => void;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
/** Small embedded subject; full routes start from rhf-edit-page.tsx. */
|
|
18
18
|
export function AccountEdit({ initial, onSave, onCancel }: AccountEditProps) {
|
|
19
|
-
const
|
|
20
|
-
const input = useRef<HTMLInputElement>(null);
|
|
21
|
-
const inFlight = useRef(false);
|
|
22
|
-
const [name, setName] = useState(initial.name);
|
|
23
|
-
const [validation, setValidation] = useState<string>();
|
|
24
|
-
const [error, setError] = useState<string>();
|
|
25
|
-
const [pending, setPending] = useState(false);
|
|
19
|
+
const lock = useRef(false);
|
|
26
20
|
const [saved, setSaved] = useState(false);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
setValidation(undefined); inFlight.current = true; setPending(true);
|
|
21
|
+
const form = useForm<AccountEditValue>({ defaultValues: initial });
|
|
22
|
+
const { isDirty, isSubmitting, errors } = form.formState;
|
|
23
|
+
const submit = form.handleSubmit(async values => {
|
|
24
|
+
if (lock.current) return;
|
|
25
|
+
lock.current = true;
|
|
26
|
+
setSaved(false);
|
|
27
|
+
form.clearErrors('root.server');
|
|
36
28
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
form.reset(await onSave({ ...values, name: values.name.trim() }));
|
|
30
|
+
setSaved(true);
|
|
39
31
|
} catch {
|
|
40
|
-
setError('Saving failed. Your draft is kept; retry Save account.');
|
|
41
|
-
} finally {
|
|
42
|
-
}
|
|
32
|
+
form.setError('root.server', { type: 'transport', message: 'Saving failed. Your draft is kept; retry Save account.' });
|
|
33
|
+
} finally { lock.current = false; }
|
|
34
|
+
});
|
|
43
35
|
return <Card title="Edit account" surface="card">
|
|
44
|
-
<form onSubmit={submit} noValidate aria-busy={
|
|
36
|
+
<form onSubmit={submit} noValidate aria-busy={isSubmitting}>
|
|
45
37
|
<Stack gap="lg">
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
{saved && <Text role="status">Account saved.</Text>}
|
|
54
|
-
<
|
|
55
|
-
<Button
|
|
56
|
-
<
|
|
57
|
-
</
|
|
38
|
+
{isDirty && <DirtyStateBanner title="Unsaved changes" />}
|
|
39
|
+
{errors.root?.server?.message && <ErrorSummary title="Account could not be saved" errors={[errors.root.server.message]} />}
|
|
40
|
+
<ControlledFormField control={form.control} name="name" label="Account name" required
|
|
41
|
+
hint="At least two characters." rules={{ validate: value => value.trim().length >= 2 || 'Enter at least two characters.' }}>
|
|
42
|
+
{(field, _error, invalid) => <Input ref={field.ref} name={field.name} value={field.value}
|
|
43
|
+
onValueChange={value => field.onChange(value)} onBlur={field.onBlur} invalid={invalid} disabled={isSubmitting} />}
|
|
44
|
+
</ControlledFormField>
|
|
45
|
+
{saved && !isDirty && <Text role="status">Account saved.</Text>}
|
|
46
|
+
<FormActionsBar>
|
|
47
|
+
<Button type="button" disabled={isSubmitting} onClick={onCancel}>Cancel edit</Button>
|
|
48
|
+
<SubmitStateButton dirty={isDirty} disableWhenPristine submitting={isSubmitting}>Save account</SubmitStateButton>
|
|
49
|
+
</FormActionsBar>
|
|
58
50
|
</Stack>
|
|
59
51
|
</form>
|
|
60
52
|
</Card>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from 'themelia-react-ui/base/accordion';
|
|
3
|
+
import { Badge } from 'themelia-react-ui/base/badge';
|
|
4
|
+
import { Stack } from 'themelia-react-ui/base/structure';
|
|
5
|
+
import { Heading, Text } from 'themelia-react-ui/typography';
|
|
6
|
+
|
|
7
|
+
export interface EditableSection {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
content: ReactNode;
|
|
11
|
+
/** App-localized validation status, e.g. "2 errors"; omit when valid. */
|
|
12
|
+
validationMessage?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Place inside the app's one form. Closing a panel retains the same field DOM. */
|
|
16
|
+
export function EditableSections({title, sections, openSections, onOpenSectionsChange}: {
|
|
17
|
+
title: string;
|
|
18
|
+
sections: EditableSection[];
|
|
19
|
+
openSections: string[];
|
|
20
|
+
onOpenSectionsChange: (values: string[]) => void;
|
|
21
|
+
}) {
|
|
22
|
+
return <Stack gap="xl">
|
|
23
|
+
<Heading tag="h2" size="base">{title}</Heading>
|
|
24
|
+
<Accordion multiple value={openSections} onValueChange={onOpenSectionsChange} aria-label={title}>
|
|
25
|
+
{sections.map(section => <AccordionItem key={section.id} value={section.id}>
|
|
26
|
+
<AccordionTrigger>
|
|
27
|
+
<Stack render={<span />} direction="horizontal" gap="md" className="min-w-0">
|
|
28
|
+
<Text tag="span" size="inherit" type="inherit" weight="medium">{section.title}</Text>
|
|
29
|
+
{section.validationMessage && <>{' '}<Badge tone="destructive">{section.validationMessage}</Badge></>}
|
|
30
|
+
</Stack>
|
|
31
|
+
</AccordionTrigger>
|
|
32
|
+
<AccordionContent keepMounted>
|
|
33
|
+
<Stack gap="xl">{section.content}</Stack>
|
|
34
|
+
</AccordionContent>
|
|
35
|
+
</AccordionItem>)}
|
|
36
|
+
</Accordion>
|
|
37
|
+
</Stack>;
|
|
38
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Button } from 'themelia-react-ui/base/buttons';
|
|
2
|
-
import {
|
|
2
|
+
import { Page } from 'themelia-react-ui/layout/page';
|
|
3
3
|
import { Stack } from 'themelia-react-ui/base/structure';
|
|
4
4
|
import { DataTable, type DataTableProps } from 'themelia-react-ui/features/table';
|
|
5
5
|
import { Email, Name } from 'themelia-react-ui/primitives';
|
|
@@ -23,14 +23,14 @@ export function AccountIndex({ state, onRetry, hrefFor, onNavigate }: AccountInd
|
|
|
23
23
|
{ accessorKey: 'name', header: 'Name', cell: ({ row }) => <Name value={row.original.name} /> },
|
|
24
24
|
{ accessorKey: 'email', header: 'Email', cell: ({ row }) => <Email value={row.original.email} /> },
|
|
25
25
|
];
|
|
26
|
-
return <
|
|
26
|
+
return <Page header={{ title: 'Accounts' }}>
|
|
27
27
|
{state.status === 'loading' ? <Text role="status">Loading accounts…</Text> :
|
|
28
28
|
state.status === 'error' ? <Stack gap="md">
|
|
29
29
|
<Text role="alert" type="error">{state.message}</Text>
|
|
30
30
|
<Button onClick={onRetry}>Retry loading</Button>
|
|
31
|
-
</Stack> : <DataTable columns={columns} data={state.records}
|
|
31
|
+
</Stack> : <DataTable columns={columns} data={state.records}
|
|
32
32
|
getRowId={account => account.id} enablePagination={false} showFullscreenToggle={false}
|
|
33
33
|
emptyStateMessage="No accounts yet." emptyStateAction={<Button onClick={onRetry}>Refresh accounts</Button>}
|
|
34
34
|
rowActions={[{ id: 'open', label: 'Open account', onClick: account => onNavigate(hrefFor(account)) }]} />}
|
|
35
|
-
</
|
|
35
|
+
</Page>;
|
|
36
36
|
}
|
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"examples": [
|
|
4
|
+
{
|
|
5
|
+
"file": "product-parts.tsx",
|
|
6
|
+
"exports": ["ProductOptionDefinitions", "ProductVariantManagement", "VariantComparison", "VariantInspectionAndEditor"],
|
|
7
|
+
"phase": "optional",
|
|
8
|
+
"optionalPeers": ["@tanstack/react-table", "react-hook-form"]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"file": "product-media.tsx",
|
|
12
|
+
"exports": ["LocalProductImages", "AttachedProductImages", "MediaOnlyProductOverview"],
|
|
13
|
+
"phase": "optional",
|
|
14
|
+
"optionalPeers": ["@dnd-kit/core", "@dnd-kit/sortable", "@dnd-kit/utilities", "@tanstack/react-table", "react-hook-form"]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"file": "token-domain-structure.tsx",
|
|
18
|
+
"exports": ["AllocationByLocation", "AllocationSubjectExample"],
|
|
19
|
+
"phase": "basic",
|
|
20
|
+
"optionalPeers": []
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"file": "commerce-lines.tsx",
|
|
24
|
+
"exports": ["SoldProductWithSummary", "CustomServiceInvoice", "OrderFulfillmentDetail", "ReceivableInvoiceDetail"],
|
|
25
|
+
"phase": "optional",
|
|
26
|
+
"optionalPeers": ["react-day-picker", "react-hook-form", "sonner"]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"file": "editable-sections.tsx",
|
|
30
|
+
"exports": ["EditableSections"],
|
|
31
|
+
"phase": "basic",
|
|
32
|
+
"optionalPeers": []
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"file": "custom-composition.tsx",
|
|
36
|
+
"exports": ["AssessmentComparison"],
|
|
37
|
+
"phase": "basic",
|
|
38
|
+
"optionalPeers": []
|
|
39
|
+
},
|
|
4
40
|
{
|
|
5
41
|
"file": "website.tsx",
|
|
6
42
|
"exports": [
|
|
@@ -105,7 +141,8 @@
|
|
|
105
141
|
{
|
|
106
142
|
"file": "app-layout.tsx",
|
|
107
143
|
"exports": [
|
|
108
|
-
"WorkspaceLayout"
|
|
144
|
+
"WorkspaceLayout",
|
|
145
|
+
"WorkspaceExamplePage"
|
|
109
146
|
],
|
|
110
147
|
"phase": "basic",
|
|
111
148
|
"optionalPeers": []
|
|
@@ -137,6 +174,56 @@
|
|
|
137
174
|
],
|
|
138
175
|
"phase": "basic",
|
|
139
176
|
"optionalPeers": []
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"file": "authentication.tsx",
|
|
180
|
+
"exports": [
|
|
181
|
+
"PasswordSignIn"
|
|
182
|
+
],
|
|
183
|
+
"phase": "optional",
|
|
184
|
+
"optionalPeers": [
|
|
185
|
+
"react-hook-form"
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"file": "rhf-edit-page.tsx",
|
|
190
|
+
"exports": [
|
|
191
|
+
"RHFEditPage"
|
|
192
|
+
],
|
|
193
|
+
"phase": "optional",
|
|
194
|
+
"optionalPeers": [
|
|
195
|
+
"react-hook-form"
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"file": "rhf-overlay-form.tsx",
|
|
200
|
+
"exports": [
|
|
201
|
+
"RHFOverlayForm",
|
|
202
|
+
"RHFDialogForm"
|
|
203
|
+
],
|
|
204
|
+
"phase": "optional",
|
|
205
|
+
"optionalPeers": [
|
|
206
|
+
"react-hook-form"
|
|
207
|
+
]
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"file": "rhf-bindings.tsx",
|
|
211
|
+
"exports": [
|
|
212
|
+
"TypedControlBindings"
|
|
213
|
+
],
|
|
214
|
+
"phase": "optional",
|
|
215
|
+
"optionalPeers": [
|
|
216
|
+
"react-day-picker",
|
|
217
|
+
"react-hook-form"
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"file": "rhf-transport.tsx",
|
|
222
|
+
"exports": [
|
|
223
|
+
"awaitCallbackSave"
|
|
224
|
+
],
|
|
225
|
+
"phase": "basic",
|
|
226
|
+
"optionalPeers": []
|
|
140
227
|
}
|
|
141
228
|
]
|
|
142
229
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { FormField } from 'themelia-react-ui/base/forms';
|
|
2
|
+
import { MediaGallery } from 'themelia-react-ui/base/media-gallery';
|
|
3
|
+
import { MediaResourceGallery, type MediaLibraryAccessors } from 'themelia-react-ui/features/media-library';
|
|
4
|
+
import { ProductOverview } from 'themelia-react-ui/features/products';
|
|
5
|
+
|
|
6
|
+
/** New local files remain File[]; the caller owns transfer and persistence. */
|
|
7
|
+
export function LocalProductImages({ files, onFilesChange }: { files: File[]; onFilesChange: (files: File[]) => void }) {
|
|
8
|
+
return <FormField label="New product images" hint="The first image is the cover. Drag or use the keyboard to change order.">
|
|
9
|
+
<MediaGallery value={files} onValueChange={onFilesChange} accept="image/*" maxFiles={6} />
|
|
10
|
+
</FormField>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ProductAsset { key: string; filename: string; url: string; altText: string; }
|
|
14
|
+
const accessors: MediaLibraryAccessors<ProductAsset> = {
|
|
15
|
+
getId: item => item.key, getName: item => item.filename, getType: () => 'image',
|
|
16
|
+
getSrc: item => item.url, getAlt: item => item.altText,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Existing records stay records; callbacks propose resource attachment changes. */
|
|
20
|
+
export function AttachedProductImages({ assets, primaryId, onAssetsChange, onPrimaryChange, onAdd }: {
|
|
21
|
+
assets: ProductAsset[];
|
|
22
|
+
primaryId?: string;
|
|
23
|
+
onAssetsChange: (assets: ProductAsset[]) => void;
|
|
24
|
+
onPrimaryChange: (id: string | undefined) => void;
|
|
25
|
+
onAdd?: () => void;
|
|
26
|
+
}) {
|
|
27
|
+
return <MediaResourceGallery items={assets} accessors={accessors} title="Product media" layout="grid"
|
|
28
|
+
primaryId={primaryId} onPrimaryChange={id => onPrimaryChange(id)} onAdd={onAdd}
|
|
29
|
+
onReorder={(_ids, next) => onAssetsChange(next)} onRemove={id => {
|
|
30
|
+
const next = assets.filter(asset => asset.key !== id);
|
|
31
|
+
onAssetsChange(next);
|
|
32
|
+
if (id === (primaryId ?? assets[0]?.key)) onPrimaryChange(next[0]?.key);
|
|
33
|
+
}} />;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** ProductOverview owns the subject; media enters through details.media, not a top-level media prop. */
|
|
37
|
+
export function MediaOnlyProductOverview({ assets }: { assets: ProductAsset[] }) {
|
|
38
|
+
return <ProductOverview details={{ title: 'Product media', description: false,
|
|
39
|
+
media: <MediaResourceGallery items={assets} accessors={accessors} readOnly layout="field" />,
|
|
40
|
+
}} />;
|
|
41
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useState, type ReactNode } from 'react';
|
|
2
|
+
import { Alert, AlertDescription } from 'themelia-react-ui/base/feedback';
|
|
3
|
+
import { Stack } from 'themelia-react-ui/base/structure';
|
|
4
|
+
import {
|
|
5
|
+
ProductOptionsMatrix, ProductVariantDetails, ProductVariantEditor,
|
|
6
|
+
ProductVariantsBulkTable, ProductVariantsManager,
|
|
7
|
+
type ProductOptionEditDraft, type ProductOptionGroup,
|
|
8
|
+
type ProductVariantEditorValues, type ProductVariantRow,
|
|
9
|
+
} from 'themelia-react-ui/features/products';
|
|
10
|
+
import { Money } from 'themelia-react-ui/primitives';
|
|
11
|
+
|
|
12
|
+
const colors: ProductOptionGroup[] = [{
|
|
13
|
+
id: 'color', name: 'Color', values: [{ id: 'olive', label: 'Olive' }, { id: 'natural', label: 'Natural' }],
|
|
14
|
+
}];
|
|
15
|
+
const rows: ProductVariantRow[] = [
|
|
16
|
+
{ id: 'weekender-olive', name: 'Olive', sku: 'WEEK-OLIVE', price: '€68.00', inventory: '7 available', status: 'Active', optionValueIds: { color: 'olive' } },
|
|
17
|
+
{ id: 'weekender-natural', name: 'Natural', sku: 'WEEK-NATURAL', price: '€68.00', inventory: '12 available', status: 'Active', optionValueIds: { color: 'natural' } },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/** Local definition-editing example. Variant reconciliation is a separate app rule. */
|
|
21
|
+
export function ProductOptionDefinitions() {
|
|
22
|
+
const [groups, setGroups] = useState(colors);
|
|
23
|
+
const [editingId, setEditingId] = useState<string | null>(null);
|
|
24
|
+
return <ProductOptionsMatrix optionGroups={groups} editingOptionId={editingId}
|
|
25
|
+
onEditingOptionIdChange={setEditingId} showVariantToggle={false}
|
|
26
|
+
onSaveEditingOption={(option, draft) => {
|
|
27
|
+
setGroups(current => current.map(group => group.id === option.id ? { ...group, name: draft.name, values: draft.values } : group));
|
|
28
|
+
}} />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Callbacks apply app-owned accepted definitions/rows; the manager does not generate business data. */
|
|
32
|
+
export function ProductVariantManagement({ optionGroups, variants, onSaveOption, onInspectVariant }: {
|
|
33
|
+
optionGroups: ProductOptionGroup[];
|
|
34
|
+
variants: ProductVariantRow[];
|
|
35
|
+
onSaveOption: (option: ProductOptionGroup, draft: ProductOptionEditDraft) => void | Promise<void>;
|
|
36
|
+
onInspectVariant: (variant: ProductVariantRow) => void;
|
|
37
|
+
}) {
|
|
38
|
+
const [editingId, setEditingId] = useState<string | null>(null);
|
|
39
|
+
return <ProductVariantsManager optionGroups={optionGroups} variants={variants}
|
|
40
|
+
editingOptionId={editingId} onEditingOptionIdChange={setEditingId}
|
|
41
|
+
onSaveEditingOption={onSaveOption}
|
|
42
|
+
onSelectVariant={onInspectVariant} groupByOptionId={null}
|
|
43
|
+
visibleColumns={['variant', 'sku', 'price', 'inventory', 'status']} selectionMode="none" cellDisplay="text" />;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A comparison can use the table independently from option-definition editing. */
|
|
47
|
+
export function VariantComparison({ onInspectVariant }: { onInspectVariant: (variant: ProductVariantRow) => void }) {
|
|
48
|
+
return <ProductVariantsBulkTable variants={rows} optionGroups={colors} groupByOptionId={null}
|
|
49
|
+
visibleColumns={['variant', 'sku', 'price', 'inventory', 'status']} selectionMode="none"
|
|
50
|
+
cellDisplay="text" onSelectVariant={onInspectVariant} />;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The caller validates/persists and returns accepted fields; a failed save keeps the editor draft. */
|
|
54
|
+
export function VariantInspectionAndEditor({ id, initialValue, media, detailFooter, onSave }: {
|
|
55
|
+
id: string;
|
|
56
|
+
initialValue: ProductVariantEditorValues;
|
|
57
|
+
media?: ReactNode;
|
|
58
|
+
detailFooter?: ReactNode;
|
|
59
|
+
onSave: (draft: ProductVariantEditorValues) => Promise<ProductVariantEditorValues>;
|
|
60
|
+
}) {
|
|
61
|
+
const [accepted, setAccepted] = useState(initialValue);
|
|
62
|
+
const [draft, setDraft] = useState(initialValue);
|
|
63
|
+
const [editing, setEditing] = useState(false);
|
|
64
|
+
const [error, setError] = useState('');
|
|
65
|
+
return <Stack gap="2xl">
|
|
66
|
+
<ProductVariantDetails variant={{ id, name: accepted.name || 'Variant', sku: accepted.sku,
|
|
67
|
+
price: accepted.price ? <Money amount={Number(accepted.price)} currency="EUR" /> : undefined,
|
|
68
|
+
inventory: accepted.inventory, status: accepted.status,
|
|
69
|
+
options: Object.values(accepted.options ?? {}).filter((value): value is string => value !== undefined),
|
|
70
|
+
}} media={media} footerSlot={detailFooter} onEditVariant={() => {
|
|
71
|
+
setDraft(accepted); setError(''); setEditing(true);
|
|
72
|
+
}} />
|
|
73
|
+
{editing && <Stack gap="xl">
|
|
74
|
+
{error && <Alert tone="destructive"><AlertDescription>{error}</AlertDescription></Alert>}
|
|
75
|
+
<ProductVariantEditor value={draft} onValueChange={setDraft}
|
|
76
|
+
onSubmit={async value => { const saved = await onSave(value); setAccepted(saved); setEditing(false); }}
|
|
77
|
+
onError={() => setError('Variant could not be saved. Your draft is kept; retry Save variant.')}
|
|
78
|
+
onCancel={() => setEditing(false)} />
|
|
79
|
+
</Stack>}
|
|
80
|
+
</Stack>;
|
|
81
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useId } from 'react';
|
|
2
|
+
import { useForm } from 'react-hook-form';
|
|
3
|
+
import { ControlledFormField } from 'themelia-react-ui/base/forms';
|
|
4
|
+
import { Input, Textarea } from 'themelia-react-ui/base/text-inputs';
|
|
5
|
+
import { Checkbox, Select, Switch, SwitchCard, ToggleField } from 'themelia-react-ui/base/choice-inputs';
|
|
6
|
+
import { DatePicker } from 'themelia-react-ui/base/date-pickers';
|
|
7
|
+
import { ColorInput, DateTimeInput, PhoneInput, SliderField, TagsInput } from 'themelia-react-ui/base/value-inputs';
|
|
8
|
+
import { Stack } from 'themelia-react-ui/base/structure';
|
|
9
|
+
import { Text } from 'themelia-react-ui/typography';
|
|
10
|
+
|
|
11
|
+
export interface BindingValues {
|
|
12
|
+
title: string; description: string; category: string;
|
|
13
|
+
enabled: boolean; accepted: boolean; notifications: boolean; automation: boolean;
|
|
14
|
+
dueDate: Date | null; appointment: string; phone: string; tags: string[];
|
|
15
|
+
threshold: number; color: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Each binding follows the public control's actual value contract. */
|
|
19
|
+
export function TypedControlBindings() {
|
|
20
|
+
const id = useId();
|
|
21
|
+
const { control } = useForm<BindingValues>({ defaultValues: {
|
|
22
|
+
title: '', description: '', category: 'standard', enabled: false, accepted: false,
|
|
23
|
+
notifications: true, automation: false, dueDate: null, appointment: '', phone: '',
|
|
24
|
+
tags: [], threshold: 50, color: '#335577',
|
|
25
|
+
} });
|
|
26
|
+
return <Stack gap="xl">
|
|
27
|
+
<ControlledFormField control={control} name="title" label="Title">
|
|
28
|
+
{(field, _error, invalid) => <Input ref={field.ref} name={field.name}
|
|
29
|
+
value={field.value} onValueChange={value => field.onChange(value)} onBlur={field.onBlur} invalid={invalid} />}
|
|
30
|
+
</ControlledFormField>
|
|
31
|
+
<ControlledFormField control={control} name="description" label="Description">
|
|
32
|
+
{(field, _error, invalid) => <Textarea ref={field.ref} name={field.name}
|
|
33
|
+
value={field.value} onValueChange={value => field.onChange(value)} onBlur={field.onBlur} invalid={invalid} />}
|
|
34
|
+
</ControlledFormField>
|
|
35
|
+
<ControlledFormField control={control} name="category" label="Category">
|
|
36
|
+
{(field, _error, invalid) => <Select ref={field.ref} name={field.name}
|
|
37
|
+
value={field.value} onValueChange={value => field.onChange(value ?? '')} onBlur={field.onBlur}
|
|
38
|
+
invalid={invalid} options={[{ value: 'standard', label: 'Standard' }, { value: 'priority', label: 'Priority' }]} />}
|
|
39
|
+
</ControlledFormField>
|
|
40
|
+
<ControlledFormField control={control} name="enabled" label="Enabled">
|
|
41
|
+
{(field, _error, invalid) => <Switch ref={field.ref} name={field.name}
|
|
42
|
+
checked={field.value} onValueChange={checked => field.onChange(checked)} onBlur={field.onBlur} invalid={invalid} />}
|
|
43
|
+
</ControlledFormField>
|
|
44
|
+
<ControlledFormField control={control} name="accepted" label="Accept terms" rules={{ validate: value => value || 'Accept the terms.' }}>
|
|
45
|
+
{(field, _error, invalid) => <Checkbox ref={field.ref} name={field.name}
|
|
46
|
+
checked={field.value} onValueChange={checked => field.onChange(checked)} onBlur={field.onBlur} invalid={invalid} />}
|
|
47
|
+
</ControlledFormField>
|
|
48
|
+
<ControlledFormField control={control} name="notifications">
|
|
49
|
+
{(field, _error, invalid) => <ToggleField label="Email notifications" name={field.name}
|
|
50
|
+
value={field.value} controlRef={field.ref} onBlur={field.onBlur} onValueChange={checked => field.onChange(checked)}
|
|
51
|
+
invalid={invalid} />}
|
|
52
|
+
</ControlledFormField>
|
|
53
|
+
<ControlledFormField control={control} name="automation">
|
|
54
|
+
{(field, _error, invalid) => <SwitchCard label="Automatic processing" name={field.name}
|
|
55
|
+
value={field.value} controlRef={field.ref} onBlur={field.onBlur} onValueChange={checked => field.onChange(checked)}
|
|
56
|
+
invalid={invalid} />}
|
|
57
|
+
</ControlledFormField>
|
|
58
|
+
<ControlledFormField control={control} name="dueDate" label="Due date">
|
|
59
|
+
{(field, _error, invalid) => <DatePicker mode="single" value={field.value ?? undefined}
|
|
60
|
+
triggerRef={field.ref} onBlur={field.onBlur} onValueChange={output => field.onChange(output.date ?? null)} invalid={invalid} />}
|
|
61
|
+
</ControlledFormField>
|
|
62
|
+
<ControlledFormField control={control} name="appointment" label="Appointment">
|
|
63
|
+
{(field, _error, invalid) => <DateTimeInput value={field.value || undefined}
|
|
64
|
+
triggerRef={field.ref} onBlur={field.onBlur} onValueChange={value => field.onChange(value ?? '')} invalid={invalid} />}
|
|
65
|
+
</ControlledFormField>
|
|
66
|
+
<ControlledFormField control={control} name="phone" label="Phone">
|
|
67
|
+
{(field, _error, invalid) => <PhoneInput ref={field.ref} name={field.name}
|
|
68
|
+
value={field.value} onChange={event => field.onChange(event.target.value)} onBlur={field.onBlur} invalid={invalid} />}
|
|
69
|
+
</ControlledFormField>
|
|
70
|
+
<ControlledFormField control={control} name="tags" label="Tags">
|
|
71
|
+
{(field, _error, invalid) => <TagsInput<string> name={field.name} value={field.value}
|
|
72
|
+
inputRef={field.ref} onBlur={field.onBlur} onValueChange={value => field.onChange(value)} aria-invalid={invalid} />}
|
|
73
|
+
</ControlledFormField>
|
|
74
|
+
<ControlledFormField control={control} name="threshold" hint="Choose a value between 0 and 100." supportingTextId={`${id}-threshold-help`}>
|
|
75
|
+
{(field, _error, invalid) => <Stack gap="xs">
|
|
76
|
+
<Text tag="span" id={`${id}-threshold-label`} weight="medium">Threshold</Text>
|
|
77
|
+
<SliderField name={field.name} value={field.value} aria-labelledby={`${id}-threshold-label`} aria-describedby={`${id}-threshold-help`}
|
|
78
|
+
onValueChange={value => field.onChange(value)} inputRef={field.ref} onBlur={field.onBlur} invalid={invalid} showValue />
|
|
79
|
+
</Stack>}
|
|
80
|
+
</ControlledFormField>
|
|
81
|
+
<ControlledFormField control={control} name="color" label="Brand color">
|
|
82
|
+
{(field, _error, invalid) => <ColorInput ref={field.ref} name={field.name}
|
|
83
|
+
value={field.value} onValueChange={value => field.onChange(value)} onBlur={field.onBlur} invalid={invalid} />}
|
|
84
|
+
</ControlledFormField>
|
|
85
|
+
</Stack>;
|
|
86
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { useEffect, useId, useRef, useState } from 'react';
|
|
2
|
+
import { useFieldArray, useForm, useWatch, type FieldErrors } from 'react-hook-form';
|
|
3
|
+
import { Button } from 'themelia-react-ui/base/buttons';
|
|
4
|
+
import { ControlledFormField, DirtyStateBanner, ErrorSummary, FormActionsBar, FormSection, SubmitStateButton } from 'themelia-react-ui/base/forms';
|
|
5
|
+
import { Select, ToggleField } from 'themelia-react-ui/base/choice-inputs';
|
|
6
|
+
import { Input, Textarea } from 'themelia-react-ui/base/text-inputs';
|
|
7
|
+
import { Stack } from 'themelia-react-ui/base/structure';
|
|
8
|
+
import { MetadataList } from 'themelia-react-ui/base/metadata-list';
|
|
9
|
+
import { TwoColumnLayout } from 'themelia-react-ui/layout/containers';
|
|
10
|
+
import { Page } from 'themelia-react-ui/layout/page';
|
|
11
|
+
import { Money } from 'themelia-react-ui/primitives';
|
|
12
|
+
import { Text } from 'themelia-react-ui/typography';
|
|
13
|
+
|
|
14
|
+
export interface OrderEditValues {
|
|
15
|
+
reference: string; email: string; notes: string; priority: string; notify: boolean;
|
|
16
|
+
lines: { product: string; quantity: number; priceMinor: number }[];
|
|
17
|
+
}
|
|
18
|
+
export type OrderSaveResult =
|
|
19
|
+
| { ok: true; values: OrderEditValues }
|
|
20
|
+
| { ok: false; errors: Partial<Record<'reference' | 'email' | 'notes' | 'lines', string>>; message?: string };
|
|
21
|
+
export interface RHFEditPageProps {
|
|
22
|
+
initial: OrderEditValues;
|
|
23
|
+
/** Await the complete request and return the accepted server representation. */
|
|
24
|
+
onSave: (values: OrderEditValues) => Promise<OrderSaveResult>;
|
|
25
|
+
onCancel: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function messages(errors: FieldErrors<OrderEditValues>): string[] {
|
|
29
|
+
const result: string[] = [];
|
|
30
|
+
for (const error of [errors.reference, errors.email, errors.notes, errors.priority, errors.lines, errors.lines?.root, errors.root?.server]) {
|
|
31
|
+
if (typeof error?.message === 'string') result.push(error.message);
|
|
32
|
+
}
|
|
33
|
+
for (const line of Array.isArray(errors.lines) ? errors.lines : []) for (const error of [line?.product, line?.quantity, line?.priceMinor]) {
|
|
34
|
+
if (typeof error?.message === 'string') result.push(error.message);
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Mount with key={record.id}; background revalidation must not reset a dirty draft. */
|
|
40
|
+
export function RHFEditPage({ initial, onSave, onCancel }: RHFEditPageProps) {
|
|
41
|
+
const id = useId();
|
|
42
|
+
const lock = useRef(false);
|
|
43
|
+
const pendingFocus = useRef<'reference' | 'email' | 'notes' | 'lines.0.product' | undefined>(undefined);
|
|
44
|
+
const [saved, setSaved] = useState(false);
|
|
45
|
+
const form = useForm<OrderEditValues>({ defaultValues: initial, mode: 'onBlur' });
|
|
46
|
+
const lines = useFieldArray({ control: form.control, name: 'lines', rules: { minLength: { value: 1, message: 'Keep at least one line item.' } } });
|
|
47
|
+
const draftLines = useWatch({ control: form.control, name: 'lines' });
|
|
48
|
+
const totalMinor = draftLines.reduce((sum, line) => sum + (line.quantity || 0) * (line.priceMinor || 0), 0);
|
|
49
|
+
const { isDirty, isSubmitting, errors } = form.formState;
|
|
50
|
+
const summary = messages(errors);
|
|
51
|
+
const { setFocus } = form;
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!isSubmitting && pendingFocus.current) {
|
|
54
|
+
setFocus(pendingFocus.current);
|
|
55
|
+
pendingFocus.current = undefined;
|
|
56
|
+
}
|
|
57
|
+
}, [isSubmitting, setFocus]);
|
|
58
|
+
const submit = form.handleSubmit(async values => {
|
|
59
|
+
if (lock.current) return;
|
|
60
|
+
lock.current = true;
|
|
61
|
+
setSaved(false);
|
|
62
|
+
pendingFocus.current = undefined;
|
|
63
|
+
form.clearErrors();
|
|
64
|
+
try {
|
|
65
|
+
const result = await onSave(values);
|
|
66
|
+
if (!result.ok) {
|
|
67
|
+
let focused = false;
|
|
68
|
+
for (const name of ['reference', 'email', 'notes', 'lines'] as const) {
|
|
69
|
+
const message = result.errors[name];
|
|
70
|
+
if (message) {
|
|
71
|
+
form.setError(name, { type: 'server', message });
|
|
72
|
+
if (!focused) pendingFocus.current = name === 'lines' ? (values.lines.length ? 'lines.0.product' : undefined) : name;
|
|
73
|
+
focused = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
form.setError('root.server', { type: 'server', message: result.message ?? 'Review the highlighted fields. Your draft is kept.' });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
form.reset(result.values);
|
|
80
|
+
setSaved(true);
|
|
81
|
+
} catch {
|
|
82
|
+
form.setError('root.server', { type: 'transport', message: 'Saving failed. Your draft is kept; retry Save order.' });
|
|
83
|
+
} finally { lock.current = false; }
|
|
84
|
+
});
|
|
85
|
+
return <Page maxWidth="xl"
|
|
86
|
+
render={<form id={`${id}-form`} onSubmit={submit} noValidate aria-busy={isSubmitting} />}
|
|
87
|
+
header={{ title: 'Edit order', description: 'Update customer, fulfillment and line items.' }}>
|
|
88
|
+
{isDirty && <DirtyStateBanner title="Unsaved changes" description="Save to commit this draft, or discard to restore the last saved version." />}
|
|
89
|
+
{summary.length > 0 && <ErrorSummary title="Order could not be saved" errors={summary} />}
|
|
90
|
+
{saved && !isDirty && <Text role="status">Order saved.</Text>}
|
|
91
|
+
<TwoColumnLayout adaptTo="container" main={<>
|
|
92
|
+
<FormSection title="Customer" description="The customer receives order updates at this address.">
|
|
93
|
+
<ControlledFormField control={form.control} name="reference" label="Order reference" required rules={{ required: 'Enter an order reference.' }}>
|
|
94
|
+
{(field, _error, invalid) => <Input ref={field.ref} name={field.name} value={field.value} onValueChange={value => field.onChange(value)} onBlur={field.onBlur} invalid={invalid} disabled={isSubmitting} />}
|
|
95
|
+
</ControlledFormField>
|
|
96
|
+
<ControlledFormField control={form.control} name="email" label="Customer email" required rules={{ required: 'Enter an email.', pattern: { value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: 'Enter a valid email.' } }}>
|
|
97
|
+
{(field, _error, invalid) => <Input type="email" ref={field.ref} name={field.name} value={field.value} onValueChange={value => field.onChange(value)} onBlur={field.onBlur} invalid={invalid} disabled={isSubmitting} />}
|
|
98
|
+
</ControlledFormField>
|
|
99
|
+
</FormSection>
|
|
100
|
+
<FormSection title="Line items" actions={<Button type="button" disabled={isSubmitting} onClick={() => lines.append({ product: '', quantity: 1, priceMinor: 0 })}>Add item</Button>}>
|
|
101
|
+
{lines.fields.map((line, index) => <Stack key={line.id} gap="md">
|
|
102
|
+
<ControlledFormField control={form.control} name={`lines.${index}.product`} label={`Item ${index + 1}`} rules={{ required: 'Enter a product.' }}>
|
|
103
|
+
{(field, _error, invalid) => <Input ref={field.ref} name={field.name} value={field.value} onValueChange={value => field.onChange(value)} onBlur={field.onBlur} invalid={invalid} disabled={isSubmitting} />}
|
|
104
|
+
</ControlledFormField>
|
|
105
|
+
<Stack direction={{ base: 'vertical', sm: 'horizontal' }} gap="md" align="end">
|
|
106
|
+
<ControlledFormField control={form.control} name={`lines.${index}.quantity`} label="Quantity" rules={{ validate: value => (Number.isFinite(value) && Number.isInteger(value) && value >= 1) || 'Enter a whole quantity of at least one.' }}>
|
|
107
|
+
{(field, _error, invalid) => <Input ref={field.ref} type="number" name={field.name} min={1} value={Number.isNaN(field.value) ? '' : String(field.value)} onChange={event => field.onChange(event.target.valueAsNumber)} onBlur={field.onBlur} invalid={invalid} disabled={isSubmitting} />}
|
|
108
|
+
</ControlledFormField>
|
|
109
|
+
<ControlledFormField control={form.control} name={`lines.${index}.priceMinor`} label="Unit price (cents)" rules={{ validate: value => (Number.isFinite(value) && Number.isInteger(value) && value >= 0) || 'Enter a whole price in cents of zero or more.' }}>
|
|
110
|
+
{(field, _error, invalid) => <Input ref={field.ref} type="number" name={field.name} min={0} value={Number.isNaN(field.value) ? '' : String(field.value)} onChange={event => field.onChange(event.target.valueAsNumber)} onBlur={field.onBlur} invalid={invalid} disabled={isSubmitting} />}
|
|
111
|
+
</ControlledFormField>
|
|
112
|
+
<Button type="button" disabled={isSubmitting || lines.fields.length < 2} onClick={() => lines.remove(index)}>Remove item {index + 1}</Button>
|
|
113
|
+
</Stack>
|
|
114
|
+
</Stack>)}
|
|
115
|
+
</FormSection>
|
|
116
|
+
<FormSection title="Internal notes">
|
|
117
|
+
<ControlledFormField control={form.control} name="notes" label="Notes" hint="Visible to your team only.">
|
|
118
|
+
{(field, _error, invalid) => <Textarea ref={field.ref} name={field.name} value={field.value} onValueChange={value => field.onChange(value)} onBlur={field.onBlur} invalid={invalid} disabled={isSubmitting} />}
|
|
119
|
+
</ControlledFormField>
|
|
120
|
+
</FormSection>
|
|
121
|
+
</>} aside={<>
|
|
122
|
+
<FormSection title="Fulfillment">
|
|
123
|
+
<ControlledFormField control={form.control} name="priority" label="Priority">
|
|
124
|
+
{(field, _error, invalid) => <Select ref={field.ref} name={field.name} value={field.value} onValueChange={value => field.onChange(value ?? '')} onBlur={field.onBlur} invalid={invalid} disabled={isSubmitting} options={[{ value: 'standard', label: 'Standard' }, { value: 'express', label: 'Express' }]} />}
|
|
125
|
+
</ControlledFormField>
|
|
126
|
+
<ControlledFormField control={form.control} name="notify">
|
|
127
|
+
{(field, _error, invalid) => <ToggleField label="Email customer on save" name={field.name} value={field.value} controlRef={field.ref} onBlur={field.onBlur} onValueChange={value => field.onChange(value)} invalid={invalid} disabled={isSubmitting} />}
|
|
128
|
+
</ControlledFormField>
|
|
129
|
+
</FormSection>
|
|
130
|
+
<FormSection title="Summary"><MetadataList items={[
|
|
131
|
+
{ id: 'items', label: 'Line items', value: lines.fields.length },
|
|
132
|
+
{ id: 'total', label: 'Draft total', value: <Money amount={totalMinor} currency="USD" unit="minor" /> },
|
|
133
|
+
]} /></FormSection>
|
|
134
|
+
</>} />
|
|
135
|
+
<FormActionsBar sticky leading={isDirty ? 'Changes are not saved yet.' : 'All changes saved.'}>
|
|
136
|
+
<Button type="button" disabled={isSubmitting} onClick={onCancel}>Cancel</Button>
|
|
137
|
+
<Button type="button" disabled={isSubmitting || !isDirty} onClick={() => { form.reset(); setSaved(false); }}>Discard changes</Button>
|
|
138
|
+
<SubmitStateButton dirty={isDirty} disableWhenPristine submitting={isSubmitting}>Save order</SubmitStateButton>
|
|
139
|
+
</FormActionsBar>
|
|
140
|
+
</Page>;
|
|
141
|
+
}
|