@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.
- package/.turbo/turbo-build.log +2 -2
- package/dist/backend/AppShell.js +66 -7
- package/dist/backend/AppShell.js.map +2 -2
- package/dist/backend/CrudForm.js +14 -8
- package/dist/backend/CrudForm.js.map +2 -2
- package/dist/backend/DataTable.js +108 -11
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/forms/ActionsDropdown.js +35 -5
- package/dist/backend/forms/ActionsDropdown.js.map +2 -2
- package/dist/backend/forms/index.js.map +2 -2
- package/dist/backend/icons/lucideRegistry.generated.js +6 -0
- package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
- package/dist/backend/injection/spotIds.js +2 -0
- package/dist/backend/injection/spotIds.js.map +2 -2
- package/dist/backend/inputs/DurationInput.js +182 -0
- package/dist/backend/inputs/DurationInput.js.map +7 -0
- package/dist/backend/inputs/EventSelect.js.map +2 -2
- package/dist/backend/inputs/index.js +1 -0
- package/dist/backend/inputs/index.js.map +2 -2
- package/dist/backend/nav/navBadges.js +38 -0
- package/dist/backend/nav/navBadges.js.map +7 -0
- package/dist/primitives/segmented-control.js +4 -4
- package/dist/primitives/segmented-control.js.map +1 -1
- package/dist/primitives/sheet.js +21 -11
- package/dist/primitives/sheet.js.map +2 -2
- package/dist/primitives/textarea.js +54 -4
- package/dist/primitives/textarea.js.map +2 -2
- package/package.json +3 -3
- package/src/backend/AppShell.tsx +106 -2
- package/src/backend/CrudForm.tsx +28 -9
- package/src/backend/DataTable.tsx +176 -22
- package/src/backend/__tests__/CrudForm.density.test.tsx +113 -0
- package/src/backend/__tests__/DataTable.propBulkActions.test.tsx +200 -0
- package/src/backend/__tests__/DataTable.render.test.tsx +76 -0
- package/src/backend/__tests__/navBadges.test.ts +57 -0
- package/src/backend/forms/ActionsDropdown.tsx +73 -6
- package/src/backend/forms/index.ts +1 -1
- package/src/backend/icons/lucideRegistry.generated.tsx +6 -0
- package/src/backend/injection/spotIds.ts +8 -0
- package/src/backend/inputs/DurationInput.tsx +207 -0
- package/src/backend/inputs/EventSelect.tsx +2 -0
- package/src/backend/inputs/__tests__/DurationInput.test.tsx +220 -0
- package/src/backend/inputs/index.ts +1 -0
- package/src/backend/nav/navBadges.ts +61 -0
- package/src/primitives/__tests__/segmented-control.test.tsx +2 -2
- package/src/primitives/__tests__/textarea.test.tsx +50 -0
- package/src/primitives/segmented-control.tsx +7 -7
- package/src/primitives/sheet.tsx +7 -1
- 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
|
-
(
|
|
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={
|
|
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
|
)
|