@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,128 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
5
|
+
|
|
6
|
+
type TabsContextValue = {
|
|
7
|
+
value: string
|
|
8
|
+
onValueChange: (value: string) => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const TabsContext = React.createContext<TabsContextValue | undefined>(undefined)
|
|
12
|
+
|
|
13
|
+
function useTabsContext() {
|
|
14
|
+
const context = React.useContext(TabsContext)
|
|
15
|
+
if (!context) {
|
|
16
|
+
throw new Error('Tabs components must be used within a Tabs provider')
|
|
17
|
+
}
|
|
18
|
+
return context
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type TabsProps = {
|
|
22
|
+
value?: string
|
|
23
|
+
defaultValue?: string
|
|
24
|
+
onValueChange?: (value: string) => void
|
|
25
|
+
children: React.ReactNode
|
|
26
|
+
className?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function Tabs({
|
|
30
|
+
value: controlledValue,
|
|
31
|
+
defaultValue,
|
|
32
|
+
onValueChange,
|
|
33
|
+
children,
|
|
34
|
+
className,
|
|
35
|
+
}: TabsProps) {
|
|
36
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue ?? '')
|
|
37
|
+
const isControlled = controlledValue !== undefined
|
|
38
|
+
const value = isControlled ? controlledValue : uncontrolledValue
|
|
39
|
+
|
|
40
|
+
const handleValueChange = React.useCallback(
|
|
41
|
+
(newValue: string) => {
|
|
42
|
+
if (!isControlled) {
|
|
43
|
+
setUncontrolledValue(newValue)
|
|
44
|
+
}
|
|
45
|
+
onValueChange?.(newValue)
|
|
46
|
+
},
|
|
47
|
+
[isControlled, onValueChange],
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<TabsContext.Provider value={{ value, onValueChange: handleValueChange }}>
|
|
52
|
+
<div className={className}>{children}</div>
|
|
53
|
+
</TabsContext.Provider>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type TabsListProps = {
|
|
58
|
+
children: React.ReactNode
|
|
59
|
+
className?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function TabsList({ children, className }: TabsListProps) {
|
|
63
|
+
return (
|
|
64
|
+
<div
|
|
65
|
+
className={cn(
|
|
66
|
+
'inline-flex h-9 items-center justify-start rounded-lg bg-muted p-1 text-muted-foreground',
|
|
67
|
+
className,
|
|
68
|
+
)}
|
|
69
|
+
role="tablist"
|
|
70
|
+
>
|
|
71
|
+
{children}
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type TabsTriggerProps = {
|
|
77
|
+
value: string
|
|
78
|
+
children: React.ReactNode
|
|
79
|
+
className?: string
|
|
80
|
+
disabled?: boolean
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function TabsTrigger({ value, children, className, disabled }: TabsTriggerProps) {
|
|
84
|
+
const { value: selectedValue, onValueChange } = useTabsContext()
|
|
85
|
+
const isSelected = selectedValue === value
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
role="tab"
|
|
91
|
+
aria-selected={isSelected}
|
|
92
|
+
disabled={disabled}
|
|
93
|
+
onClick={() => onValueChange(value)}
|
|
94
|
+
className={cn(
|
|
95
|
+
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
|
96
|
+
isSelected
|
|
97
|
+
? 'bg-background text-foreground shadow'
|
|
98
|
+
: 'hover:bg-background/50 hover:text-foreground',
|
|
99
|
+
className,
|
|
100
|
+
)}
|
|
101
|
+
>
|
|
102
|
+
{children}
|
|
103
|
+
</button>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type TabsContentProps = {
|
|
108
|
+
value: string
|
|
109
|
+
children: React.ReactNode
|
|
110
|
+
className?: string
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function TabsContent({ value, children, className }: TabsContentProps) {
|
|
114
|
+
const { value: selectedValue } = useTabsContext()
|
|
115
|
+
|
|
116
|
+
if (selectedValue !== value) {
|
|
117
|
+
return null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div
|
|
122
|
+
role="tabpanel"
|
|
123
|
+
className={cn('mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', className)}
|
|
124
|
+
>
|
|
125
|
+
{children}
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
4
|
+
|
|
5
|
+
type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>
|
|
6
|
+
|
|
7
|
+
export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
8
|
+
({ className, ...props }, ref) => (
|
|
9
|
+
<textarea
|
|
10
|
+
ref={ref}
|
|
11
|
+
className={cn(
|
|
12
|
+
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground 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
|
+
Textarea.displayName = 'Textarea'
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
|
|
5
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
6
|
+
|
|
7
|
+
export const TooltipProvider = TooltipPrimitive.Provider
|
|
8
|
+
|
|
9
|
+
export const Tooltip = TooltipPrimitive.Root
|
|
10
|
+
|
|
11
|
+
export const TooltipTrigger = TooltipPrimitive.Trigger
|
|
12
|
+
|
|
13
|
+
export const TooltipContent = React.forwardRef<
|
|
14
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
15
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
16
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
17
|
+
<TooltipPrimitive.Portal>
|
|
18
|
+
<TooltipPrimitive.Content
|
|
19
|
+
ref={ref}
|
|
20
|
+
sideOffset={sideOffset}
|
|
21
|
+
className={cn(
|
|
22
|
+
'z-50 overflow-hidden rounded-md bg-slate-900 px-3 py-1.5 text-xs text-slate-50 animate-in fade-in-0 zoom-in-95',
|
|
23
|
+
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
|
24
|
+
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',
|
|
25
|
+
'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
26
|
+
'max-w-xs break-words',
|
|
27
|
+
className
|
|
28
|
+
)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
</TooltipPrimitive.Portal>
|
|
32
|
+
))
|
|
33
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
|
34
|
+
|
|
35
|
+
export type TooltipProps = {
|
|
36
|
+
content: React.ReactNode
|
|
37
|
+
children: React.ReactNode
|
|
38
|
+
delayDuration?: number
|
|
39
|
+
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
40
|
+
align?: 'start' | 'center' | 'end'
|
|
41
|
+
open?: boolean
|
|
42
|
+
onOpenChange?: (open: boolean) => void
|
|
43
|
+
disabled?: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Simple tooltip wrapper component for common use cases.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* <SimpleTooltip content="Full text here">
|
|
51
|
+
* <span>Truncated...</span>
|
|
52
|
+
* </SimpleTooltip>
|
|
53
|
+
*/
|
|
54
|
+
export function SimpleTooltip({
|
|
55
|
+
content,
|
|
56
|
+
children,
|
|
57
|
+
delayDuration = 300,
|
|
58
|
+
side = 'top',
|
|
59
|
+
align = 'center',
|
|
60
|
+
open,
|
|
61
|
+
onOpenChange,
|
|
62
|
+
disabled = false,
|
|
63
|
+
}: TooltipProps) {
|
|
64
|
+
// If disabled or no content, just render children without tooltip
|
|
65
|
+
const isDisabled = disabled || !content
|
|
66
|
+
|
|
67
|
+
if (isDisabled) {
|
|
68
|
+
return <>{children}</>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Tooltip
|
|
73
|
+
open={open}
|
|
74
|
+
onOpenChange={onOpenChange}
|
|
75
|
+
delayDuration={delayDuration}
|
|
76
|
+
>
|
|
77
|
+
<TooltipTrigger asChild>
|
|
78
|
+
{children}
|
|
79
|
+
</TooltipTrigger>
|
|
80
|
+
<TooltipContent side={side} align={align}>
|
|
81
|
+
{content}
|
|
82
|
+
</TooltipContent>
|
|
83
|
+
</Tooltip>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { MutationCache, QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
4
|
+
import { redirectToSessionRefresh, redirectToForbiddenLogin, UnauthorizedError, ForbiddenError, apiFetch, setAuthRedirectConfig } from '../backend/utils/api'
|
|
5
|
+
|
|
6
|
+
// Ensure global fetch calls also respect our redirect-on-401/403 policy.
|
|
7
|
+
function ensureGlobalFetchInterception() {
|
|
8
|
+
if (typeof window === 'undefined') return
|
|
9
|
+
const w = window as any
|
|
10
|
+
if (w.__omFetchPatched) return
|
|
11
|
+
w.__omFetchPatched = true
|
|
12
|
+
w.__omOriginalFetch = window.fetch
|
|
13
|
+
window.fetch = ((input: RequestInfo | URL, init?: RequestInit) => apiFetch(input, init)) as any
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const client = new QueryClient({
|
|
17
|
+
queryCache: new QueryCache({
|
|
18
|
+
onError: (error) => {
|
|
19
|
+
if (error instanceof UnauthorizedError) redirectToSessionRefresh()
|
|
20
|
+
else if (error instanceof ForbiddenError) redirectToForbiddenLogin()
|
|
21
|
+
// As a fallback, try to detect common cases
|
|
22
|
+
else if ((error as any)?.status === 401) redirectToSessionRefresh()
|
|
23
|
+
else if ((error as any)?.status === 403) redirectToForbiddenLogin()
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
|
+
mutationCache: new MutationCache({
|
|
27
|
+
onError: (error) => {
|
|
28
|
+
if (error instanceof UnauthorizedError) redirectToSessionRefresh()
|
|
29
|
+
else if (error instanceof ForbiddenError) redirectToForbiddenLogin()
|
|
30
|
+
else if ((error as any)?.status === 401) redirectToSessionRefresh()
|
|
31
|
+
else if ((error as any)?.status === 403) redirectToForbiddenLogin()
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
type QueryProviderProps = { children: React.ReactNode; defaultForbiddenRoles?: string[] }
|
|
37
|
+
|
|
38
|
+
export function QueryProvider({ children, defaultForbiddenRoles }: QueryProviderProps) {
|
|
39
|
+
React.useEffect(() => {
|
|
40
|
+
ensureGlobalFetchInterception()
|
|
41
|
+
if (defaultForbiddenRoles && defaultForbiddenRoles.length) {
|
|
42
|
+
setAuthRedirectConfig({ defaultForbiddenRoles })
|
|
43
|
+
}
|
|
44
|
+
}, [])
|
|
45
|
+
return <QueryClientProvider client={client}>{children}</QueryClientProvider>
|
|
46
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
|
|
5
|
+
export type Theme = 'light' | 'dark' | 'system'
|
|
6
|
+
|
|
7
|
+
type ThemeContextValue = {
|
|
8
|
+
theme: Theme
|
|
9
|
+
resolvedTheme: 'light' | 'dark'
|
|
10
|
+
setTheme: (theme: Theme) => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const ThemeContext = React.createContext<ThemeContextValue | undefined>(undefined)
|
|
14
|
+
|
|
15
|
+
const THEME_STORAGE_KEY = 'om-theme'
|
|
16
|
+
|
|
17
|
+
function getSystemTheme(): 'light' | 'dark' {
|
|
18
|
+
if (typeof window === 'undefined') return 'light'
|
|
19
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getStoredTheme(): Theme {
|
|
23
|
+
if (typeof window === 'undefined') return 'system'
|
|
24
|
+
try {
|
|
25
|
+
const stored = localStorage.getItem(THEME_STORAGE_KEY)
|
|
26
|
+
if (stored === 'light' || stored === 'dark' || stored === 'system') {
|
|
27
|
+
return stored
|
|
28
|
+
}
|
|
29
|
+
} catch (error) {
|
|
30
|
+
// localStorage may be unavailable in private browsing, iframes, or restricted contexts
|
|
31
|
+
// Theme will default to system preference - this is expected graceful degradation
|
|
32
|
+
if (process.env.NODE_ENV === 'development') {
|
|
33
|
+
console.warn('[ThemeProvider] localStorage read failed:', error)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return 'system'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function applyTheme(resolvedTheme: 'light' | 'dark') {
|
|
40
|
+
const root = document.documentElement
|
|
41
|
+
if (resolvedTheme === 'dark') {
|
|
42
|
+
root.classList.add('dark')
|
|
43
|
+
} else {
|
|
44
|
+
root.classList.remove('dark')
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
49
|
+
const [theme, setThemeState] = React.useState<Theme>('system')
|
|
50
|
+
const [resolvedTheme, setResolvedTheme] = React.useState<'light' | 'dark'>('light')
|
|
51
|
+
const [mounted, setMounted] = React.useState(false)
|
|
52
|
+
|
|
53
|
+
// Initialize theme from localStorage on mount
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
const stored = getStoredTheme()
|
|
56
|
+
setThemeState(stored)
|
|
57
|
+
const resolved = stored === 'system' ? getSystemTheme() : stored
|
|
58
|
+
setResolvedTheme(resolved)
|
|
59
|
+
applyTheme(resolved)
|
|
60
|
+
setMounted(true)
|
|
61
|
+
}, [])
|
|
62
|
+
|
|
63
|
+
// Listen for system theme changes
|
|
64
|
+
React.useEffect(() => {
|
|
65
|
+
if (typeof window === 'undefined') return
|
|
66
|
+
|
|
67
|
+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
|
68
|
+
const handleChange = () => {
|
|
69
|
+
if (theme === 'system') {
|
|
70
|
+
const newResolved = getSystemTheme()
|
|
71
|
+
setResolvedTheme(newResolved)
|
|
72
|
+
applyTheme(newResolved)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
mediaQuery.addEventListener('change', handleChange)
|
|
77
|
+
return () => mediaQuery.removeEventListener('change', handleChange)
|
|
78
|
+
}, [theme])
|
|
79
|
+
|
|
80
|
+
const setTheme = React.useCallback((newTheme: Theme) => {
|
|
81
|
+
setThemeState(newTheme)
|
|
82
|
+
try {
|
|
83
|
+
localStorage.setItem(THEME_STORAGE_KEY, newTheme)
|
|
84
|
+
} catch (error) {
|
|
85
|
+
// localStorage may be unavailable - theme still works for this session, just won't persist
|
|
86
|
+
if (process.env.NODE_ENV === 'development') {
|
|
87
|
+
console.warn('[ThemeProvider] localStorage write failed:', error)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const resolved = newTheme === 'system' ? getSystemTheme() : newTheme
|
|
91
|
+
setResolvedTheme(resolved)
|
|
92
|
+
applyTheme(resolved)
|
|
93
|
+
}, [])
|
|
94
|
+
|
|
95
|
+
const value = React.useMemo(
|
|
96
|
+
() => ({ theme, resolvedTheme, setTheme }),
|
|
97
|
+
[theme, resolvedTheme, setTheme]
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
// Prevent flash of wrong theme during hydration
|
|
101
|
+
if (!mounted) {
|
|
102
|
+
return <>{children}</>
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function useTheme(): ThemeContextValue {
|
|
109
|
+
const context = React.useContext(ThemeContext)
|
|
110
|
+
if (context === undefined) {
|
|
111
|
+
// Return safe defaults when not in provider (e.g., server render)
|
|
112
|
+
return {
|
|
113
|
+
theme: 'system',
|
|
114
|
+
resolvedTheme: 'light',
|
|
115
|
+
setTheme: () => {},
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return context
|
|
119
|
+
}
|
|
120
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Moon, Sun } from 'lucide-react'
|
|
5
|
+
import { useTheme } from './ThemeProvider'
|
|
6
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
8
|
+
|
|
9
|
+
type ThemeToggleProps = {
|
|
10
|
+
className?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ThemeToggle({ className }: ThemeToggleProps) {
|
|
14
|
+
const { resolvedTheme, setTheme } = useTheme()
|
|
15
|
+
const t = useT()
|
|
16
|
+
const [mounted, setMounted] = React.useState(false)
|
|
17
|
+
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
setMounted(true)
|
|
20
|
+
}, [])
|
|
21
|
+
|
|
22
|
+
const isDark = resolvedTheme === 'dark'
|
|
23
|
+
const toggleLabel = t('common.theme.toggle', 'Toggle theme')
|
|
24
|
+
|
|
25
|
+
const toggle = () => {
|
|
26
|
+
setTheme(isDark ? 'light' : 'dark')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Render placeholder during SSR to prevent hydration mismatch
|
|
30
|
+
if (!mounted) {
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
className={cn(
|
|
34
|
+
'relative flex h-7 w-14 items-center rounded-full bg-muted p-1',
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
aria-hidden="true"
|
|
38
|
+
>
|
|
39
|
+
<div className="flex w-full justify-between px-1">
|
|
40
|
+
<Sun className="size-3.5 text-muted-foreground" />
|
|
41
|
+
<Moon className="size-3.5 text-muted-foreground" />
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
role="switch"
|
|
51
|
+
aria-checked={isDark}
|
|
52
|
+
aria-label={toggleLabel}
|
|
53
|
+
onClick={toggle}
|
|
54
|
+
className={cn(
|
|
55
|
+
'relative flex h-7 w-14 cursor-pointer items-center rounded-full p-1 transition-colors',
|
|
56
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
57
|
+
isDark ? 'bg-primary' : 'bg-muted',
|
|
58
|
+
className
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
|
+
{/* Sliding indicator */}
|
|
62
|
+
<span
|
|
63
|
+
className={cn(
|
|
64
|
+
'absolute size-5 rounded-full bg-background shadow-sm transition-transform duration-200 motion-reduce:transition-none',
|
|
65
|
+
isDark ? 'translate-x-7' : 'translate-x-0'
|
|
66
|
+
)}
|
|
67
|
+
/>
|
|
68
|
+
{/* Icons */}
|
|
69
|
+
<span className="relative flex w-full justify-between px-0.5">
|
|
70
|
+
<Sun
|
|
71
|
+
className={cn(
|
|
72
|
+
'size-3.5 transition-colors motion-reduce:transition-none',
|
|
73
|
+
isDark ? 'text-muted-foreground' : 'text-amber-500'
|
|
74
|
+
)}
|
|
75
|
+
/>
|
|
76
|
+
<Moon
|
|
77
|
+
className={cn(
|
|
78
|
+
'size-3.5 transition-colors motion-reduce:transition-none',
|
|
79
|
+
isDark ? 'text-primary-foreground' : 'text-muted-foreground'
|
|
80
|
+
)}
|
|
81
|
+
/>
|
|
82
|
+
</span>
|
|
83
|
+
<span className="sr-only">
|
|
84
|
+
{isDark ? t('common.theme.dark', 'Dark') : t('common.theme.light', 'Light')}
|
|
85
|
+
</span>
|
|
86
|
+
</button>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
declare module 'react-big-calendar' {
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
|
|
4
|
+
export type View = 'month' | 'week' | 'day' | 'agenda' | string
|
|
5
|
+
|
|
6
|
+
export type SlotInfo = {
|
|
7
|
+
start: Date
|
|
8
|
+
end: Date
|
|
9
|
+
slots: Date[]
|
|
10
|
+
action: 'select' | 'click' | 'doubleClick'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function dateFnsLocalizer(_config: Record<string, unknown>): unknown
|
|
14
|
+
|
|
15
|
+
export const Calendar: React.ComponentType<Record<string, unknown>>
|
|
16
|
+
}
|
package/tsconfig.json
ADDED