@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.
Files changed (96) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +8 -0
  3. package/dist/backend/CrudForm.js +57 -29
  4. package/dist/backend/CrudForm.js.map +2 -2
  5. package/dist/backend/DataTable.js +32 -14
  6. package/dist/backend/DataTable.js.map +2 -2
  7. package/dist/backend/FilterOverlay.js +23 -17
  8. package/dist/backend/FilterOverlay.js.map +2 -2
  9. package/dist/backend/JsonBuilder.js +32 -18
  10. package/dist/backend/JsonBuilder.js.map +2 -2
  11. package/dist/backend/columns/ColumnChooserPanel.js +12 -13
  12. package/dist/backend/columns/ColumnChooserPanel.js.map +2 -2
  13. package/dist/backend/custom-fields/FieldDefinitionsEditor.js +71 -62
  14. package/dist/backend/custom-fields/FieldDefinitionsEditor.js.map +2 -2
  15. package/dist/backend/date-range/DateRangeSelect.js +11 -10
  16. package/dist/backend/date-range/DateRangeSelect.js.map +2 -2
  17. package/dist/backend/date-range/InlineDateRangeSelect.js +10 -22
  18. package/dist/backend/date-range/InlineDateRangeSelect.js.map +2 -2
  19. package/dist/backend/detail/ActivitiesSection.js +20 -12
  20. package/dist/backend/detail/ActivitiesSection.js.map +2 -2
  21. package/dist/backend/detail/AddressEditor.js +24 -7
  22. package/dist/backend/detail/AddressEditor.js.map +2 -2
  23. package/dist/backend/detail/InlineEditors.js +12 -6
  24. package/dist/backend/detail/InlineEditors.js.map +2 -2
  25. package/dist/backend/detail/NotesSection.js +20 -14
  26. package/dist/backend/detail/NotesSection.js.map +2 -2
  27. package/dist/backend/filters/AdvancedFilterBuilder.js +52 -24
  28. package/dist/backend/filters/AdvancedFilterBuilder.js.map +2 -2
  29. package/dist/backend/injection/InjectedField.js +12 -7
  30. package/dist/backend/injection/InjectedField.js.map +2 -2
  31. package/dist/backend/inputs/ComboboxInput.js.map +2 -2
  32. package/dist/backend/inputs/EventSelect.js +22 -6
  33. package/dist/backend/inputs/EventSelect.js.map +2 -2
  34. package/dist/backend/inputs/PhoneNumberField.js +2 -2
  35. package/dist/backend/inputs/PhoneNumberField.js.map +2 -2
  36. package/dist/backend/inputs/TimeInput.js +9 -10
  37. package/dist/backend/inputs/TimeInput.js.map +2 -2
  38. package/dist/backend/messages/message-compose-form-groups.js +12 -7
  39. package/dist/backend/messages/message-compose-form-groups.js.map +2 -2
  40. package/dist/backend/messages/useMessageCompose.js +7 -1
  41. package/dist/backend/messages/useMessageCompose.js.map +2 -2
  42. package/dist/frontend/LanguageSwitcher.js +19 -14
  43. package/dist/frontend/LanguageSwitcher.js.map +2 -2
  44. package/dist/index.js +5 -0
  45. package/dist/index.js.map +2 -2
  46. package/dist/primitives/checkbox-field.js +17 -5
  47. package/dist/primitives/checkbox-field.js.map +2 -2
  48. package/dist/primitives/input.js +71 -14
  49. package/dist/primitives/input.js.map +2 -2
  50. package/dist/primitives/radio-field.js +74 -0
  51. package/dist/primitives/radio-field.js.map +7 -0
  52. package/dist/primitives/radio.js +37 -0
  53. package/dist/primitives/radio.js.map +7 -0
  54. package/dist/primitives/select.js +155 -0
  55. package/dist/primitives/select.js.map +7 -0
  56. package/dist/primitives/switch-field.js +76 -0
  57. package/dist/primitives/switch-field.js.map +7 -0
  58. package/dist/primitives/switch.js +17 -3
  59. package/dist/primitives/switch.js.map +2 -2
  60. package/dist/primitives/textarea.js +48 -12
  61. package/dist/primitives/textarea.js.map +2 -2
  62. package/dist/primitives/tooltip.js +44 -15
  63. package/dist/primitives/tooltip.js.map +2 -2
  64. package/package.json +5 -3
  65. package/src/backend/CrudForm.tsx +104 -37
  66. package/src/backend/DataTable.tsx +38 -20
  67. package/src/backend/FilterOverlay.tsx +35 -21
  68. package/src/backend/JsonBuilder.tsx +38 -20
  69. package/src/backend/__tests__/FieldDefinitionsEditor.test.tsx +23 -6
  70. package/src/backend/columns/ColumnChooserPanel.tsx +9 -10
  71. package/src/backend/custom-fields/FieldDefinitionsEditor.tsx +120 -87
  72. package/src/backend/date-range/DateRangeSelect.tsx +19 -12
  73. package/src/backend/date-range/InlineDateRangeSelect.tsx +16 -20
  74. package/src/backend/detail/ActivitiesSection.tsx +35 -23
  75. package/src/backend/detail/AddressEditor.tsx +30 -16
  76. package/src/backend/detail/InlineEditors.tsx +21 -11
  77. package/src/backend/detail/NotesSection.tsx +35 -25
  78. package/src/backend/filters/AdvancedFilterBuilder.tsx +60 -34
  79. package/src/backend/injection/InjectedField.tsx +21 -12
  80. package/src/backend/inputs/ComboboxInput.tsx +4 -0
  81. package/src/backend/inputs/EventSelect.tsx +30 -17
  82. package/src/backend/inputs/PhoneNumberField.tsx +2 -2
  83. package/src/backend/inputs/TimeInput.tsx +9 -10
  84. package/src/backend/messages/message-compose-form-groups.tsx +21 -12
  85. package/src/backend/messages/useMessageCompose.ts +20 -1
  86. package/src/frontend/LanguageSwitcher.tsx +20 -17
  87. package/src/index.ts +5 -0
  88. package/src/primitives/checkbox-field.tsx +10 -2
  89. package/src/primitives/input.tsx +73 -12
  90. package/src/primitives/radio-field.tsx +92 -0
  91. package/src/primitives/radio.tsx +42 -0
  92. package/src/primitives/select.tsx +200 -0
  93. package/src/primitives/switch-field.tsx +100 -0
  94. package/src/primitives/switch.tsx +17 -4
  95. package/src/primitives/textarea.tsx +67 -11
  96. package/src/primitives/tooltip.tsx +68 -24
@@ -0,0 +1,100 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { cn } from '@open-mercato/shared/lib/utils'
5
+ import { Switch } from './switch'
6
+
7
+ export type SwitchFieldProps = Omit<React.ComponentProps<typeof Switch>, 'id'> & {
8
+ id?: string
9
+ label: React.ReactNode
10
+ sublabel?: React.ReactNode
11
+ description?: React.ReactNode
12
+ badge?: React.ReactNode
13
+ link?: React.ReactNode
14
+ /** When true, renders the switch on the left of the label content. */
15
+ flip?: boolean
16
+ containerClassName?: string
17
+ contentClassName?: string
18
+ }
19
+
20
+ export const SwitchField = React.forwardRef<
21
+ React.ElementRef<typeof Switch>,
22
+ SwitchFieldProps
23
+ >(({
24
+ id: idProp,
25
+ label,
26
+ sublabel,
27
+ description,
28
+ badge,
29
+ link,
30
+ flip = false,
31
+ containerClassName,
32
+ contentClassName,
33
+ className,
34
+ disabled,
35
+ ...switchProps
36
+ }, ref) => {
37
+ // useId is SSR/HMR-stable; counter-based fallbacks drift on hydration.
38
+ const fallbackId = React.useId()
39
+ const id = idProp ?? fallbackId
40
+
41
+ const hasMultiLine = Boolean(description || sublabel || link)
42
+ const switchEl = (
43
+ <Switch
44
+ ref={ref}
45
+ id={id}
46
+ disabled={disabled}
47
+ className={cn(hasMultiLine && 'mt-0.5', className)}
48
+ {...switchProps}
49
+ />
50
+ )
51
+
52
+ const content = (
53
+ <div className={cn('flex flex-1 min-w-0 flex-col gap-2.5', contentClassName)}>
54
+ <div className="flex flex-col gap-1">
55
+ <div className="flex flex-wrap items-center gap-1">
56
+ <label
57
+ htmlFor={id}
58
+ className={cn(
59
+ 'text-sm font-medium leading-5 text-foreground select-none',
60
+ disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'
61
+ )}
62
+ >
63
+ {label}
64
+ </label>
65
+ {sublabel ? (
66
+ <span className="text-xs leading-4 text-muted-foreground select-none">{sublabel}</span>
67
+ ) : null}
68
+ {badge ? <span className="inline-flex shrink-0">{badge}</span> : null}
69
+ </div>
70
+ {description ? (
71
+ <p className="text-xs leading-4 text-muted-foreground">{description}</p>
72
+ ) : null}
73
+ </div>
74
+ {link ? <div className="flex">{link}</div> : null}
75
+ </div>
76
+ )
77
+
78
+ return (
79
+ <div
80
+ className={cn(
81
+ 'flex gap-3',
82
+ hasMultiLine ? 'items-start' : 'items-center',
83
+ containerClassName
84
+ )}
85
+ >
86
+ {flip ? (
87
+ <>
88
+ {switchEl}
89
+ {content}
90
+ </>
91
+ ) : (
92
+ <>
93
+ {content}
94
+ {switchEl}
95
+ </>
96
+ )}
97
+ </div>
98
+ )
99
+ })
100
+ SwitchField.displayName = 'SwitchField'
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from 'react'
2
4
 
3
5
  import { cn } from '@open-mercato/shared/lib/utils'
@@ -66,7 +68,9 @@ export const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
66
68
  onKeyDown={handleKeyDown}
67
69
  disabled={disabled}
68
70
  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',
71
+ 'group relative inline-flex h-5 w-8 shrink-0 cursor-pointer items-center justify-center rounded-full bg-transparent p-0',
72
+ 'focus-visible:outline-none focus-visible:shadow-focus',
73
+ 'disabled:cursor-not-allowed disabled:opacity-60',
70
74
  className
71
75
  )}
72
76
  {...props}
@@ -74,10 +78,19 @@ export const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
74
78
  <span
75
79
  aria-hidden
76
80
  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'
81
+ 'pointer-events-none flex h-4 w-7 items-center rounded-full px-0.5 transition-colors duration-150',
82
+ 'bg-border group-hover:bg-muted-foreground/30',
83
+ 'group-data-[state=checked]:bg-accent-indigo group-data-[state=checked]:group-hover:bg-accent-indigo/85'
79
84
  )}
80
- />
85
+ >
86
+ <span
87
+ className={cn(
88
+ 'block size-3 rounded-full bg-white transition-transform duration-200',
89
+ 'shadow-[0_1px_2px_rgba(10,13,20,0.10),0_0_0_0.5px_rgba(10,13,20,0.04)]',
90
+ currentChecked ? 'translate-x-3' : 'translate-x-0'
91
+ )}
92
+ />
93
+ </span>
81
94
  </button>
82
95
  )
83
96
  }
@@ -1,20 +1,76 @@
1
+ "use client"
2
+
1
3
  import * as React from 'react'
2
4
 
3
5
  import { cn } from '@open-mercato/shared/lib/utils'
4
6
 
5
- type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>
7
+ const baseTextareaClass =
8
+ 'flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-xs transition-colors placeholder:text-muted-foreground outline-none focus-visible:outline-none focus-visible:shadow-focus focus-visible:border-foreground hover:bg-muted/40 disabled:cursor-not-allowed disabled:bg-bg-disabled disabled:border-border-disabled disabled:shadow-none disabled:hover:bg-bg-disabled aria-[invalid=true]:border-destructive aria-[invalid=true]:focus-visible:border-destructive resize-y min-h-[80px]'
9
+
10
+ export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
11
+ /** Show character counter (`current/max`) below the textarea. Requires `maxLength`. */
12
+ showCount?: boolean
13
+ /** Optional className applied to the outer wrapper (when counter is shown). */
14
+ wrapperClassName?: string
15
+ }
6
16
 
7
17
  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
+ ({ className, showCount, wrapperClassName, value, defaultValue, maxLength, onChange, ...props }, ref) => {
19
+ const [internalValue, setInternalValue] = React.useState<string>(
20
+ typeof defaultValue === 'string' ? defaultValue : typeof value === 'string' ? value : ''
21
+ )
22
+
23
+ const isControlled = value !== undefined
24
+ const currentValue = isControlled ? String(value ?? '') : internalValue
25
+
26
+ const handleChange = React.useCallback(
27
+ (event: React.ChangeEvent<HTMLTextAreaElement>) => {
28
+ if (!isControlled) setInternalValue(event.target.value)
29
+ onChange?.(event)
30
+ },
31
+ [isControlled, onChange]
32
+ )
33
+
34
+ const textarea = (
35
+ <textarea
36
+ ref={ref}
37
+ value={value}
38
+ defaultValue={isControlled ? undefined : defaultValue}
39
+ maxLength={maxLength}
40
+ onChange={handleChange}
41
+ className={cn(baseTextareaClass, className)}
42
+ {...props}
43
+ />
44
+ )
45
+
46
+ if (!showCount) return textarea
47
+
48
+ const length = currentValue.length
49
+ const max = typeof maxLength === 'number' ? maxLength : undefined
50
+ const isError = max != null && length > max
51
+ const isDisabled = props.disabled
52
+
53
+ return (
54
+ <div className={cn('flex flex-col gap-1', wrapperClassName)}>
55
+ {textarea}
56
+ <div className="flex justify-end">
57
+ <span
58
+ className={cn(
59
+ 'text-overline uppercase',
60
+ isDisabled
61
+ ? 'text-text-disabled'
62
+ : isError
63
+ ? 'text-destructive'
64
+ : 'text-muted-foreground'
65
+ )}
66
+ aria-live="polite"
67
+ >
68
+ {max != null ? `${length}/${max}` : `${length}`}
69
+ </span>
70
+ </div>
71
+ </div>
72
+ )
73
+ }
18
74
  )
19
75
 
20
76
  Textarea.displayName = 'Textarea'
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import * as TooltipPrimitive from '@radix-ui/react-tooltip'
5
+ import { cva, type VariantProps } from 'class-variance-authority'
5
6
  import { cn } from '@open-mercato/shared/lib/utils'
6
7
 
7
8
  export const TooltipProvider = TooltipPrimitive.Provider
@@ -10,24 +11,53 @@ export const Tooltip = TooltipPrimitive.Root
10
11
 
11
12
  export const TooltipTrigger = TooltipPrimitive.Trigger
12
13
 
14
+ const tooltipContentVariants = cva(
15
+ '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',
16
+ {
17
+ variants: {
18
+ variant: {
19
+ dark: 'bg-foreground text-background',
20
+ light: 'bg-popover text-popover-foreground border border-input',
21
+ },
22
+ size: {
23
+ sm: 'px-1.5 py-0.5 text-xs leading-4',
24
+ default: 'px-2 py-0.5 text-xs leading-4',
25
+ lg: 'px-3 py-2 text-sm leading-5',
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ variant: 'dark',
30
+ size: 'default',
31
+ },
32
+ }
33
+ )
34
+
35
+ export type TooltipContentProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> &
36
+ VariantProps<typeof tooltipContentVariants> & {
37
+ /** Show a small arrow pointing at the trigger. */
38
+ arrow?: boolean
39
+ }
40
+
13
41
  export const TooltipContent = React.forwardRef<
14
42
  React.ElementRef<typeof TooltipPrimitive.Content>,
15
- React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
16
- >(({ className, sideOffset = 4, ...props }, ref) => (
43
+ TooltipContentProps
44
+ >(({ className, sideOffset = 4, variant, size, arrow = true, children, ...props }, ref) => (
17
45
  <TooltipPrimitive.Portal>
18
46
  <TooltipPrimitive.Content
19
47
  ref={ref}
20
48
  sideOffset={sideOffset}
21
- className={cn(
22
- 'z-tooltip 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
- )}
49
+ className={cn(tooltipContentVariants({ variant, size }), className)}
29
50
  {...props}
30
- />
51
+ >
52
+ {children}
53
+ {arrow ? (
54
+ <TooltipPrimitive.Arrow
55
+ width={10}
56
+ height={5}
57
+ className={cn(variant === 'light' ? 'fill-popover stroke-input' : 'fill-foreground')}
58
+ />
59
+ ) : null}
60
+ </TooltipPrimitive.Content>
31
61
  </TooltipPrimitive.Portal>
32
62
  ))
33
63
  TooltipContent.displayName = TooltipPrimitive.Content.displayName
@@ -41,6 +71,9 @@ export type TooltipProps = {
41
71
  open?: boolean
42
72
  onOpenChange?: (open: boolean) => void
43
73
  disabled?: boolean
74
+ variant?: 'dark' | 'light'
75
+ size?: 'sm' | 'default' | 'lg'
76
+ arrow?: boolean
44
77
  }
45
78
 
46
79
  /**
@@ -50,6 +83,11 @@ export type TooltipProps = {
50
83
  * <SimpleTooltip content="Full text here">
51
84
  * <span>Truncated...</span>
52
85
  * </SimpleTooltip>
86
+ *
87
+ * @example with arrow + light variant
88
+ * <SimpleTooltip content="Help text" variant="light" arrow>
89
+ * <InfoIcon />
90
+ * </SimpleTooltip>
53
91
  */
54
92
  export function SimpleTooltip({
55
93
  content,
@@ -60,8 +98,10 @@ export function SimpleTooltip({
60
98
  open,
61
99
  onOpenChange,
62
100
  disabled = false,
101
+ variant,
102
+ size,
103
+ arrow,
63
104
  }: TooltipProps) {
64
- // If disabled or no content, just render children without tooltip
65
105
  const isDisabled = disabled || !content
66
106
 
67
107
  if (isDisabled) {
@@ -69,17 +109,21 @@ export function SimpleTooltip({
69
109
  }
70
110
 
71
111
  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>
112
+ <TooltipProvider delayDuration={delayDuration}>
113
+ <Tooltip
114
+ open={open}
115
+ onOpenChange={onOpenChange}
116
+ delayDuration={delayDuration}
117
+ >
118
+ <TooltipTrigger asChild>
119
+ {children}
120
+ </TooltipTrigger>
121
+ <TooltipContent side={side} align={align} variant={variant} size={size} arrow={arrow}>
122
+ {content}
123
+ </TooltipContent>
124
+ </Tooltip>
125
+ </TooltipProvider>
84
126
  )
85
127
  }
128
+
129
+ export { tooltipContentVariants }