@open-mercato/ui 0.7.1-develop.7193.1.910a5b0a1e → 0.7.1-develop.7194.1.ab4fc81f82

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 (49) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/backend/AppShell.js +66 -7
  3. package/dist/backend/AppShell.js.map +2 -2
  4. package/dist/backend/CrudForm.js +14 -8
  5. package/dist/backend/CrudForm.js.map +2 -2
  6. package/dist/backend/DataTable.js +108 -11
  7. package/dist/backend/DataTable.js.map +2 -2
  8. package/dist/backend/forms/ActionsDropdown.js +35 -5
  9. package/dist/backend/forms/ActionsDropdown.js.map +2 -2
  10. package/dist/backend/forms/index.js.map +2 -2
  11. package/dist/backend/icons/lucideRegistry.generated.js +6 -0
  12. package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
  13. package/dist/backend/injection/spotIds.js +2 -0
  14. package/dist/backend/injection/spotIds.js.map +2 -2
  15. package/dist/backend/inputs/DurationInput.js +182 -0
  16. package/dist/backend/inputs/DurationInput.js.map +7 -0
  17. package/dist/backend/inputs/EventSelect.js.map +2 -2
  18. package/dist/backend/inputs/index.js +1 -0
  19. package/dist/backend/inputs/index.js.map +2 -2
  20. package/dist/backend/nav/navBadges.js +38 -0
  21. package/dist/backend/nav/navBadges.js.map +7 -0
  22. package/dist/primitives/segmented-control.js +4 -4
  23. package/dist/primitives/segmented-control.js.map +1 -1
  24. package/dist/primitives/sheet.js +21 -11
  25. package/dist/primitives/sheet.js.map +2 -2
  26. package/dist/primitives/textarea.js +54 -4
  27. package/dist/primitives/textarea.js.map +2 -2
  28. package/package.json +3 -3
  29. package/src/backend/AppShell.tsx +106 -2
  30. package/src/backend/CrudForm.tsx +28 -9
  31. package/src/backend/DataTable.tsx +176 -22
  32. package/src/backend/__tests__/CrudForm.density.test.tsx +113 -0
  33. package/src/backend/__tests__/DataTable.propBulkActions.test.tsx +200 -0
  34. package/src/backend/__tests__/DataTable.render.test.tsx +76 -0
  35. package/src/backend/__tests__/navBadges.test.ts +57 -0
  36. package/src/backend/forms/ActionsDropdown.tsx +73 -6
  37. package/src/backend/forms/index.ts +1 -1
  38. package/src/backend/icons/lucideRegistry.generated.tsx +6 -0
  39. package/src/backend/injection/spotIds.ts +8 -0
  40. package/src/backend/inputs/DurationInput.tsx +207 -0
  41. package/src/backend/inputs/EventSelect.tsx +2 -0
  42. package/src/backend/inputs/__tests__/DurationInput.test.tsx +220 -0
  43. package/src/backend/inputs/index.ts +1 -0
  44. package/src/backend/nav/navBadges.ts +61 -0
  45. package/src/primitives/__tests__/segmented-control.test.tsx +2 -2
  46. package/src/primitives/__tests__/textarea.test.tsx +50 -0
  47. package/src/primitives/segmented-control.tsx +7 -7
  48. package/src/primitives/sheet.tsx +7 -1
  49. package/src/primitives/textarea.tsx +77 -4
@@ -7,15 +7,65 @@ import { cn } from '@open-mercato/shared/lib/utils'
7
7
  const baseTextareaClass =
8
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
9
 
10
+ const DEFAULT_AUTO_RESIZE_MAX_ROWS = 12
11
+ const FALLBACK_LINE_HEIGHT_PX = 20
12
+
13
+ function readPixels(value: string): number {
14
+ const parsed = Number.parseFloat(value)
15
+ return Number.isFinite(parsed) ? parsed : 0
16
+ }
17
+
18
+ /**
19
+ * Grow a textarea to fit its content, capped at `maxRows` rendered lines.
20
+ * The cap is expressed in rows rather than pixels so it follows the element's
21
+ * own type scale instead of a magic height, and the element only scrolls once
22
+ * the cap is reached.
23
+ */
24
+ function applyAutoResize(element: HTMLTextAreaElement | null, maxRows: number): void {
25
+ if (!element) return
26
+ const computed = typeof window !== 'undefined' ? window.getComputedStyle(element) : null
27
+ const lineHeight = computed ? readPixels(computed.lineHeight) || FALLBACK_LINE_HEIGHT_PX : FALLBACK_LINE_HEIGHT_PX
28
+ const padding = computed ? readPixels(computed.paddingTop) + readPixels(computed.paddingBottom) : 0
29
+ const border = computed ? readPixels(computed.borderTopWidth) + readPixels(computed.borderBottomWidth) : 0
30
+ const maxContentHeight = lineHeight * Math.max(1, maxRows) + padding
31
+
32
+ element.style.height = 'auto'
33
+ const contentHeight = element.scrollHeight
34
+ const cappedHeight = Math.min(contentHeight, maxContentHeight)
35
+ element.style.height = `${cappedHeight + border}px`
36
+ element.style.overflowY = contentHeight > maxContentHeight ? 'auto' : 'hidden'
37
+ }
38
+
10
39
  export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
11
40
  /** Show character counter (`current/max`) below the textarea. Requires `maxLength`. */
12
41
  showCount?: boolean
13
42
  /** Optional className applied to the outer wrapper (when counter is shown). */
14
43
  wrapperClassName?: string
44
+ /**
45
+ * Grow the field to fit its content instead of scrolling inside a fixed box.
46
+ * Disables the manual resize handle — the height is owned by the content.
47
+ */
48
+ autoResize?: boolean
49
+ /** Rows to grow to before the field starts scrolling. Only used with `autoResize`. */
50
+ maxRows?: number
15
51
  }
16
52
 
17
53
  export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
18
- ({ className, showCount, wrapperClassName, value, defaultValue, maxLength, onChange, ...props }, ref) => {
54
+ (
55
+ {
56
+ className,
57
+ showCount,
58
+ wrapperClassName,
59
+ autoResize,
60
+ maxRows = DEFAULT_AUTO_RESIZE_MAX_ROWS,
61
+ value,
62
+ defaultValue,
63
+ maxLength,
64
+ onChange,
65
+ ...props
66
+ },
67
+ ref
68
+ ) => {
19
69
  const [internalValue, setInternalValue] = React.useState<string>(
20
70
  typeof defaultValue === 'string' ? defaultValue : typeof value === 'string' ? value : ''
21
71
  )
@@ -23,22 +73,45 @@ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
23
73
  const isControlled = value !== undefined
24
74
  const currentValue = isControlled ? String(value ?? '') : internalValue
25
75
 
76
+ const elementRef = React.useRef<HTMLTextAreaElement | null>(null)
77
+ const setElementRef = React.useCallback(
78
+ (node: HTMLTextAreaElement | null) => {
79
+ elementRef.current = node
80
+ if (typeof ref === 'function') ref(node)
81
+ else if (ref) (ref as React.MutableRefObject<HTMLTextAreaElement | null>).current = node
82
+ },
83
+ [ref]
84
+ )
85
+
86
+ React.useLayoutEffect(() => {
87
+ if (!autoResize) return
88
+ applyAutoResize(elementRef.current, maxRows)
89
+ }, [autoResize, maxRows, currentValue])
90
+
91
+ React.useEffect(() => {
92
+ if (!autoResize || typeof window === 'undefined') return
93
+ const onWindowResize = () => applyAutoResize(elementRef.current, maxRows)
94
+ window.addEventListener('resize', onWindowResize)
95
+ return () => window.removeEventListener('resize', onWindowResize)
96
+ }, [autoResize, maxRows])
97
+
26
98
  const handleChange = React.useCallback(
27
99
  (event: React.ChangeEvent<HTMLTextAreaElement>) => {
28
100
  if (!isControlled) setInternalValue(event.target.value)
101
+ if (autoResize) applyAutoResize(event.currentTarget, maxRows)
29
102
  onChange?.(event)
30
103
  },
31
- [isControlled, onChange]
104
+ [isControlled, onChange, autoResize, maxRows]
32
105
  )
33
106
 
34
107
  const textarea = (
35
108
  <textarea
36
- ref={ref}
109
+ ref={setElementRef}
37
110
  value={value}
38
111
  defaultValue={isControlled ? undefined : defaultValue}
39
112
  maxLength={maxLength}
40
113
  onChange={handleChange}
41
- className={cn(baseTextareaClass, className)}
114
+ className={cn(baseTextareaClass, autoResize && 'resize-none overflow-hidden', className)}
42
115
  {...props}
43
116
  />
44
117
  )