@open-mercato/ui 0.4.2-canary-c02407ff85
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/build.mjs +62 -0
- package/dist/backend/AppShell.js +902 -0
- package/dist/backend/AppShell.js.map +7 -0
- package/dist/backend/ConfirmDialog.js +17 -0
- package/dist/backend/ConfirmDialog.js.map +7 -0
- package/dist/backend/ContextHelp.js +31 -0
- package/dist/backend/ContextHelp.js.map +7 -0
- package/dist/backend/CrudForm.js +2028 -0
- package/dist/backend/CrudForm.js.map +7 -0
- package/dist/backend/DataTable.js +1363 -0
- package/dist/backend/DataTable.js.map +7 -0
- package/dist/backend/EmptyState.js +52 -0
- package/dist/backend/EmptyState.js.map +7 -0
- package/dist/backend/FilterBar.js +140 -0
- package/dist/backend/FilterBar.js.map +7 -0
- package/dist/backend/FilterOverlay.js +279 -0
- package/dist/backend/FilterOverlay.js.map +7 -0
- package/dist/backend/FlashMessages.js +66 -0
- package/dist/backend/FlashMessages.js.map +7 -0
- package/dist/backend/JsonBuilder.js +322 -0
- package/dist/backend/JsonBuilder.js.map +7 -0
- package/dist/backend/JsonDisplay.js +203 -0
- package/dist/backend/JsonDisplay.js.map +7 -0
- package/dist/backend/Page.js +27 -0
- package/dist/backend/Page.js.map +7 -0
- package/dist/backend/PerspectiveSidebar.js +282 -0
- package/dist/backend/PerspectiveSidebar.js.map +7 -0
- package/dist/backend/RowActions.js +148 -0
- package/dist/backend/RowActions.js.map +7 -0
- package/dist/backend/TruncatedCell.js +92 -0
- package/dist/backend/TruncatedCell.js.map +7 -0
- package/dist/backend/UserMenu.js +107 -0
- package/dist/backend/UserMenu.js.map +7 -0
- package/dist/backend/ValueIcons.js +34 -0
- package/dist/backend/ValueIcons.js.map +7 -0
- package/dist/backend/custom-fields/FieldDefinitionsEditor.js +1264 -0
- package/dist/backend/custom-fields/FieldDefinitionsEditor.js.map +7 -0
- package/dist/backend/custom-fields/FieldDefinitionsManager.js +332 -0
- package/dist/backend/custom-fields/FieldDefinitionsManager.js.map +7 -0
- package/dist/backend/dashboard/DashboardScreen.js +578 -0
- package/dist/backend/dashboard/DashboardScreen.js.map +7 -0
- package/dist/backend/dashboard/index.js +5 -0
- package/dist/backend/dashboard/index.js.map +7 -0
- package/dist/backend/dashboard/widgetRegistry.js +55 -0
- package/dist/backend/dashboard/widgetRegistry.js.map +7 -0
- package/dist/backend/detail/ActivitiesSection.js +962 -0
- package/dist/backend/detail/ActivitiesSection.js.map +7 -0
- package/dist/backend/detail/AddressEditor.js +413 -0
- package/dist/backend/detail/AddressEditor.js.map +7 -0
- package/dist/backend/detail/AddressTiles.js +437 -0
- package/dist/backend/detail/AddressTiles.js.map +7 -0
- package/dist/backend/detail/AddressesSection.js +264 -0
- package/dist/backend/detail/AddressesSection.js.map +7 -0
- package/dist/backend/detail/AttachmentDeleteDialog.js +41 -0
- package/dist/backend/detail/AttachmentDeleteDialog.js.map +7 -0
- package/dist/backend/detail/AttachmentMetadataDialog.js +517 -0
- package/dist/backend/detail/AttachmentMetadataDialog.js.map +7 -0
- package/dist/backend/detail/AttachmentsSection.js +367 -0
- package/dist/backend/detail/AttachmentsSection.js.map +7 -0
- package/dist/backend/detail/CustomDataSection.js +433 -0
- package/dist/backend/detail/CustomDataSection.js.map +7 -0
- package/dist/backend/detail/DetailFieldsSection.js +75 -0
- package/dist/backend/detail/DetailFieldsSection.js.map +7 -0
- package/dist/backend/detail/ErrorMessage.js +28 -0
- package/dist/backend/detail/ErrorMessage.js.map +7 -0
- package/dist/backend/detail/InlineEditors.js +681 -0
- package/dist/backend/detail/InlineEditors.js.map +7 -0
- package/dist/backend/detail/LoadingMessage.js +14 -0
- package/dist/backend/detail/LoadingMessage.js.map +7 -0
- package/dist/backend/detail/NotesSection.js +1032 -0
- package/dist/backend/detail/NotesSection.js.map +7 -0
- package/dist/backend/detail/TabEmptyState.js +25 -0
- package/dist/backend/detail/TabEmptyState.js.map +7 -0
- package/dist/backend/detail/TagsSection.js +254 -0
- package/dist/backend/detail/TagsSection.js.map +7 -0
- package/dist/backend/detail/addressFormat.js +77 -0
- package/dist/backend/detail/addressFormat.js.map +7 -0
- package/dist/backend/detail/index.js +34 -0
- package/dist/backend/detail/index.js.map +7 -0
- package/dist/backend/fields/registry.generated.js +8 -0
- package/dist/backend/fields/registry.generated.js.map +7 -0
- package/dist/backend/fields/registry.js +29 -0
- package/dist/backend/fields/registry.js.map +7 -0
- package/dist/backend/indexes/PartialIndexBanner.js +58 -0
- package/dist/backend/indexes/PartialIndexBanner.js.map +7 -0
- package/dist/backend/indexes/store.js +62 -0
- package/dist/backend/indexes/store.js.map +7 -0
- package/dist/backend/injection/InjectionSpot.js +179 -0
- package/dist/backend/injection/InjectionSpot.js.map +7 -0
- package/dist/backend/injection/PageInjectionBoundary.js +26 -0
- package/dist/backend/injection/PageInjectionBoundary.js.map +7 -0
- package/dist/backend/injection/helpers.js +26 -0
- package/dist/backend/injection/helpers.js.map +7 -0
- package/dist/backend/injection/widgetRegistry.js +55 -0
- package/dist/backend/injection/widgetRegistry.js.map +7 -0
- package/dist/backend/inputs/ComboboxInput.js +225 -0
- package/dist/backend/inputs/ComboboxInput.js.map +7 -0
- package/dist/backend/inputs/LookupSelect.js +191 -0
- package/dist/backend/inputs/LookupSelect.js.map +7 -0
- package/dist/backend/inputs/PhoneNumberField.js +100 -0
- package/dist/backend/inputs/PhoneNumberField.js.map +7 -0
- package/dist/backend/inputs/SwitchableMarkdownInput.js +92 -0
- package/dist/backend/inputs/SwitchableMarkdownInput.js.map +7 -0
- package/dist/backend/inputs/TagsInput.js +222 -0
- package/dist/backend/inputs/TagsInput.js.map +7 -0
- package/dist/backend/inputs/index.js +6 -0
- package/dist/backend/inputs/index.js.map +7 -0
- package/dist/backend/operations/LastOperationBanner.js +80 -0
- package/dist/backend/operations/LastOperationBanner.js.map +7 -0
- package/dist/backend/operations/store.js +183 -0
- package/dist/backend/operations/store.js.map +7 -0
- package/dist/backend/schedule/ScheduleAgenda.js +107 -0
- package/dist/backend/schedule/ScheduleAgenda.js.map +7 -0
- package/dist/backend/schedule/ScheduleGrid.js +107 -0
- package/dist/backend/schedule/ScheduleGrid.js.map +7 -0
- package/dist/backend/schedule/ScheduleToolbar.js +166 -0
- package/dist/backend/schedule/ScheduleToolbar.js.map +7 -0
- package/dist/backend/schedule/ScheduleView.js +165 -0
- package/dist/backend/schedule/ScheduleView.js.map +7 -0
- package/dist/backend/schedule/index.js +6 -0
- package/dist/backend/schedule/index.js.map +7 -0
- package/dist/backend/schedule/recurrence.js +83 -0
- package/dist/backend/schedule/recurrence.js.map +7 -0
- package/dist/backend/schedule/types.js +1 -0
- package/dist/backend/schedule/types.js.map +7 -0
- package/dist/backend/upgrades/UpgradeActionBanner.js +91 -0
- package/dist/backend/upgrades/UpgradeActionBanner.js.map +7 -0
- package/dist/backend/utils/api.js +127 -0
- package/dist/backend/utils/api.js.map +7 -0
- package/dist/backend/utils/apiCall.js +48 -0
- package/dist/backend/utils/apiCall.js.map +7 -0
- package/dist/backend/utils/crud.js +126 -0
- package/dist/backend/utils/crud.js.map +7 -0
- package/dist/backend/utils/customFieldColumns.js +56 -0
- package/dist/backend/utils/customFieldColumns.js.map +7 -0
- package/dist/backend/utils/customFieldDefs.js +143 -0
- package/dist/backend/utils/customFieldDefs.js.map +7 -0
- package/dist/backend/utils/customFieldFilters.js +126 -0
- package/dist/backend/utils/customFieldFilters.js.map +7 -0
- package/dist/backend/utils/customFieldForms.js +162 -0
- package/dist/backend/utils/customFieldForms.js.map +7 -0
- package/dist/backend/utils/customFieldValues.js +26 -0
- package/dist/backend/utils/customFieldValues.js.map +7 -0
- package/dist/backend/utils/flash.js +16 -0
- package/dist/backend/utils/flash.js.map +7 -0
- package/dist/backend/utils/nav.js +185 -0
- package/dist/backend/utils/nav.js.map +7 -0
- package/dist/backend/utils/serverErrors.js +230 -0
- package/dist/backend/utils/serverErrors.js.map +7 -0
- package/dist/frontend/AuthFooter.js +23 -0
- package/dist/frontend/AuthFooter.js.map +7 -0
- package/dist/frontend/LanguageSwitcher.js +57 -0
- package/dist/frontend/LanguageSwitcher.js.map +7 -0
- package/dist/frontend/Layout.js +14 -0
- package/dist/frontend/Layout.js.map +7 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +7 -0
- package/dist/primitives/DataLoader.js +67 -0
- package/dist/primitives/DataLoader.js.map +7 -0
- package/dist/primitives/ErrorNotice.js +20 -0
- package/dist/primitives/ErrorNotice.js.map +7 -0
- package/dist/primitives/alert.js +38 -0
- package/dist/primitives/alert.js.map +7 -0
- package/dist/primitives/badge.js +28 -0
- package/dist/primitives/badge.js.map +7 -0
- package/dist/primitives/button.js +44 -0
- package/dist/primitives/button.js.map +7 -0
- package/dist/primitives/card.js +91 -0
- package/dist/primitives/card.js.map +7 -0
- package/dist/primitives/checkbox.js +28 -0
- package/dist/primitives/checkbox.js.map +7 -0
- package/dist/primitives/dialog.js +90 -0
- package/dist/primitives/dialog.js.map +7 -0
- package/dist/primitives/input.js +22 -0
- package/dist/primitives/input.js.map +7 -0
- package/dist/primitives/label.js +21 -0
- package/dist/primitives/label.js.map +7 -0
- package/dist/primitives/separator.js +9 -0
- package/dist/primitives/separator.js.map +7 -0
- package/dist/primitives/spinner.js +24 -0
- package/dist/primitives/spinner.js.map +7 -0
- package/dist/primitives/switch.js +80 -0
- package/dist/primitives/switch.js.map +7 -0
- package/dist/primitives/table.js +29 -0
- package/dist/primitives/table.js.map +7 -0
- package/dist/primitives/tabs.js +87 -0
- package/dist/primitives/tabs.js.map +7 -0
- package/dist/primitives/textarea.js +21 -0
- package/dist/primitives/textarea.js.map +7 -0
- package/dist/primitives/tooltip.js +60 -0
- package/dist/primitives/tooltip.js.map +7 -0
- package/dist/theme/QueryProvider.js +44 -0
- package/dist/theme/QueryProvider.js.map +7 -0
- package/dist/theme/ThemeProvider.js +95 -0
- package/dist/theme/ThemeProvider.js.map +7 -0
- package/dist/theme/ThemeToggle.js +88 -0
- package/dist/theme/ThemeToggle.js.map +7 -0
- package/dist/theme/index.js +10 -0
- package/dist/theme/index.js.map +7 -0
- package/dist/types/react-big-calendar.d.js +1 -0
- package/dist/types/react-big-calendar.d.js.map +7 -0
- package/jest.config.cjs +23 -0
- package/jest.setup.ts +55 -0
- package/package.json +105 -0
- package/src/backend/AppShell.tsx +1096 -0
- package/src/backend/ConfirmDialog.tsx +19 -0
- package/src/backend/ContextHelp.tsx +38 -0
- package/src/backend/CrudForm.tsx +2503 -0
- package/src/backend/DataTable.tsx +1730 -0
- package/src/backend/EmptyState.tsx +65 -0
- package/src/backend/FilterBar.tsx +161 -0
- package/src/backend/FilterOverlay.tsx +328 -0
- package/src/backend/FlashMessages.tsx +82 -0
- package/src/backend/JsonBuilder.tsx +362 -0
- package/src/backend/JsonDisplay.tsx +254 -0
- package/src/backend/Page.tsx +30 -0
- package/src/backend/PerspectiveSidebar.tsx +337 -0
- package/src/backend/RowActions.tsx +151 -0
- package/src/backend/TruncatedCell.tsx +133 -0
- package/src/backend/UserMenu.tsx +118 -0
- package/src/backend/ValueIcons.tsx +48 -0
- package/src/backend/__tests__/AppShell.test.tsx +115 -0
- package/src/backend/__tests__/CrudForm.render.test.tsx +30 -0
- package/src/backend/__tests__/DataTable.render.test.tsx +48 -0
- package/src/backend/__tests__/custom-field-filters.test.ts +72 -0
- package/src/backend/__tests__/custom-field-forms.test.ts +54 -0
- package/src/backend/__tests__/serverErrors.test.ts +83 -0
- package/src/backend/custom-fields/FieldDefinitionsEditor.tsx +1292 -0
- package/src/backend/custom-fields/FieldDefinitionsManager.tsx +381 -0
- package/src/backend/dashboard/DashboardScreen.tsx +684 -0
- package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +112 -0
- package/src/backend/dashboard/index.ts +1 -0
- package/src/backend/dashboard/widgetRegistry.ts +68 -0
- package/src/backend/detail/ActivitiesSection.tsx +1284 -0
- package/src/backend/detail/AddressEditor.tsx +472 -0
- package/src/backend/detail/AddressTiles.tsx +587 -0
- package/src/backend/detail/AddressesSection.tsx +346 -0
- package/src/backend/detail/AttachmentDeleteDialog.tsx +56 -0
- package/src/backend/detail/AttachmentMetadataDialog.tsx +672 -0
- package/src/backend/detail/AttachmentsSection.tsx +414 -0
- package/src/backend/detail/CustomDataSection.tsx +530 -0
- package/src/backend/detail/DetailFieldsSection.tsx +147 -0
- package/src/backend/detail/ErrorMessage.tsx +32 -0
- package/src/backend/detail/InlineEditors.tsx +877 -0
- package/src/backend/detail/LoadingMessage.tsx +14 -0
- package/src/backend/detail/NotesSection.tsx +1275 -0
- package/src/backend/detail/TabEmptyState.tsx +48 -0
- package/src/backend/detail/TagsSection.tsx +314 -0
- package/src/backend/detail/addressFormat.tsx +121 -0
- package/src/backend/detail/index.ts +44 -0
- package/src/backend/fields/registry.generated.ts +8 -0
- package/src/backend/fields/registry.ts +38 -0
- package/src/backend/indexes/PartialIndexBanner.tsx +68 -0
- package/src/backend/indexes/store.ts +88 -0
- package/src/backend/injection/InjectionSpot.tsx +236 -0
- package/src/backend/injection/PageInjectionBoundary.tsx +31 -0
- package/src/backend/injection/helpers.ts +35 -0
- package/src/backend/injection/widgetRegistry.ts +68 -0
- package/src/backend/inputs/ComboboxInput.tsx +269 -0
- package/src/backend/inputs/LookupSelect.tsx +247 -0
- package/src/backend/inputs/PhoneNumberField.tsx +129 -0
- package/src/backend/inputs/SwitchableMarkdownInput.tsx +128 -0
- package/src/backend/inputs/TagsInput.tsx +259 -0
- package/src/backend/inputs/index.ts +5 -0
- package/src/backend/operations/LastOperationBanner.tsx +85 -0
- package/src/backend/operations/__tests__/LastOperationBanner.test.tsx +99 -0
- package/src/backend/operations/store.ts +230 -0
- package/src/backend/schedule/ScheduleAgenda.tsx +136 -0
- package/src/backend/schedule/ScheduleGrid.tsx +136 -0
- package/src/backend/schedule/ScheduleToolbar.tsx +178 -0
- package/src/backend/schedule/ScheduleView.tsx +198 -0
- package/src/backend/schedule/index.ts +5 -0
- package/src/backend/schedule/recurrence.ts +99 -0
- package/src/backend/schedule/types.ts +26 -0
- package/src/backend/upgrades/UpgradeActionBanner.tsx +128 -0
- package/src/backend/utils/__tests__/apiCall.test.ts +109 -0
- package/src/backend/utils/__tests__/crud.test.ts +87 -0
- package/src/backend/utils/__tests__/customFieldDefs.test.ts +25 -0
- package/src/backend/utils/__tests__/customFieldValues.test.ts +35 -0
- package/src/backend/utils/api.ts +149 -0
- package/src/backend/utils/apiCall.ts +96 -0
- package/src/backend/utils/crud.ts +174 -0
- package/src/backend/utils/customFieldColumns.ts +71 -0
- package/src/backend/utils/customFieldDefs.ts +245 -0
- package/src/backend/utils/customFieldFilters.ts +145 -0
- package/src/backend/utils/customFieldForms.ts +196 -0
- package/src/backend/utils/customFieldValues.ts +41 -0
- package/src/backend/utils/flash.ts +17 -0
- package/src/backend/utils/nav.ts +238 -0
- package/src/backend/utils/serverErrors.ts +302 -0
- package/src/frontend/AuthFooter.tsx +29 -0
- package/src/frontend/LanguageSwitcher.tsx +66 -0
- package/src/frontend/Layout.tsx +13 -0
- package/src/index.ts +32 -0
- package/src/primitives/DataLoader.tsx +92 -0
- package/src/primitives/ErrorNotice.tsx +26 -0
- package/src/primitives/alert.tsx +52 -0
- package/src/primitives/badge.tsx +31 -0
- package/src/primitives/button.tsx +47 -0
- package/src/primitives/card.tsx +92 -0
- package/src/primitives/checkbox.tsx +28 -0
- package/src/primitives/dialog.tsx +110 -0
- package/src/primitives/input.tsx +20 -0
- package/src/primitives/label.tsx +18 -0
- package/src/primitives/separator.tsx +7 -0
- package/src/primitives/spinner.tsx +27 -0
- package/src/primitives/switch.tsx +86 -0
- package/src/primitives/table.tsx +27 -0
- package/src/primitives/tabs.tsx +128 -0
- package/src/primitives/textarea.tsx +20 -0
- package/src/primitives/tooltip.tsx +85 -0
- package/src/theme/QueryProvider.tsx +46 -0
- package/src/theme/ThemeProvider.tsx +120 -0
- package/src/theme/ThemeToggle.tsx +88 -0
- package/src/theme/index.ts +3 -0
- package/src/types/react-big-calendar.d.ts +16 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +9 -0
- package/watch.mjs +6 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { Spinner } from './spinner'
|
|
3
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
|
+
|
|
5
|
+
export interface DataLoaderProps {
|
|
6
|
+
isLoading: boolean
|
|
7
|
+
children: React.ReactNode
|
|
8
|
+
loadingMessage?: string
|
|
9
|
+
spinnerSize?: 'sm' | 'md' | 'lg'
|
|
10
|
+
className?: string
|
|
11
|
+
loadingClassName?: string
|
|
12
|
+
// Optional: show a skeleton or placeholder instead of just spinner
|
|
13
|
+
showSkeleton?: boolean
|
|
14
|
+
skeletonComponent?: React.ReactNode
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function DataLoader({
|
|
18
|
+
isLoading,
|
|
19
|
+
children,
|
|
20
|
+
loadingMessage,
|
|
21
|
+
spinnerSize = 'md',
|
|
22
|
+
className = '',
|
|
23
|
+
loadingClassName = '',
|
|
24
|
+
showSkeleton = false,
|
|
25
|
+
skeletonComponent
|
|
26
|
+
}: DataLoaderProps) {
|
|
27
|
+
const t = useT()
|
|
28
|
+
const resolvedLoadingMessage = loadingMessage ?? t('ui.dataLoader.loading', 'Loading...')
|
|
29
|
+
if (isLoading) {
|
|
30
|
+
if (showSkeleton && skeletonComponent) {
|
|
31
|
+
return <div className={className}>{skeletonComponent}</div>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className={`flex items-center justify-center gap-2 py-4 ${loadingClassName} ${className}`}>
|
|
36
|
+
<Spinner size={spinnerSize} />
|
|
37
|
+
<span className="text-sm text-muted-foreground">{resolvedLoadingMessage}</span>
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return <div className={className}>{children}</div>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Convenience component for inline loading states
|
|
46
|
+
export function InlineLoader({
|
|
47
|
+
isLoading,
|
|
48
|
+
children,
|
|
49
|
+
loadingMessage,
|
|
50
|
+
spinnerSize = 'sm'
|
|
51
|
+
}: {
|
|
52
|
+
isLoading: boolean
|
|
53
|
+
children: React.ReactNode
|
|
54
|
+
loadingMessage?: string
|
|
55
|
+
spinnerSize?: 'sm' | 'md' | 'lg'
|
|
56
|
+
}) {
|
|
57
|
+
return (
|
|
58
|
+
<DataLoader
|
|
59
|
+
isLoading={isLoading}
|
|
60
|
+
loadingMessage={loadingMessage}
|
|
61
|
+
spinnerSize={spinnerSize}
|
|
62
|
+
className="inline-flex items-center"
|
|
63
|
+
loadingClassName="py-2"
|
|
64
|
+
>
|
|
65
|
+
{children}
|
|
66
|
+
</DataLoader>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Convenience component for full-page loading states
|
|
71
|
+
export function PageLoader({
|
|
72
|
+
isLoading,
|
|
73
|
+
children,
|
|
74
|
+
loadingMessage,
|
|
75
|
+
spinnerSize = 'lg'
|
|
76
|
+
}: {
|
|
77
|
+
isLoading: boolean
|
|
78
|
+
children: React.ReactNode
|
|
79
|
+
loadingMessage?: string
|
|
80
|
+
spinnerSize?: 'sm' | 'md' | 'lg'
|
|
81
|
+
}) {
|
|
82
|
+
return (
|
|
83
|
+
<DataLoader
|
|
84
|
+
isLoading={isLoading}
|
|
85
|
+
loadingMessage={loadingMessage}
|
|
86
|
+
spinnerSize={spinnerSize}
|
|
87
|
+
className="min-h-[200px] flex items-center justify-center"
|
|
88
|
+
>
|
|
89
|
+
{children}
|
|
90
|
+
</DataLoader>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
|
+
|
|
5
|
+
export function ErrorNotice({ title, message, action }: {
|
|
6
|
+
title?: string
|
|
7
|
+
message?: string
|
|
8
|
+
action?: React.ReactNode
|
|
9
|
+
}) {
|
|
10
|
+
const t = useT()
|
|
11
|
+
const defaultTitle = title ?? t('ui.errors.defaultTitle', 'Something went wrong')
|
|
12
|
+
const defaultMessage = message ?? t('ui.errors.defaultMessage', 'Unable to load data. Please try again.')
|
|
13
|
+
return (
|
|
14
|
+
<div className="rounded-md border border-red-200 bg-red-50 p-4 text-red-800">
|
|
15
|
+
<div className="flex items-start gap-3">
|
|
16
|
+
<span className="inline-block mt-0.5 h-4 w-4 rounded-full border-2 border-red-500" aria-hidden />
|
|
17
|
+
<div className="space-y-1">
|
|
18
|
+
<div className="text-sm font-medium">{defaultTitle}</div>
|
|
19
|
+
<div className="text-sm opacity-90">{defaultMessage}</div>
|
|
20
|
+
{action ? <div className="mt-2">{action}</div> : null}
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
3
|
+
|
|
4
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
5
|
+
|
|
6
|
+
const alertVariants = cva(
|
|
7
|
+
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg~*]:pl-8 [&>svg]:text-current",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'border-border bg-background text-foreground',
|
|
12
|
+
destructive:
|
|
13
|
+
'border-destructive/60 bg-destructive/10 text-destructive dark:border-destructive/40 dark:bg-destructive/20 dark:text-destructive-foreground',
|
|
14
|
+
success:
|
|
15
|
+
'border-emerald-600/30 bg-emerald-500/10 text-emerald-900 dark:border-emerald-500/50 dark:bg-emerald-500/15 dark:text-emerald-50',
|
|
16
|
+
warning:
|
|
17
|
+
'border-amber-500/30 bg-amber-400/10 text-amber-950 dark:border-amber-400/40 dark:bg-amber-400/20 dark:text-amber-50',
|
|
18
|
+
info:
|
|
19
|
+
'border-sky-600/30 bg-sky-500/10 text-sky-900 dark:border-sky-500/40 dark:bg-sky-500/20 dark:text-sky-50',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
variant: 'default',
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
type AlertProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
|
29
|
+
|
|
30
|
+
const Alert = React.forwardRef<HTMLDivElement, AlertProps>(({ className, variant, ...props }, ref) => (
|
|
31
|
+
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
|
|
32
|
+
))
|
|
33
|
+
|
|
34
|
+
Alert.displayName = 'Alert'
|
|
35
|
+
|
|
36
|
+
const AlertTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
|
37
|
+
({ className, ...props }, ref) => (
|
|
38
|
+
<h5 ref={ref} className={cn('mb-1 text-sm font-semibold leading-tight', className)} {...props} />
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
AlertTitle.displayName = 'AlertTitle'
|
|
43
|
+
|
|
44
|
+
const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
|
45
|
+
({ className, ...props }, ref) => (
|
|
46
|
+
<p ref={ref} className={cn('text-sm leading-relaxed', className)} {...props} />
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
AlertDescription.displayName = 'AlertDescription'
|
|
51
|
+
|
|
52
|
+
export { Alert, AlertDescription, AlertTitle, alertVariants }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
3
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
4
|
+
|
|
5
|
+
const badgeVariants = cva(
|
|
6
|
+
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: 'border-transparent bg-primary text-primary-foreground shadow',
|
|
11
|
+
secondary: 'border-transparent bg-secondary text-secondary-foreground',
|
|
12
|
+
destructive: 'border-transparent bg-destructive text-destructive-foreground shadow',
|
|
13
|
+
outline: 'text-foreground',
|
|
14
|
+
muted: 'border-transparent bg-muted text-muted-foreground',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
defaultVariants: {
|
|
18
|
+
variant: 'default',
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
export type BadgeProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof badgeVariants>
|
|
24
|
+
|
|
25
|
+
export const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(({ className, variant, ...props }, ref) => (
|
|
26
|
+
<div ref={ref} className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
27
|
+
))
|
|
28
|
+
|
|
29
|
+
Badge.displayName = 'Badge'
|
|
30
|
+
|
|
31
|
+
export { badgeVariants }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { Slot } from '@radix-ui/react-slot'
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
4
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
|
|
12
|
+
destructive:
|
|
13
|
+
'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
|
14
|
+
outline:
|
|
15
|
+
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
|
16
|
+
secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
|
17
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
|
18
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
19
|
+
},
|
|
20
|
+
size: {
|
|
21
|
+
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
|
22
|
+
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
|
23
|
+
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
|
24
|
+
icon: 'size-9',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
variant: 'default',
|
|
29
|
+
size: 'default',
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
export function Button({
|
|
35
|
+
className,
|
|
36
|
+
variant,
|
|
37
|
+
size,
|
|
38
|
+
asChild = false,
|
|
39
|
+
...props
|
|
40
|
+
}: React.ComponentProps<'button'> &
|
|
41
|
+
VariantProps<typeof buttonVariants> & { asChild?: boolean }) {
|
|
42
|
+
const Comp = asChild ? Slot : 'button'
|
|
43
|
+
return <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} />
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { buttonVariants }
|
|
47
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
4
|
+
|
|
5
|
+
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="card"
|
|
9
|
+
className={cn(
|
|
10
|
+
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
data-slot="card-header"
|
|
22
|
+
className={cn(
|
|
23
|
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
data-slot="card-title"
|
|
35
|
+
className={cn("leading-none font-semibold", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
data-slot="card-description"
|
|
45
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
data-slot="card-action"
|
|
55
|
+
className={cn(
|
|
56
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
57
|
+
className
|
|
58
|
+
)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
data-slot="card-content"
|
|
68
|
+
className={cn("px-6", className)}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
data-slot="card-footer"
|
|
78
|
+
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
Card,
|
|
86
|
+
CardHeader,
|
|
87
|
+
CardFooter,
|
|
88
|
+
CardTitle,
|
|
89
|
+
CardAction,
|
|
90
|
+
CardDescription,
|
|
91
|
+
CardContent,
|
|
92
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
|
3
|
+
import { Check } from "lucide-react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@open-mercato/shared/lib/utils"
|
|
6
|
+
|
|
7
|
+
const Checkbox = React.forwardRef<
|
|
8
|
+
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
|
9
|
+
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
|
10
|
+
>(({ className, ...props }, ref) => (
|
|
11
|
+
<CheckboxPrimitive.Root
|
|
12
|
+
ref={ref}
|
|
13
|
+
className={cn(
|
|
14
|
+
"peer size-4 shrink-0 rounded-sm border border-input bg-background shadow-xs ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary",
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
>
|
|
19
|
+
<CheckboxPrimitive.Indicator
|
|
20
|
+
className={cn("flex items-center justify-center text-current")}
|
|
21
|
+
>
|
|
22
|
+
<Check className="size-3.5" />
|
|
23
|
+
</CheckboxPrimitive.Indicator>
|
|
24
|
+
</CheckboxPrimitive.Root>
|
|
25
|
+
))
|
|
26
|
+
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
|
27
|
+
|
|
28
|
+
export { Checkbox }
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
5
|
+
import { X } from 'lucide-react'
|
|
6
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
7
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
8
|
+
|
|
9
|
+
const Dialog = DialogPrimitive.Root
|
|
10
|
+
|
|
11
|
+
const DialogTrigger = DialogPrimitive.Trigger
|
|
12
|
+
|
|
13
|
+
const DialogPortal = DialogPrimitive.Portal
|
|
14
|
+
|
|
15
|
+
const DialogClose = DialogPrimitive.Close
|
|
16
|
+
|
|
17
|
+
const DialogOverlay = React.forwardRef<
|
|
18
|
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
19
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
20
|
+
>(({ className, ...props }, ref) => (
|
|
21
|
+
<DialogPrimitive.Overlay
|
|
22
|
+
ref={ref}
|
|
23
|
+
className={cn(
|
|
24
|
+
'fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
))
|
|
30
|
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
|
31
|
+
|
|
32
|
+
const DialogContent = React.forwardRef<
|
|
33
|
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
34
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
35
|
+
>(({ className, children, ...props }, ref) => {
|
|
36
|
+
const t = useT()
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<DialogPortal>
|
|
40
|
+
<DialogOverlay />
|
|
41
|
+
<DialogPrimitive.Content
|
|
42
|
+
ref={ref}
|
|
43
|
+
data-dialog-content=""
|
|
44
|
+
className={cn(
|
|
45
|
+
'fixed inset-x-0 bottom-0 z-50 flex min-h-[50vh] max-h-[70vh] w-full translate-x-0 translate-y-0 flex-col gap-4 overflow-y-auto rounded-t-2xl border-t bg-card p-6 shadow-lg',
|
|
46
|
+
'sm:inset-auto sm:left-1/2 sm:top-1/2 sm:min-h-0 sm:h-auto sm:w-full sm:max-w-lg sm:max-h-[90vh] sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-xl sm:border',
|
|
47
|
+
'focus:outline-none data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
>
|
|
52
|
+
<DialogClose
|
|
53
|
+
className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
|
54
|
+
aria-label={t('ui.dialog.close.ariaLabel', 'Close')}
|
|
55
|
+
>
|
|
56
|
+
<X className="h-4 w-4" />
|
|
57
|
+
</DialogClose>
|
|
58
|
+
{children}
|
|
59
|
+
</DialogPrimitive.Content>
|
|
60
|
+
</DialogPortal>
|
|
61
|
+
)
|
|
62
|
+
})
|
|
63
|
+
DialogContent.displayName = DialogPrimitive.Content.displayName
|
|
64
|
+
|
|
65
|
+
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
66
|
+
<div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...props} />
|
|
67
|
+
)
|
|
68
|
+
DialogHeader.displayName = 'DialogHeader'
|
|
69
|
+
|
|
70
|
+
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
71
|
+
<div className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)} {...props} />
|
|
72
|
+
)
|
|
73
|
+
DialogFooter.displayName = 'DialogFooter'
|
|
74
|
+
|
|
75
|
+
const DialogTitle = React.forwardRef<
|
|
76
|
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
77
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
78
|
+
>(({ className, ...props }, ref) => (
|
|
79
|
+
<DialogPrimitive.Title
|
|
80
|
+
ref={ref}
|
|
81
|
+
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
))
|
|
85
|
+
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
|
86
|
+
|
|
87
|
+
const DialogDescription = React.forwardRef<
|
|
88
|
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
89
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
90
|
+
>(({ className, ...props }, ref) => (
|
|
91
|
+
<DialogPrimitive.Description
|
|
92
|
+
ref={ref}
|
|
93
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
))
|
|
97
|
+
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
Dialog,
|
|
101
|
+
DialogPortal,
|
|
102
|
+
DialogOverlay,
|
|
103
|
+
DialogTrigger,
|
|
104
|
+
DialogClose,
|
|
105
|
+
DialogContent,
|
|
106
|
+
DialogHeader,
|
|
107
|
+
DialogFooter,
|
|
108
|
+
DialogTitle,
|
|
109
|
+
DialogDescription,
|
|
110
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
3
|
+
|
|
4
|
+
type InputProps = React.ComponentPropsWithoutRef<'input'>
|
|
5
|
+
|
|
6
|
+
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
7
|
+
({ className, type = 'text', ...props }, ref) => (
|
|
8
|
+
<input
|
|
9
|
+
ref={ref}
|
|
10
|
+
type={type}
|
|
11
|
+
className={cn(
|
|
12
|
+
'flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
Input.displayName = 'Input'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import * as LabelPrimitive from '@radix-ui/react-label'
|
|
4
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
5
|
+
|
|
6
|
+
export function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
7
|
+
return (
|
|
8
|
+
<LabelPrimitive.Root
|
|
9
|
+
data-slot="label"
|
|
10
|
+
className={cn(
|
|
11
|
+
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
export function Separator({ className = '', orientation = 'horizontal' }: { className?: string; orientation?: 'horizontal' | 'vertical' }) {
|
|
4
|
+
const base = orientation === 'vertical' ? 'w-px h-full' : 'h-px w-full'
|
|
5
|
+
return <div role="separator" aria-orientation={orientation} className={`${base} bg-border ${className}`} />
|
|
6
|
+
}
|
|
7
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
|
+
|
|
5
|
+
export interface SpinnerProps {
|
|
6
|
+
className?: string
|
|
7
|
+
size?: 'sm' | 'md' | 'lg'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Spinner({ className = '', size = 'md' }: SpinnerProps) {
|
|
11
|
+
const t = useT()
|
|
12
|
+
const sizeClasses = {
|
|
13
|
+
sm: 'h-4 w-4',
|
|
14
|
+
md: 'h-6 w-6',
|
|
15
|
+
lg: 'h-8 w-8',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<span
|
|
20
|
+
className={`inline-flex items-center justify-center animate-spin rounded-full border-2 border-border border-t-foreground ${sizeClasses[size]} ${className}`}
|
|
21
|
+
role="status"
|
|
22
|
+
aria-label={t('ui.spinner.ariaLabel', 'Loading')}
|
|
23
|
+
>
|
|
24
|
+
<span className="sr-only">{t('ui.spinner.srOnly', 'Loading...')}</span>
|
|
25
|
+
</span>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
4
|
+
|
|
5
|
+
type SwitchProps = {
|
|
6
|
+
checked?: boolean
|
|
7
|
+
defaultChecked?: boolean
|
|
8
|
+
onCheckedChange?: (checked: boolean) => void
|
|
9
|
+
} & Omit<React.ComponentProps<'button'>, 'onChange'>
|
|
10
|
+
|
|
11
|
+
export const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
|
|
12
|
+
({ checked, defaultChecked, onCheckedChange, disabled, className, onClick, onKeyDown, ...props }, ref) => {
|
|
13
|
+
const isControlled = typeof checked === 'boolean'
|
|
14
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultChecked ?? false)
|
|
15
|
+
|
|
16
|
+
const currentChecked = isControlled ? checked : uncontrolledValue
|
|
17
|
+
|
|
18
|
+
const toggle = React.useCallback(
|
|
19
|
+
(event: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLButtonElement>) => {
|
|
20
|
+
event.preventDefault()
|
|
21
|
+
if (disabled) {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
const next = !currentChecked
|
|
25
|
+
if (!isControlled) {
|
|
26
|
+
setUncontrolledValue(next)
|
|
27
|
+
}
|
|
28
|
+
onCheckedChange?.(next)
|
|
29
|
+
},
|
|
30
|
+
[currentChecked, disabled, isControlled, onCheckedChange]
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
const handleClick = React.useCallback(
|
|
34
|
+
(event: React.MouseEvent<HTMLButtonElement>) => {
|
|
35
|
+
onClick?.(event)
|
|
36
|
+
if (!event.defaultPrevented) {
|
|
37
|
+
toggle(event)
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
[onClick, toggle]
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const handleKeyDown = React.useCallback(
|
|
44
|
+
(event: React.KeyboardEvent<HTMLButtonElement>) => {
|
|
45
|
+
onKeyDown?.(event)
|
|
46
|
+
if (event.defaultPrevented) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
if (event.key === ' ' || event.key === 'Enter') {
|
|
50
|
+
toggle(event)
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
[onKeyDown, toggle]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
role="switch"
|
|
60
|
+
aria-checked={currentChecked}
|
|
61
|
+
aria-disabled={disabled}
|
|
62
|
+
data-state={currentChecked ? 'checked' : 'unchecked'}
|
|
63
|
+
data-disabled={disabled ? '' : undefined}
|
|
64
|
+
ref={ref}
|
|
65
|
+
onClick={handleClick}
|
|
66
|
+
onKeyDown={handleKeyDown}
|
|
67
|
+
disabled={disabled}
|
|
68
|
+
className={cn(
|
|
69
|
+
'inline-flex h-6 w-11 items-center rounded-full border border-transparent bg-input/60 transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary',
|
|
70
|
+
className
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
<span
|
|
75
|
+
aria-hidden
|
|
76
|
+
className={cn(
|
|
77
|
+
'inline-block size-5 translate-x-0 rounded-full bg-background shadow transition-transform duration-200',
|
|
78
|
+
currentChecked ? 'translate-x-5' : 'translate-x-0'
|
|
79
|
+
)}
|
|
80
|
+
/>
|
|
81
|
+
</button>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
Switch.displayName = 'Switch'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
3
|
+
|
|
4
|
+
export function Table({ className, ...props }: React.HTMLAttributes<HTMLTableElement>) {
|
|
5
|
+
return <table className={cn('w-full text-sm', className)} {...props} />
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function TableHeader(props: React.HTMLAttributes<HTMLTableSectionElement>) {
|
|
9
|
+
return <thead {...props} />
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function TableBody(props: React.HTMLAttributes<HTMLTableSectionElement>) {
|
|
13
|
+
return <tbody {...props} />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function TableRow({ className, ...props }: React.HTMLAttributes<HTMLTableRowElement>) {
|
|
17
|
+
return <tr className={cn('border-b last:border-b-0', className)} {...props} />
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function TableHead({ className, ...props }: React.ThHTMLAttributes<HTMLTableCellElement>) {
|
|
21
|
+
return <th className={cn('text-left font-medium px-4 py-2 whitespace-nowrap text-muted-foreground', className)} {...props} />
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function TableCell({ className, ...props }: React.TdHTMLAttributes<HTMLTableCellElement>) {
|
|
25
|
+
return <td className={cn('px-4 py-2', className)} {...props} />
|
|
26
|
+
}
|
|
27
|
+
|