@open-mercato/ui 0.5.1-develop.2953.6647bb2c43 → 0.5.1-develop.2964.d5ac4a6ebb
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +8 -0
- package/dist/backend/CrudForm.js +57 -29
- package/dist/backend/CrudForm.js.map +2 -2
- package/dist/backend/DataTable.js +32 -14
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/FilterOverlay.js +23 -17
- package/dist/backend/FilterOverlay.js.map +2 -2
- package/dist/backend/JsonBuilder.js +32 -18
- package/dist/backend/JsonBuilder.js.map +2 -2
- package/dist/backend/columns/ColumnChooserPanel.js +12 -13
- package/dist/backend/columns/ColumnChooserPanel.js.map +2 -2
- package/dist/backend/custom-fields/FieldDefinitionsEditor.js +71 -62
- package/dist/backend/custom-fields/FieldDefinitionsEditor.js.map +2 -2
- package/dist/backend/date-range/DateRangeSelect.js +11 -10
- package/dist/backend/date-range/DateRangeSelect.js.map +2 -2
- package/dist/backend/date-range/InlineDateRangeSelect.js +10 -22
- package/dist/backend/date-range/InlineDateRangeSelect.js.map +2 -2
- package/dist/backend/detail/ActivitiesSection.js +20 -12
- package/dist/backend/detail/ActivitiesSection.js.map +2 -2
- package/dist/backend/detail/AddressEditor.js +24 -7
- package/dist/backend/detail/AddressEditor.js.map +2 -2
- package/dist/backend/detail/InlineEditors.js +12 -6
- package/dist/backend/detail/InlineEditors.js.map +2 -2
- package/dist/backend/detail/NotesSection.js +20 -14
- package/dist/backend/detail/NotesSection.js.map +2 -2
- package/dist/backend/filters/AdvancedFilterBuilder.js +52 -24
- package/dist/backend/filters/AdvancedFilterBuilder.js.map +2 -2
- package/dist/backend/injection/InjectedField.js +12 -7
- package/dist/backend/injection/InjectedField.js.map +2 -2
- package/dist/backend/inputs/ComboboxInput.js.map +2 -2
- package/dist/backend/inputs/EventSelect.js +22 -6
- package/dist/backend/inputs/EventSelect.js.map +2 -2
- package/dist/backend/inputs/PhoneNumberField.js +2 -2
- package/dist/backend/inputs/PhoneNumberField.js.map +2 -2
- package/dist/backend/inputs/TimeInput.js +9 -10
- package/dist/backend/inputs/TimeInput.js.map +2 -2
- package/dist/backend/messages/message-compose-form-groups.js +12 -7
- package/dist/backend/messages/message-compose-form-groups.js.map +2 -2
- package/dist/backend/messages/useMessageCompose.js +7 -1
- package/dist/backend/messages/useMessageCompose.js.map +2 -2
- package/dist/frontend/LanguageSwitcher.js +19 -14
- package/dist/frontend/LanguageSwitcher.js.map +2 -2
- package/dist/index.js +5 -0
- package/dist/index.js.map +2 -2
- package/dist/primitives/checkbox-field.js +17 -5
- package/dist/primitives/checkbox-field.js.map +2 -2
- package/dist/primitives/input.js +71 -14
- package/dist/primitives/input.js.map +2 -2
- package/dist/primitives/radio-field.js +74 -0
- package/dist/primitives/radio-field.js.map +7 -0
- package/dist/primitives/radio.js +37 -0
- package/dist/primitives/radio.js.map +7 -0
- package/dist/primitives/select.js +155 -0
- package/dist/primitives/select.js.map +7 -0
- package/dist/primitives/switch-field.js +76 -0
- package/dist/primitives/switch-field.js.map +7 -0
- package/dist/primitives/switch.js +17 -3
- package/dist/primitives/switch.js.map +2 -2
- package/dist/primitives/textarea.js +48 -12
- package/dist/primitives/textarea.js.map +2 -2
- package/dist/primitives/tooltip.js +44 -15
- package/dist/primitives/tooltip.js.map +2 -2
- package/package.json +5 -3
- package/src/backend/CrudForm.tsx +104 -37
- package/src/backend/DataTable.tsx +38 -20
- package/src/backend/FilterOverlay.tsx +35 -21
- package/src/backend/JsonBuilder.tsx +38 -20
- package/src/backend/__tests__/FieldDefinitionsEditor.test.tsx +23 -6
- package/src/backend/columns/ColumnChooserPanel.tsx +9 -10
- package/src/backend/custom-fields/FieldDefinitionsEditor.tsx +120 -87
- package/src/backend/date-range/DateRangeSelect.tsx +19 -12
- package/src/backend/date-range/InlineDateRangeSelect.tsx +16 -20
- package/src/backend/detail/ActivitiesSection.tsx +35 -23
- package/src/backend/detail/AddressEditor.tsx +30 -16
- package/src/backend/detail/InlineEditors.tsx +21 -11
- package/src/backend/detail/NotesSection.tsx +35 -25
- package/src/backend/filters/AdvancedFilterBuilder.tsx +60 -34
- package/src/backend/injection/InjectedField.tsx +21 -12
- package/src/backend/inputs/ComboboxInput.tsx +4 -0
- package/src/backend/inputs/EventSelect.tsx +30 -17
- package/src/backend/inputs/PhoneNumberField.tsx +2 -2
- package/src/backend/inputs/TimeInput.tsx +9 -10
- package/src/backend/messages/message-compose-form-groups.tsx +21 -12
- package/src/backend/messages/useMessageCompose.ts +20 -1
- package/src/frontend/LanguageSwitcher.tsx +20 -17
- package/src/index.ts +5 -0
- package/src/primitives/checkbox-field.tsx +10 -2
- package/src/primitives/input.tsx +73 -12
- package/src/primitives/radio-field.tsx +92 -0
- package/src/primitives/radio.tsx +42 -0
- package/src/primitives/select.tsx +200 -0
- package/src/primitives/switch-field.tsx +100 -0
- package/src/primitives/switch.tsx +17 -4
- package/src/primitives/textarea.tsx +67 -11
- package/src/primitives/tooltip.tsx +68 -24
|
@@ -2,24 +2,49 @@
|
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
5
|
+
import { cva } from "class-variance-authority";
|
|
5
6
|
import { cn } from "@open-mercato/shared/lib/utils";
|
|
6
7
|
const TooltipProvider = TooltipPrimitive.Provider;
|
|
7
8
|
const Tooltip = TooltipPrimitive.Root;
|
|
8
9
|
const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
9
|
-
const
|
|
10
|
+
const tooltipContentVariants = cva(
|
|
11
|
+
"z-tooltip overflow-hidden rounded-sm max-w-xs break-words shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
12
|
+
{
|
|
13
|
+
variants: {
|
|
14
|
+
variant: {
|
|
15
|
+
dark: "bg-foreground text-background",
|
|
16
|
+
light: "bg-popover text-popover-foreground border border-input"
|
|
17
|
+
},
|
|
18
|
+
size: {
|
|
19
|
+
sm: "px-1.5 py-0.5 text-xs leading-4",
|
|
20
|
+
default: "px-2 py-0.5 text-xs leading-4",
|
|
21
|
+
lg: "px-3 py-2 text-sm leading-5"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
variant: "dark",
|
|
26
|
+
size: "default"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
const TooltipContent = React.forwardRef(({ className, sideOffset = 4, variant, size, arrow = true, children, ...props }, ref) => /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
|
|
10
31
|
TooltipPrimitive.Content,
|
|
11
32
|
{
|
|
12
33
|
ref,
|
|
13
34
|
sideOffset,
|
|
14
|
-
className: cn(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
35
|
+
className: cn(tooltipContentVariants({ variant, size }), className),
|
|
36
|
+
...props,
|
|
37
|
+
children: [
|
|
38
|
+
children,
|
|
39
|
+
arrow ? /* @__PURE__ */ jsx(
|
|
40
|
+
TooltipPrimitive.Arrow,
|
|
41
|
+
{
|
|
42
|
+
width: 10,
|
|
43
|
+
height: 5,
|
|
44
|
+
className: cn(variant === "light" ? "fill-popover stroke-input" : "fill-foreground")
|
|
45
|
+
}
|
|
46
|
+
) : null
|
|
47
|
+
]
|
|
23
48
|
}
|
|
24
49
|
) }));
|
|
25
50
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
@@ -31,13 +56,16 @@ function SimpleTooltip({
|
|
|
31
56
|
align = "center",
|
|
32
57
|
open,
|
|
33
58
|
onOpenChange,
|
|
34
|
-
disabled = false
|
|
59
|
+
disabled = false,
|
|
60
|
+
variant,
|
|
61
|
+
size,
|
|
62
|
+
arrow
|
|
35
63
|
}) {
|
|
36
64
|
const isDisabled = disabled || !content;
|
|
37
65
|
if (isDisabled) {
|
|
38
66
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
39
67
|
}
|
|
40
|
-
return /* @__PURE__ */ jsxs(
|
|
68
|
+
return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration, children: /* @__PURE__ */ jsxs(
|
|
41
69
|
Tooltip,
|
|
42
70
|
{
|
|
43
71
|
open,
|
|
@@ -45,16 +73,17 @@ function SimpleTooltip({
|
|
|
45
73
|
delayDuration,
|
|
46
74
|
children: [
|
|
47
75
|
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children }),
|
|
48
|
-
/* @__PURE__ */ jsx(TooltipContent, { side, align, children: content })
|
|
76
|
+
/* @__PURE__ */ jsx(TooltipContent, { side, align, variant, size, arrow, children: content })
|
|
49
77
|
]
|
|
50
78
|
}
|
|
51
|
-
);
|
|
79
|
+
) });
|
|
52
80
|
}
|
|
53
81
|
export {
|
|
54
82
|
SimpleTooltip,
|
|
55
83
|
Tooltip,
|
|
56
84
|
TooltipContent,
|
|
57
85
|
TooltipProvider,
|
|
58
|
-
TooltipTrigger
|
|
86
|
+
TooltipTrigger,
|
|
87
|
+
tooltipContentVariants
|
|
59
88
|
};
|
|
60
89
|
//# sourceMappingURL=tooltip.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/primitives/tooltip.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport const TooltipProvider = TooltipPrimitive.Provider\n\nexport const Tooltip = TooltipPrimitive.Root\n\nexport const TooltipTrigger = TooltipPrimitive.Trigger\n\
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport const TooltipProvider = TooltipPrimitive.Provider\n\nexport const Tooltip = TooltipPrimitive.Root\n\nexport const TooltipTrigger = TooltipPrimitive.Trigger\n\nconst tooltipContentVariants = cva(\n 'z-tooltip overflow-hidden rounded-sm max-w-xs break-words shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n {\n variants: {\n variant: {\n dark: 'bg-foreground text-background',\n light: 'bg-popover text-popover-foreground border border-input',\n },\n size: {\n sm: 'px-1.5 py-0.5 text-xs leading-4',\n default: 'px-2 py-0.5 text-xs leading-4',\n lg: 'px-3 py-2 text-sm leading-5',\n },\n },\n defaultVariants: {\n variant: 'dark',\n size: 'default',\n },\n }\n)\n\nexport type TooltipContentProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> &\n VariantProps<typeof tooltipContentVariants> & {\n /** Show a small arrow pointing at the trigger. */\n arrow?: boolean\n }\n\nexport const TooltipContent = React.forwardRef<\n React.ElementRef<typeof TooltipPrimitive.Content>,\n TooltipContentProps\n>(({ className, sideOffset = 4, variant, size, arrow = true, children, ...props }, ref) => (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(tooltipContentVariants({ variant, size }), className)}\n {...props}\n >\n {children}\n {arrow ? (\n <TooltipPrimitive.Arrow\n width={10}\n height={5}\n className={cn(variant === 'light' ? 'fill-popover stroke-input' : 'fill-foreground')}\n />\n ) : null}\n </TooltipPrimitive.Content>\n </TooltipPrimitive.Portal>\n))\nTooltipContent.displayName = TooltipPrimitive.Content.displayName\n\nexport type TooltipProps = {\n content: React.ReactNode\n children: React.ReactNode\n delayDuration?: number\n side?: 'top' | 'right' | 'bottom' | 'left'\n align?: 'start' | 'center' | 'end'\n open?: boolean\n onOpenChange?: (open: boolean) => void\n disabled?: boolean\n variant?: 'dark' | 'light'\n size?: 'sm' | 'default' | 'lg'\n arrow?: boolean\n}\n\n/**\n * Simple tooltip wrapper component for common use cases.\n *\n * @example\n * <SimpleTooltip content=\"Full text here\">\n * <span>Truncated...</span>\n * </SimpleTooltip>\n *\n * @example with arrow + light variant\n * <SimpleTooltip content=\"Help text\" variant=\"light\" arrow>\n * <InfoIcon />\n * </SimpleTooltip>\n */\nexport function SimpleTooltip({\n content,\n children,\n delayDuration = 300,\n side = 'top',\n align = 'center',\n open,\n onOpenChange,\n disabled = false,\n variant,\n size,\n arrow,\n}: TooltipProps) {\n const isDisabled = disabled || !content\n\n if (isDisabled) {\n return <>{children}</>\n }\n\n return (\n <TooltipProvider delayDuration={delayDuration}>\n <Tooltip\n open={open}\n onOpenChange={onOpenChange}\n delayDuration={delayDuration}\n >\n <TooltipTrigger asChild>\n {children}\n </TooltipTrigger>\n <TooltipContent side={side} align={align} variant={variant} size={size} arrow={arrow}>\n {content}\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n )\n}\n\nexport { tooltipContentVariants }\n"],
|
|
5
|
+
"mappings": ";AA6CI,SA8DO,UAtDH,KARJ;AA3CJ,YAAY,WAAW;AACvB,YAAY,sBAAsB;AAClC,SAAS,WAA8B;AACvC,SAAS,UAAU;AAEZ,MAAM,kBAAkB,iBAAiB;AAEzC,MAAM,UAAU,iBAAiB;AAEjC,MAAM,iBAAiB,iBAAiB;AAE/C,MAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,MACA,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,SAAS;AAAA,QACT,IAAI;AAAA,MACN;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAQO,MAAM,iBAAiB,MAAM,WAGlC,CAAC,EAAE,WAAW,aAAa,GAAG,SAAS,MAAM,QAAQ,MAAM,UAAU,GAAG,MAAM,GAAG,QACjF,oBAAC,iBAAiB,QAAjB,EACC;AAAA,EAAC,iBAAiB;AAAA,EAAjB;AAAA,IACC;AAAA,IACA;AAAA,IACA,WAAW,GAAG,uBAAuB,EAAE,SAAS,KAAK,CAAC,GAAG,SAAS;AAAA,IACjE,GAAG;AAAA,IAEH;AAAA;AAAA,MACA,QACC;AAAA,QAAC,iBAAiB;AAAA,QAAjB;AAAA,UACC,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,WAAW,GAAG,YAAY,UAAU,8BAA8B,iBAAiB;AAAA;AAAA,MACrF,IACE;AAAA;AAAA;AACN,GACF,CACD;AACD,eAAe,cAAc,iBAAiB,QAAQ;AA6B/C,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB,OAAO;AAAA,EACP,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AACF,GAAiB;AACf,QAAM,aAAa,YAAY,CAAC;AAEhC,MAAI,YAAY;AACd,WAAO,gCAAG,UAAS;AAAA,EACrB;AAEA,SACE,oBAAC,mBAAgB,eACf;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MAEA;AAAA,4BAAC,kBAAe,SAAO,MACpB,UACH;AAAA,QACA,oBAAC,kBAAe,MAAY,OAAc,SAAkB,MAAY,OACrE,mBACH;AAAA;AAAA;AAAA,EACF,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ui",
|
|
3
|
-
"version": "0.5.1-develop.
|
|
3
|
+
"version": "0.5.1-develop.2964.d5ac4a6ebb",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -124,6 +124,8 @@
|
|
|
124
124
|
"@dnd-kit/sortable": "^10.0.0",
|
|
125
125
|
"@dnd-kit/utilities": "^3.2.2",
|
|
126
126
|
"@radix-ui/react-popover": "^1.1.6",
|
|
127
|
+
"@radix-ui/react-radio-group": "^1.2.3",
|
|
128
|
+
"@radix-ui/react-select": "^2.1.6",
|
|
127
129
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
128
130
|
"@tanstack/react-virtual": "^3.13.23",
|
|
129
131
|
"date-fns": "^4.1.0",
|
|
@@ -132,12 +134,12 @@
|
|
|
132
134
|
"recharts": "^3.8.1"
|
|
133
135
|
},
|
|
134
136
|
"peerDependencies": {
|
|
135
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
137
|
+
"@open-mercato/shared": "0.5.1-develop.2964.d5ac4a6ebb",
|
|
136
138
|
"react": ">=18.0.0",
|
|
137
139
|
"react-dom": ">=18.0.0"
|
|
138
140
|
},
|
|
139
141
|
"devDependencies": {
|
|
140
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
142
|
+
"@open-mercato/shared": "0.5.1-develop.2964.d5ac4a6ebb",
|
|
141
143
|
"@testing-library/dom": "^10.4.1",
|
|
142
144
|
"@testing-library/jest-dom": "^6.9.1",
|
|
143
145
|
"@testing-library/react": "^16.3.1",
|
package/src/backend/CrudForm.tsx
CHANGED
|
@@ -18,6 +18,15 @@ import { SortableContext, verticalListSortingStrategy, useSortable, arrayMove }
|
|
|
18
18
|
import { CSS } from '@dnd-kit/utilities'
|
|
19
19
|
import { DataLoader } from '../primitives/DataLoader'
|
|
20
20
|
import { Checkbox } from '../primitives/checkbox'
|
|
21
|
+
import { Input } from '../primitives/input'
|
|
22
|
+
import { Textarea } from '../primitives/textarea'
|
|
23
|
+
import {
|
|
24
|
+
Select,
|
|
25
|
+
SelectContent,
|
|
26
|
+
SelectItem,
|
|
27
|
+
SelectTrigger,
|
|
28
|
+
SelectValue,
|
|
29
|
+
} from '../primitives/select'
|
|
21
30
|
import { flash } from './FlashMessages'
|
|
22
31
|
import dynamic from 'next/dynamic'
|
|
23
32
|
import { FormHeader } from './forms/FormHeader'
|
|
@@ -96,6 +105,10 @@ import { sanitizeHtmlRichText, sanitizeRichTextHref, sanitizeRichTextPasteConten
|
|
|
96
105
|
|
|
97
106
|
// Stable empty options array to avoid creating a new [] every render
|
|
98
107
|
const EMPTY_OPTIONS: CrudFieldOption[] = []
|
|
108
|
+
// Sentinel for the optional-Select clear affordance. Radix Select forbids
|
|
109
|
+
// empty-string item values, so we use a stable non-empty token that maps to
|
|
110
|
+
// `undefined` in the change handler.
|
|
111
|
+
const SELECT_CLEAR_SENTINEL = '__crudform_select_clear__'
|
|
99
112
|
const FOCUSABLE_SELECTOR =
|
|
100
113
|
'[data-crud-focus-target], input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
101
114
|
const CRUDFORM_EXTENDED_EVENTS_ENABLED = parseBooleanWithDefault(
|
|
@@ -203,6 +216,12 @@ export type CrudBuiltinField = CrudFieldBase & {
|
|
|
203
216
|
suggestions?: string[]
|
|
204
217
|
// for combobox fields; allow custom values or restrict to suggestions only
|
|
205
218
|
allowCustomValues?: boolean
|
|
219
|
+
// for text/textarea fields; HTML maxLength + (textarea only) char counter when showCount=true
|
|
220
|
+
maxLength?: number
|
|
221
|
+
// for textarea fields; show character counter (requires maxLength)
|
|
222
|
+
showCount?: boolean
|
|
223
|
+
// for textarea fields; min height in rows
|
|
224
|
+
rows?: number
|
|
206
225
|
// for datetime/time fields
|
|
207
226
|
minuteStep?: number
|
|
208
227
|
minDate?: Date
|
|
@@ -1918,6 +1937,18 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
1918
1937
|
|
|
1919
1938
|
const form = document.getElementById(formId)
|
|
1920
1939
|
if (!form) return
|
|
1940
|
+
|
|
1941
|
+
// Don't steal focus if the user is already typing inside the form. The auto-focus
|
|
1942
|
+
// is meant for "submit failed, jump to first invalid field" — not for "user is
|
|
1943
|
+
// editing one error field and our focus jumps to a different remaining error
|
|
1944
|
+
// each keystroke as the errors object shrinks". Keystrokes would otherwise land
|
|
1945
|
+
// in the wrong input.
|
|
1946
|
+
const active = document.activeElement
|
|
1947
|
+
if (active instanceof HTMLElement && form.contains(active)) {
|
|
1948
|
+
lastErrorFieldRef.current = fieldId
|
|
1949
|
+
return
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1921
1952
|
const container = form.querySelector<HTMLElement>(`[data-crud-field-id="${fieldId}"]`)
|
|
1922
1953
|
const target =
|
|
1923
1954
|
container?.querySelector<HTMLElement>(FOCUSABLE_SELECTOR) ??
|
|
@@ -2681,22 +2712,25 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
2681
2712
|
<label className="text-xs uppercase tracking-wide text-muted-foreground">
|
|
2682
2713
|
{fieldsetSelectorLabel}
|
|
2683
2714
|
</label>
|
|
2684
|
-
<
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
onChange={(event) =>
|
|
2715
|
+
<Select
|
|
2716
|
+
value={entityLayout.activeFieldset || undefined}
|
|
2717
|
+
onValueChange={(value) =>
|
|
2688
2718
|
handleFieldsetSelectionChange(
|
|
2689
2719
|
entityLayout.entityId,
|
|
2690
|
-
|
|
2720
|
+
value || null,
|
|
2691
2721
|
)}
|
|
2692
2722
|
>
|
|
2693
|
-
<
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2723
|
+
<SelectTrigger className="w-auto min-w-[10rem]">
|
|
2724
|
+
<SelectValue placeholder={defaultFieldsetLabel} />
|
|
2725
|
+
</SelectTrigger>
|
|
2726
|
+
<SelectContent>
|
|
2727
|
+
{entityLayout.availableFieldsets.map((fs) => (
|
|
2728
|
+
<SelectItem key={fs.code} value={fs.code}>
|
|
2729
|
+
{fs.label}
|
|
2730
|
+
</SelectItem>
|
|
2731
|
+
))}
|
|
2732
|
+
</SelectContent>
|
|
2733
|
+
</Select>
|
|
2700
2734
|
<IconButton
|
|
2701
2735
|
variant="outline"
|
|
2702
2736
|
className="text-muted-foreground hover:text-foreground"
|
|
@@ -3250,9 +3284,8 @@ function RelationSelect({
|
|
|
3250
3284
|
|
|
3251
3285
|
return (
|
|
3252
3286
|
<div className="space-y-1">
|
|
3253
|
-
<
|
|
3287
|
+
<Input
|
|
3254
3288
|
ref={inputRef}
|
|
3255
|
-
className="w-full h-9 rounded border px-2 text-sm"
|
|
3256
3289
|
placeholder={placeholder || t('ui.forms.listbox.searchPlaceholder', 'Search...')}
|
|
3257
3290
|
value={query}
|
|
3258
3291
|
onChange={(e) => setQuery(e.target.value)}
|
|
@@ -3350,9 +3383,8 @@ function TextInput({
|
|
|
3350
3383
|
|
|
3351
3384
|
return (
|
|
3352
3385
|
<>
|
|
3353
|
-
<
|
|
3386
|
+
<Input
|
|
3354
3387
|
type={inputType}
|
|
3355
|
-
className="w-full h-9 rounded border px-2 text-sm"
|
|
3356
3388
|
placeholder={placeholder}
|
|
3357
3389
|
value={local}
|
|
3358
3390
|
onChange={handleChange}
|
|
@@ -3431,9 +3463,8 @@ function NumberInput({
|
|
|
3431
3463
|
}, [commitIfChanged])
|
|
3432
3464
|
|
|
3433
3465
|
return (
|
|
3434
|
-
<
|
|
3466
|
+
<Input
|
|
3435
3467
|
type="number"
|
|
3436
|
-
className="w-full h-9 rounded border px-2 text-sm"
|
|
3437
3468
|
placeholder={placeholder}
|
|
3438
3469
|
value={local}
|
|
3439
3470
|
onChange={handleChange}
|
|
@@ -3452,11 +3483,19 @@ function TextAreaInput({
|
|
|
3452
3483
|
onChange,
|
|
3453
3484
|
placeholder,
|
|
3454
3485
|
autoFocus,
|
|
3486
|
+
maxLength,
|
|
3487
|
+
showCount,
|
|
3488
|
+
rows,
|
|
3489
|
+
disabled,
|
|
3455
3490
|
}: {
|
|
3456
3491
|
value: string
|
|
3457
3492
|
onChange: (v: string) => void
|
|
3458
3493
|
placeholder?: string
|
|
3459
3494
|
autoFocus?: boolean
|
|
3495
|
+
maxLength?: number
|
|
3496
|
+
showCount?: boolean
|
|
3497
|
+
rows?: number
|
|
3498
|
+
disabled?: boolean
|
|
3460
3499
|
}) {
|
|
3461
3500
|
const [local, setLocal] = React.useState<string>(value)
|
|
3462
3501
|
const isFocusedRef = React.useRef(false)
|
|
@@ -3482,14 +3521,17 @@ function TextAreaInput({
|
|
|
3482
3521
|
}, [commitIfChanged])
|
|
3483
3522
|
|
|
3484
3523
|
return (
|
|
3485
|
-
<
|
|
3486
|
-
className="w-full rounded border px-2 py-2 min-h-[80px] sm:min-h-[120px] text-sm"
|
|
3524
|
+
<Textarea
|
|
3487
3525
|
placeholder={placeholder}
|
|
3488
3526
|
value={local}
|
|
3489
3527
|
onChange={handleChange}
|
|
3490
3528
|
onFocus={handleFocus}
|
|
3491
3529
|
onBlur={handleBlur}
|
|
3492
3530
|
autoFocus={autoFocus}
|
|
3531
|
+
maxLength={maxLength}
|
|
3532
|
+
showCount={showCount}
|
|
3533
|
+
rows={rows}
|
|
3534
|
+
disabled={disabled}
|
|
3493
3535
|
data-crud-focus-target=""
|
|
3494
3536
|
/>
|
|
3495
3537
|
)
|
|
@@ -3791,8 +3833,9 @@ const ListboxMultiSelect = React.memo(function ListboxMultiSelect({
|
|
|
3791
3833
|
)
|
|
3792
3834
|
return (
|
|
3793
3835
|
<div className="w-full">
|
|
3794
|
-
<
|
|
3795
|
-
className="mb-2
|
|
3836
|
+
<Input
|
|
3837
|
+
className="mb-2"
|
|
3838
|
+
size="sm"
|
|
3796
3839
|
placeholder={searchPlaceholder}
|
|
3797
3840
|
value={query}
|
|
3798
3841
|
onChange={(e) => setQuery(e.target.value)}
|
|
@@ -3914,9 +3957,8 @@ const FieldControl = React.memo(function FieldControlImpl({
|
|
|
3914
3957
|
/>
|
|
3915
3958
|
)}
|
|
3916
3959
|
{field.type === 'date' && (
|
|
3917
|
-
<
|
|
3960
|
+
<Input
|
|
3918
3961
|
type="date"
|
|
3919
|
-
className="w-full h-9 rounded border px-2 text-sm"
|
|
3920
3962
|
value={typeof value === 'string' ? value : ''}
|
|
3921
3963
|
onChange={(e) => setValue(field.id, e.target.value || undefined)}
|
|
3922
3964
|
autoFocus={autoFocusField}
|
|
@@ -3925,9 +3967,8 @@ const FieldControl = React.memo(function FieldControlImpl({
|
|
|
3925
3967
|
/>
|
|
3926
3968
|
)}
|
|
3927
3969
|
{field.type === 'datetime-local' && (
|
|
3928
|
-
<
|
|
3970
|
+
<Input
|
|
3929
3971
|
type="datetime-local"
|
|
3930
|
-
className="w-full h-9 rounded border px-2 text-sm"
|
|
3931
3972
|
value={typeof value === 'string' ? value : ''}
|
|
3932
3973
|
onChange={(e) => setValue(field.id, e.target.value || undefined)}
|
|
3933
3974
|
autoFocus={autoFocusField}
|
|
@@ -3979,6 +4020,10 @@ const FieldControl = React.memo(function FieldControlImpl({
|
|
|
3979
4020
|
placeholder={placeholder}
|
|
3980
4021
|
onChange={(next) => fieldSetValue(next)}
|
|
3981
4022
|
autoFocus={autoFocusField}
|
|
4023
|
+
maxLength={builtin?.maxLength}
|
|
4024
|
+
showCount={builtin?.showCount}
|
|
4025
|
+
rows={builtin?.rows}
|
|
4026
|
+
disabled={disabled}
|
|
3982
4027
|
/>
|
|
3983
4028
|
)}
|
|
3984
4029
|
{field.type === 'richtext' && builtin?.editor === 'simple' && (
|
|
@@ -4042,8 +4087,13 @@ const FieldControl = React.memo(function FieldControlImpl({
|
|
|
4042
4087
|
</label>
|
|
4043
4088
|
)}
|
|
4044
4089
|
{field.type === 'select' && !builtin?.multiple && (
|
|
4045
|
-
<
|
|
4046
|
-
|
|
4090
|
+
<Select
|
|
4091
|
+
// Radix Select MUST be either always-controlled or always-uncontrolled.
|
|
4092
|
+
// Passing `value={undefined}` on first render and a string later trips
|
|
4093
|
+
// React's "uncontrolled → controlled" warning and breaks Radix's
|
|
4094
|
+
// internal state (dropdown flashes / selections no-op). Use empty
|
|
4095
|
+
// string for "no selection" instead — Radix treats it the same as
|
|
4096
|
+
// undefined for matching SelectItems but keeps the prop type stable.
|
|
4047
4097
|
value={
|
|
4048
4098
|
Array.isArray(value)
|
|
4049
4099
|
? String(value[0] ?? '')
|
|
@@ -4051,17 +4101,34 @@ const FieldControl = React.memo(function FieldControlImpl({
|
|
|
4051
4101
|
? ''
|
|
4052
4102
|
: String(value)
|
|
4053
4103
|
}
|
|
4054
|
-
|
|
4055
|
-
|
|
4104
|
+
onValueChange={(next) => {
|
|
4105
|
+
// Sentinel maps back to undefined so optional selects can be cleared.
|
|
4106
|
+
if (!next || next === SELECT_CLEAR_SENTINEL) {
|
|
4107
|
+
setValue(field.id, undefined)
|
|
4108
|
+
return
|
|
4109
|
+
}
|
|
4110
|
+
setValue(field.id, next)
|
|
4111
|
+
}}
|
|
4056
4112
|
disabled={disabled}
|
|
4057
4113
|
>
|
|
4058
|
-
<
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4114
|
+
<SelectTrigger data-crud-focus-target="">
|
|
4115
|
+
<SelectValue placeholder={t('ui.forms.select.emptyOption', '—')} />
|
|
4116
|
+
</SelectTrigger>
|
|
4117
|
+
<SelectContent>
|
|
4118
|
+
{!field.required && value != null && value !== '' && (
|
|
4119
|
+
<SelectItem value={SELECT_CLEAR_SENTINEL}>
|
|
4120
|
+
{t('ui.forms.select.clearOption', '— Clear —')}
|
|
4121
|
+
</SelectItem>
|
|
4122
|
+
)}
|
|
4123
|
+
{options
|
|
4124
|
+
.filter((opt) => opt.value !== '')
|
|
4125
|
+
.map((opt) => (
|
|
4126
|
+
<SelectItem key={opt.value} value={opt.value}>
|
|
4127
|
+
{opt.label}
|
|
4128
|
+
</SelectItem>
|
|
4129
|
+
))}
|
|
4130
|
+
</SelectContent>
|
|
4131
|
+
</Select>
|
|
4065
4132
|
)}
|
|
4066
4133
|
{field.type === 'select' && builtin?.multiple && builtin.listbox === true && (
|
|
4067
4134
|
<ListboxMultiSelect
|
|
@@ -7,6 +7,13 @@ import { RefreshCw, Loader2, SlidersHorizontal, MoreHorizontal, Circle, Filter,
|
|
|
7
7
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../primitives/table'
|
|
8
8
|
import { Button } from '../primitives/button'
|
|
9
9
|
import { Checkbox } from '../primitives/checkbox'
|
|
10
|
+
import {
|
|
11
|
+
Select,
|
|
12
|
+
SelectContent,
|
|
13
|
+
SelectItem,
|
|
14
|
+
SelectTrigger,
|
|
15
|
+
SelectValue,
|
|
16
|
+
} from '../primitives/select'
|
|
10
17
|
import { Spinner } from '../primitives/spinner'
|
|
11
18
|
import { TooltipProvider } from '../primitives/tooltip'
|
|
12
19
|
import { TruncatedCell } from './TruncatedCell'
|
|
@@ -1760,19 +1767,26 @@ export function DataTable<T>({
|
|
|
1760
1767
|
: []
|
|
1761
1768
|
const pageSizeSelect = pageSizeOptions.length > 0 && pagination.onPageSizeChange ? (
|
|
1762
1769
|
<span className="inline-flex items-center gap-1.5">
|
|
1763
|
-
<
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
pagination.onPageSizeChange!(Number(event.target.value))
|
|
1770
|
+
<Select
|
|
1771
|
+
value={String(pagination.pageSize)}
|
|
1772
|
+
onValueChange={(value) => {
|
|
1773
|
+
pagination.onPageSizeChange!(Number(value))
|
|
1768
1774
|
scrollTableIntoView()
|
|
1769
1775
|
}}
|
|
1770
|
-
aria-label={t('ui.dataTable.pagination.rowsPerPage', 'Rows per page')}
|
|
1771
1776
|
>
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1777
|
+
<SelectTrigger
|
|
1778
|
+
size="sm"
|
|
1779
|
+
className="min-w-[4rem]"
|
|
1780
|
+
aria-label={t('ui.dataTable.pagination.rowsPerPage', 'Rows per page')}
|
|
1781
|
+
>
|
|
1782
|
+
<SelectValue />
|
|
1783
|
+
</SelectTrigger>
|
|
1784
|
+
<SelectContent>
|
|
1785
|
+
{pageSizeOptions.map((size) => (
|
|
1786
|
+
<SelectItem key={size} value={String(size)}>{size}</SelectItem>
|
|
1787
|
+
))}
|
|
1788
|
+
</SelectContent>
|
|
1789
|
+
</Select>
|
|
1776
1790
|
<span className="text-muted-foreground">{t('ui.dataTable.pagination.perPage', 'per page')}</span>
|
|
1777
1791
|
</span>
|
|
1778
1792
|
) : null
|
|
@@ -2105,17 +2119,21 @@ export function DataTable<T>({
|
|
|
2105
2119
|
<div className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
2106
2120
|
{t('ui.dataTable.fieldset.label', 'Fieldset')}
|
|
2107
2121
|
</div>
|
|
2108
|
-
<
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
onChange={(event) => handleCustomFieldFilterFieldsetChange(event.target.value)}
|
|
2122
|
+
<Select
|
|
2123
|
+
value={activeCustomFieldFilterFieldset || undefined}
|
|
2124
|
+
onValueChange={(value) => handleCustomFieldFilterFieldsetChange(value)}
|
|
2112
2125
|
>
|
|
2113
|
-
|
|
2114
|
-
<
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2126
|
+
<SelectTrigger>
|
|
2127
|
+
<SelectValue />
|
|
2128
|
+
</SelectTrigger>
|
|
2129
|
+
<SelectContent>
|
|
2130
|
+
{(cfFilterFieldsetsByEntity[resolvedEntityIds[0]] ?? []).map((fieldset) => (
|
|
2131
|
+
<SelectItem key={fieldset.code} value={fieldset.code}>
|
|
2132
|
+
{fieldset.label}
|
|
2133
|
+
</SelectItem>
|
|
2134
|
+
))}
|
|
2135
|
+
</SelectContent>
|
|
2136
|
+
</Select>
|
|
2119
2137
|
</div>
|
|
2120
2138
|
)
|
|
2121
2139
|
: null
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
import * as React from 'react'
|
|
3
3
|
import { Button } from '../primitives/button'
|
|
4
4
|
import { Checkbox } from '../primitives/checkbox'
|
|
5
|
+
import {
|
|
6
|
+
Select,
|
|
7
|
+
SelectContent,
|
|
8
|
+
SelectItem,
|
|
9
|
+
SelectTrigger,
|
|
10
|
+
SelectValue,
|
|
11
|
+
} from '../primitives/select'
|
|
5
12
|
import { ComboboxInput } from './inputs/ComboboxInput'
|
|
6
13
|
import { TagsInput, type TagsInputOption } from './inputs/TagsInput'
|
|
7
14
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
@@ -266,16 +273,21 @@ export function FilterOverlay({
|
|
|
266
273
|
})}
|
|
267
274
|
</div>
|
|
268
275
|
) : (
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
onChange={(e) => setValue(f.id, e.target.value || undefined)}
|
|
276
|
+
<Select
|
|
277
|
+
value={values[f.id] || undefined}
|
|
278
|
+
onValueChange={(next) => setValue(f.id, next || undefined)}
|
|
273
279
|
>
|
|
274
|
-
<
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
<SelectTrigger size="lg">
|
|
281
|
+
<SelectValue placeholder={t('ui.forms.select.emptyOption', '—')} />
|
|
282
|
+
</SelectTrigger>
|
|
283
|
+
<SelectContent>
|
|
284
|
+
{(f.options || dynamicOptions[f.id] || [])
|
|
285
|
+
.filter((opt) => opt.value !== '')
|
|
286
|
+
.map((opt) => (
|
|
287
|
+
<SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>
|
|
288
|
+
))}
|
|
289
|
+
</SelectContent>
|
|
290
|
+
</Select>
|
|
279
291
|
)}
|
|
280
292
|
</div>
|
|
281
293
|
)}
|
|
@@ -354,20 +366,22 @@ export function FilterOverlay({
|
|
|
354
366
|
})()}
|
|
355
367
|
{f.type === 'checkbox' && (
|
|
356
368
|
<div>
|
|
357
|
-
<
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (
|
|
363
|
-
else if (v === 'true') setValue(f.id, true)
|
|
364
|
-
else if (v === 'false') setValue(f.id, false)
|
|
369
|
+
<Select
|
|
370
|
+
value={values[f.id] === true ? 'true' : values[f.id] === false ? 'false' : undefined}
|
|
371
|
+
onValueChange={(next) => {
|
|
372
|
+
if (!next) setValue(f.id, undefined)
|
|
373
|
+
else if (next === 'true') setValue(f.id, true)
|
|
374
|
+
else if (next === 'false') setValue(f.id, false)
|
|
365
375
|
}}
|
|
366
376
|
>
|
|
367
|
-
<
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
377
|
+
<SelectTrigger size="lg">
|
|
378
|
+
<SelectValue placeholder={t('ui.forms.select.emptyOption', '—')} />
|
|
379
|
+
</SelectTrigger>
|
|
380
|
+
<SelectContent>
|
|
381
|
+
<SelectItem value="true">{t('common.yes', 'Yes')}</SelectItem>
|
|
382
|
+
<SelectItem value="false">{t('common.no', 'No')}</SelectItem>
|
|
383
|
+
</SelectContent>
|
|
384
|
+
</Select>
|
|
371
385
|
</div>
|
|
372
386
|
)}
|
|
373
387
|
</div>
|