@dashforge/tw 0.1.0-beta → 0.2.1-beta
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/A11Y.md +130 -0
- package/CHANGELOG.md +231 -0
- package/dist/index.d.ts +1 -82
- package/dist/index.esm.js +1608 -11
- package/dist/src/components/AspectRatio/AspectRatio.d.ts +36 -0
- package/dist/src/components/AspectRatio/AspectRatio.d.ts.map +1 -0
- package/dist/src/components/AspectRatio/aspectRatio.types.d.ts +50 -0
- package/dist/src/components/AspectRatio/aspectRatio.types.d.ts.map +1 -0
- package/dist/src/components/Box/Box.d.ts +36 -0
- package/dist/src/components/Box/Box.d.ts.map +1 -0
- package/dist/src/components/Box/box.types.d.ts +54 -0
- package/dist/src/components/Box/box.types.d.ts.map +1 -0
- package/dist/src/components/Box/box.variants.d.ts +402 -0
- package/dist/src/components/Box/box.variants.d.ts.map +1 -0
- package/dist/src/components/Button/Button.d.ts.map +1 -1
- package/dist/src/components/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/src/components/Container/Container.d.ts +35 -0
- package/dist/src/components/Container/Container.d.ts.map +1 -0
- package/dist/src/components/Container/container.types.d.ts +41 -0
- package/dist/src/components/Container/container.types.d.ts.map +1 -0
- package/dist/src/components/Container/container.variants.d.ts +93 -0
- package/dist/src/components/Container/container.variants.d.ts.map +1 -0
- package/dist/src/components/Divider/Divider.d.ts +50 -0
- package/dist/src/components/Divider/Divider.d.ts.map +1 -0
- package/dist/src/components/Divider/divider.types.d.ts +47 -0
- package/dist/src/components/Divider/divider.types.d.ts.map +1 -0
- package/dist/src/components/Divider/divider.variants.d.ts +129 -0
- package/dist/src/components/Divider/divider.variants.d.ts.map +1 -0
- package/dist/src/components/Grid/Grid.d.ts +32 -0
- package/dist/src/components/Grid/Grid.d.ts.map +1 -0
- package/dist/src/components/Grid/grid.types.d.ts +103 -0
- package/dist/src/components/Grid/grid.types.d.ts.map +1 -0
- package/dist/src/components/Grid/grid.variants.d.ts +453 -0
- package/dist/src/components/Grid/grid.variants.d.ts.map +1 -0
- package/dist/src/components/NumberField/NumberField.d.ts.map +1 -1
- package/dist/src/components/RadioGroup/RadioGroup.d.ts.map +1 -1
- package/dist/src/components/Stack/Stack.d.ts +24 -0
- package/dist/src/components/Stack/Stack.d.ts.map +1 -0
- package/dist/src/components/Stack/stack.types.d.ts +61 -0
- package/dist/src/components/Stack/stack.types.d.ts.map +1 -0
- package/dist/src/components/Stack/stack.variants.d.ts +163 -0
- package/dist/src/components/Stack/stack.variants.d.ts.map +1 -0
- package/dist/src/components/Typography/Typography.d.ts +42 -0
- package/dist/src/components/Typography/Typography.d.ts.map +1 -0
- package/dist/src/components/Typography/typography.types.d.ts +61 -0
- package/dist/src/components/Typography/typography.types.d.ts.map +1 -0
- package/dist/src/components/Typography/typography.variants.d.ts +167 -0
- package/dist/src/components/Typography/typography.variants.d.ts.map +1 -0
- package/dist/src/components/VisuallyHidden/VisuallyHidden.d.ts +42 -0
- package/dist/src/components/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
- package/dist/src/components/VisuallyHidden/visuallyHidden.types.d.ts +42 -0
- package/dist/src/components/VisuallyHidden/visuallyHidden.types.d.ts.map +1 -0
- package/dist/src/index.d.ts +23 -1
- package/dist/src/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/components/AspectRatio/AspectRatio.test.tsx +173 -0
- package/src/components/AspectRatio/AspectRatio.tsx +73 -0
- package/src/components/AspectRatio/aspectRatio.types.ts +54 -0
- package/src/components/Box/Box.test.tsx +349 -0
- package/src/components/Box/Box.tsx +83 -0
- package/src/components/Box/box.types.ts +61 -0
- package/src/components/Box/box.variants.ts +211 -0
- package/src/components/Button/Button.tsx +11 -0
- package/src/components/Checkbox/Checkbox.tsx +26 -2
- package/src/components/Container/Container.test.tsx +209 -0
- package/src/components/Container/Container.tsx +74 -0
- package/src/components/Container/container.types.ts +45 -0
- package/src/components/Container/container.variants.ts +81 -0
- package/src/components/Divider/Divider.test.tsx +241 -0
- package/src/components/Divider/Divider.tsx +140 -0
- package/src/components/Divider/divider.types.ts +52 -0
- package/src/components/Divider/divider.variants.ts +115 -0
- package/src/components/Grid/Grid.test.tsx +321 -0
- package/src/components/Grid/Grid.tsx +148 -0
- package/src/components/Grid/grid.types.ts +112 -0
- package/src/components/Grid/grid.variants.ts +133 -0
- package/src/components/NumberField/NumberField.tsx +30 -2
- package/src/components/RadioGroup/RadioGroup.tsx +23 -1
- package/src/components/Stack/Stack.test.tsx +309 -0
- package/src/components/Stack/Stack.tsx +115 -0
- package/src/components/Stack/stack.types.ts +68 -0
- package/src/components/Stack/stack.variants.ts +80 -0
- package/src/components/Typography/Typography.test.tsx +230 -0
- package/src/components/Typography/Typography.tsx +112 -0
- package/src/components/Typography/typography.types.ts +65 -0
- package/src/components/Typography/typography.variants.ts +113 -0
- package/src/components/VisuallyHidden/VisuallyHidden.test.tsx +86 -0
- package/src/components/VisuallyHidden/VisuallyHidden.tsx +59 -0
- package/src/components/VisuallyHidden/visuallyHidden.types.ts +45 -0
- package/src/index.ts +50 -1
- package/LICENSE +0 -21
- package/dist/components/AppShell/AppShell.d.ts +0 -32
- package/dist/components/AppShell/AppShell.d.ts.map +0 -1
- package/dist/components/AppShell/appShell.types.d.ts +0 -62
- package/dist/components/AppShell/appShell.types.d.ts.map +0 -1
- package/dist/components/AppShell/appShell.variants.d.ts +0 -65
- package/dist/components/AppShell/appShell.variants.d.ts.map +0 -1
- package/dist/components/Autocomplete/Autocomplete.d.ts +0 -32
- package/dist/components/Autocomplete/Autocomplete.d.ts.map +0 -1
- package/dist/components/Autocomplete/autocomplete.types.d.ts +0 -214
- package/dist/components/Autocomplete/autocomplete.types.d.ts.map +0 -1
- package/dist/components/Autocomplete/autocomplete.variants.d.ts +0 -214
- package/dist/components/Autocomplete/autocomplete.variants.d.ts.map +0 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +0 -23
- package/dist/components/Breadcrumbs/Breadcrumbs.d.ts.map +0 -1
- package/dist/components/Breadcrumbs/breadcrumbs.types.d.ts +0 -98
- package/dist/components/Breadcrumbs/breadcrumbs.types.d.ts.map +0 -1
- package/dist/components/Breadcrumbs/breadcrumbs.variants.d.ts +0 -85
- package/dist/components/Breadcrumbs/breadcrumbs.variants.d.ts.map +0 -1
- package/dist/components/Button/Button.d.ts +0 -44
- package/dist/components/Button/Button.d.ts.map +0 -1
- package/dist/components/Button/button.types.d.ts +0 -66
- package/dist/components/Button/button.types.d.ts.map +0 -1
- package/dist/components/Button/button.variants.d.ts +0 -104
- package/dist/components/Button/button.variants.d.ts.map +0 -1
- package/dist/components/Checkbox/Checkbox.d.ts +0 -31
- package/dist/components/Checkbox/Checkbox.d.ts.map +0 -1
- package/dist/components/Checkbox/checkbox.types.d.ts +0 -86
- package/dist/components/Checkbox/checkbox.types.d.ts.map +0 -1
- package/dist/components/Checkbox/checkbox.variants.d.ts +0 -109
- package/dist/components/Checkbox/checkbox.variants.d.ts.map +0 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.d.ts +0 -38
- package/dist/components/ConfirmDialog/ConfirmDialog.d.ts.map +0 -1
- package/dist/components/ConfirmDialog/confirmDialog.types.d.ts +0 -80
- package/dist/components/ConfirmDialog/confirmDialog.types.d.ts.map +0 -1
- package/dist/components/ConfirmDialog/confirmDialog.variants.d.ts +0 -90
- package/dist/components/ConfirmDialog/confirmDialog.variants.d.ts.map +0 -1
- package/dist/components/DateTimePicker/DateTimePicker.d.ts +0 -49
- package/dist/components/DateTimePicker/DateTimePicker.d.ts.map +0 -1
- package/dist/components/DateTimePicker/dateTimePicker.types.d.ts +0 -95
- package/dist/components/DateTimePicker/dateTimePicker.types.d.ts.map +0 -1
- package/dist/components/DateTimePicker/dateTimePicker.variants.d.ts +0 -165
- package/dist/components/DateTimePicker/dateTimePicker.variants.d.ts.map +0 -1
- package/dist/components/LeftNav/LeftNav.d.ts +0 -34
- package/dist/components/LeftNav/LeftNav.d.ts.map +0 -1
- package/dist/components/LeftNav/leftNav.types.d.ts +0 -136
- package/dist/components/LeftNav/leftNav.types.d.ts.map +0 -1
- package/dist/components/LeftNav/leftNav.variants.d.ts +0 -143
- package/dist/components/LeftNav/leftNav.variants.d.ts.map +0 -1
- package/dist/components/NumberField/NumberField.d.ts +0 -18
- package/dist/components/NumberField/NumberField.d.ts.map +0 -1
- package/dist/components/NumberField/numberField.types.d.ts +0 -79
- package/dist/components/NumberField/numberField.types.d.ts.map +0 -1
- package/dist/components/NumberField/numberField.variants.d.ts +0 -169
- package/dist/components/NumberField/numberField.variants.d.ts.map +0 -1
- package/dist/components/OTPField/OTPField.d.ts +0 -25
- package/dist/components/OTPField/OTPField.d.ts.map +0 -1
- package/dist/components/OTPField/otpField.types.d.ts +0 -74
- package/dist/components/OTPField/otpField.types.d.ts.map +0 -1
- package/dist/components/OTPField/otpField.variants.d.ts +0 -105
- package/dist/components/OTPField/otpField.variants.d.ts.map +0 -1
- package/dist/components/RadioGroup/RadioGroup.d.ts +0 -35
- package/dist/components/RadioGroup/RadioGroup.d.ts.map +0 -1
- package/dist/components/RadioGroup/radioGroup.types.d.ts +0 -103
- package/dist/components/RadioGroup/radioGroup.types.d.ts.map +0 -1
- package/dist/components/RadioGroup/radioGroup.variants.d.ts +0 -166
- package/dist/components/RadioGroup/radioGroup.variants.d.ts.map +0 -1
- package/dist/components/Snackbar/Snackbar.d.ts +0 -37
- package/dist/components/Snackbar/Snackbar.d.ts.map +0 -1
- package/dist/components/Snackbar/snackbar.types.d.ts +0 -86
- package/dist/components/Snackbar/snackbar.types.d.ts.map +0 -1
- package/dist/components/Snackbar/snackbar.variants.d.ts +0 -153
- package/dist/components/Snackbar/snackbar.variants.d.ts.map +0 -1
- package/dist/components/Switch/Switch.d.ts +0 -15
- package/dist/components/Switch/Switch.d.ts.map +0 -1
- package/dist/components/Switch/switch.types.d.ts +0 -47
- package/dist/components/Switch/switch.types.d.ts.map +0 -1
- package/dist/components/Switch/switch.variants.d.ts +0 -105
- package/dist/components/Switch/switch.variants.d.ts.map +0 -1
- package/dist/components/TextField/TextField.d.ts +0 -25
- package/dist/components/TextField/TextField.d.ts.map +0 -1
- package/dist/components/TextField/textField.types.d.ts +0 -72
- package/dist/components/TextField/textField.types.d.ts.map +0 -1
- package/dist/components/TextField/textField.variants.d.ts +0 -160
- package/dist/components/TextField/textField.variants.d.ts.map +0 -1
- package/dist/components/Textarea/Textarea.d.ts +0 -16
- package/dist/components/Textarea/Textarea.d.ts.map +0 -1
- package/dist/components/Textarea/textarea.types.d.ts +0 -63
- package/dist/components/Textarea/textarea.types.d.ts.map +0 -1
- package/dist/components/Textarea/textarea.variants.d.ts +0 -197
- package/dist/components/Textarea/textarea.variants.d.ts.map +0 -1
- package/dist/components/TopBar/TopBar.d.ts +0 -33
- package/dist/components/TopBar/TopBar.d.ts.map +0 -1
- package/dist/components/TopBar/topBar.types.d.ts +0 -49
- package/dist/components/TopBar/topBar.types.d.ts.map +0 -1
- package/dist/components/TopBar/topBar.variants.d.ts +0 -79
- package/dist/components/TopBar/topBar.variants.d.ts.map +0 -1
- package/dist/components/_shared/resolveValidationState.d.ts +0 -42
- package/dist/components/_shared/resolveValidationState.d.ts.map +0 -1
- package/dist/hooks/useAccessState.d.ts +0 -37
- package/dist/hooks/useAccessState.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/tsconfig.lib.tsbuildinfo +0 -1
- package/dist/utils/cn.d.ts +0 -26
- package/dist/utils/cn.d.ts.map +0 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
2
|
-
import { useMemo, forwardRef, useContext, useId, useRef, useEffect, useCallback, useState, Fragment, createContext } from 'react';
|
|
2
|
+
import { useMemo, forwardRef, useContext, useId, useRef, useEffect, useCallback, useState, Fragment, createContext, Children, isValidElement, cloneElement } from 'react';
|
|
3
3
|
import { Slot } from '@radix-ui/react-slot';
|
|
4
4
|
import clsx from 'clsx';
|
|
5
5
|
import { twMerge } from 'tailwind-merge';
|
|
@@ -403,6 +403,13 @@ function _object_without_properties_loose(source, excluded) {
|
|
|
403
403
|
fullWidth,
|
|
404
404
|
loading
|
|
405
405
|
}), sx);
|
|
406
|
+
/*
|
|
407
|
+
* `aria-busy` announces the loading state to assistive tech.
|
|
408
|
+
* `disabled` alone hides the reason (perm-denied vs loading vs
|
|
409
|
+
* intrinsic), so SR users only hear "dimmed/inactive" without
|
|
410
|
+
* knowing why. Adding `aria-busy={true}` while loading distinguishes
|
|
411
|
+
* "wait for the action to finish" from "you can't do this".
|
|
412
|
+
*/ const ariaBusy = loading ? true : undefined;
|
|
406
413
|
// `asChild` renders through Radix Slot: the immediate child element
|
|
407
414
|
// gets the resolved className, no extra Button DOM is emitted.
|
|
408
415
|
if (asChild) {
|
|
@@ -411,6 +418,7 @@ function _object_without_properties_loose(source, excluded) {
|
|
|
411
418
|
className: classes,
|
|
412
419
|
"data-disabled": effectiveDisabled || undefined,
|
|
413
420
|
"aria-disabled": effectiveDisabled || undefined,
|
|
421
|
+
"aria-busy": ariaBusy,
|
|
414
422
|
children: children
|
|
415
423
|
});
|
|
416
424
|
}
|
|
@@ -418,6 +426,7 @@ function _object_without_properties_loose(source, excluded) {
|
|
|
418
426
|
ref: ref,
|
|
419
427
|
type: (_rest_type = rest.type) != null ? _rest_type : 'button',
|
|
420
428
|
disabled: effectiveDisabled,
|
|
429
|
+
"aria-busy": ariaBusy,
|
|
421
430
|
className: classes
|
|
422
431
|
}, rest, {
|
|
423
432
|
children: [
|
|
@@ -968,10 +977,9 @@ function _object_without_properties_loose(source, excluded) {
|
|
|
968
977
|
className: cn(v.control(), slotProps == null ? void 0 : (_slotProps_control = slotProps.control) == null ? void 0 : _slotProps_control.className),
|
|
969
978
|
children: jsx(RadixCheckbox.Indicator, {
|
|
970
979
|
className: cn(v.indicator(), slotProps == null ? void 0 : (_slotProps_indicator = slotProps.indicator) == null ? void 0 : _slotProps_indicator.className),
|
|
971
|
-
|
|
972
|
-
children: resolvedChecked === true ? jsx(CheckIcon, {
|
|
980
|
+
children: jsx(CheckIcon, {
|
|
973
981
|
className: "h-full w-full"
|
|
974
|
-
})
|
|
982
|
+
})
|
|
975
983
|
})
|
|
976
984
|
})),
|
|
977
985
|
jsxs("div", {
|
|
@@ -1430,9 +1438,13 @@ function _object_without_properties_loose(source, excluded) {
|
|
|
1430
1438
|
})
|
|
1431
1439
|
]
|
|
1432
1440
|
}),
|
|
1433
|
-
jsx(RadixRadioGroup.Root, {
|
|
1434
|
-
name: name
|
|
1435
|
-
|
|
1441
|
+
jsx(RadixRadioGroup.Root, _extends({
|
|
1442
|
+
name: name
|
|
1443
|
+
}, isFormMode || explicitValue !== undefined ? {
|
|
1444
|
+
value: resolvedValue
|
|
1445
|
+
} : {
|
|
1446
|
+
defaultValue: defaultValue != null ? defaultValue : undefined
|
|
1447
|
+
}, {
|
|
1436
1448
|
onValueChange: handleValueChange,
|
|
1437
1449
|
onBlur: handleBlur,
|
|
1438
1450
|
disabled: groupEffectiveDisabled,
|
|
@@ -1463,7 +1475,7 @@ function _object_without_properties_loose(source, excluded) {
|
|
|
1463
1475
|
]
|
|
1464
1476
|
}, option.value);
|
|
1465
1477
|
})
|
|
1466
|
-
}),
|
|
1478
|
+
})),
|
|
1467
1479
|
resolvedHelperText && jsx("p", {
|
|
1468
1480
|
id: helperId,
|
|
1469
1481
|
className: cn(resolvedError ? v.errorText() : v.helperText(), resolvedError ? slotProps == null ? void 0 : (_slotProps_errorText = slotProps.errorText) == null ? void 0 : _slotProps_errorText.className : slotProps == null ? void 0 : (_slotProps_helperText = slotProps.helperText) == null ? void 0 : _slotProps_helperText.className),
|
|
@@ -1905,6 +1917,18 @@ function _object_without_properties_loose(source, excluded) {
|
|
|
1905
1917
|
useDashFieldMeta(name);
|
|
1906
1918
|
const accessState = useAccessState(access);
|
|
1907
1919
|
const inputId = useId();
|
|
1920
|
+
/*
|
|
1921
|
+
* Local state for the STANDALONE UNCONTROLLED case (no bridge, no
|
|
1922
|
+
* `value` prop, only `defaultValue`). Mirrors OTPField. Without this,
|
|
1923
|
+
* `resolvedDisplayValue` would be a snapshot computed ONCE from
|
|
1924
|
+
* `defaultValue` and the controlled `<input value={...}>` would
|
|
1925
|
+
* snap user input back on every keystroke / stepper click — same
|
|
1926
|
+
* trap that hit Checkbox + RadioGroup in this package.
|
|
1927
|
+
*
|
|
1928
|
+
* In form mode the bridge owns state. In standalone CONTROLLED mode
|
|
1929
|
+
* (consumer passes `value`) the consumer owns state. Only this
|
|
1930
|
+
* branch needs the local hook.
|
|
1931
|
+
*/ const [uncontrolledValue, setUncontrolledValue] = useState(()=>formatForDisplay(defaultValue));
|
|
1908
1932
|
const helperId = `${inputId}-help`;
|
|
1909
1933
|
// StrictMode-safe unregister-on-unmount
|
|
1910
1934
|
const unregisterRef = useRef({
|
|
@@ -1942,7 +1966,7 @@ function _object_without_properties_loose(source, excluded) {
|
|
|
1942
1966
|
resolvedHelperText = validation.helperText;
|
|
1943
1967
|
resolvedDisplayValue = userValue !== undefined ? formatForDisplay(userValue) : formatForDisplay(bridge.getValue(name));
|
|
1944
1968
|
} else {
|
|
1945
|
-
resolvedDisplayValue = userValue !== undefined ? formatForDisplay(userValue) :
|
|
1969
|
+
resolvedDisplayValue = userValue !== undefined ? formatForDisplay(userValue) : uncontrolledValue;
|
|
1946
1970
|
}
|
|
1947
1971
|
const writeToBridge = (parsed)=>{
|
|
1948
1972
|
if (!isFormMode || !bridge) return;
|
|
@@ -1957,6 +1981,13 @@ function _object_without_properties_loose(source, excluded) {
|
|
|
1957
1981
|
// internal logic still sees the raw string change.
|
|
1958
1982
|
void registration.onChange(e);
|
|
1959
1983
|
}
|
|
1984
|
+
// Standalone uncontrolled mode: mirror the raw input string so the
|
|
1985
|
+
// controlled `<input value={...}>` reflects what the user typed.
|
|
1986
|
+
// Partial states (e.g. "-", "1.") are kept verbatim — `parseFromInput`
|
|
1987
|
+
// returns `undefined` for them so `writeToBridge` is a no-op above.
|
|
1988
|
+
if (!isFormMode && userValue === undefined) {
|
|
1989
|
+
setUncontrolledValue(e.target.value);
|
|
1990
|
+
}
|
|
1960
1991
|
userOnChange == null ? void 0 : userOnChange(e);
|
|
1961
1992
|
};
|
|
1962
1993
|
const handleBlur = (e)=>{
|
|
@@ -1972,6 +2003,11 @@ function _object_without_properties_loose(source, excluded) {
|
|
|
1972
2003
|
if (typeof min === 'number') next = Math.max(min, next);
|
|
1973
2004
|
if (typeof max === 'number') next = Math.min(max, next);
|
|
1974
2005
|
writeToBridge(next);
|
|
2006
|
+
// Standalone uncontrolled: also persist the new value to local
|
|
2007
|
+
// state so the visible display tracks the stepper click.
|
|
2008
|
+
if (!isFormMode && userValue === undefined) {
|
|
2009
|
+
setUncontrolledValue(formatForDisplay(next));
|
|
2010
|
+
}
|
|
1975
2011
|
};
|
|
1976
2012
|
const canIncrement = !effectiveDisabled && (typeof max !== 'number' || ((_parseFromInput = parseFromInput(resolvedDisplayValue)) != null ? _parseFromInput : -Infinity) < max);
|
|
1977
2013
|
const canDecrement = !effectiveDisabled && (typeof min !== 'number' || ((_parseFromInput1 = parseFromInput(resolvedDisplayValue)) != null ? _parseFromInput1 : Infinity) > min);
|
|
@@ -4926,6 +4962,1567 @@ const SnackbarContext = /*#__PURE__*/ createContext(null);
|
|
|
4926
4962
|
});
|
|
4927
4963
|
}
|
|
4928
4964
|
|
|
4965
|
+
/**
|
|
4966
|
+
* `typographyVariants` — the full type scale for the @dashforge/tw library.
|
|
4967
|
+
*
|
|
4968
|
+
* Mirrors the MUI Typography variant set (h1–h6 · subtitle1/2 · body1/2 ·
|
|
4969
|
+
* caption · overline) so the mental model carries over for developers
|
|
4970
|
+
* moving between the two ecosystems. Each variant baseline maps to a
|
|
4971
|
+
* Tailwind utility chain that resolves through the @dashforge/tw-tokens
|
|
4972
|
+
* scale (so the visual stays in sync with the rest of the system when the
|
|
4973
|
+
* token theme is patched).
|
|
4974
|
+
*
|
|
4975
|
+
* Variant axes are intentionally ORTHOGONAL — `variant` chooses the type
|
|
4976
|
+
* scale, `weight` overrides the variant's default weight (useful for "h2
|
|
4977
|
+
* but lighter"), `color` picks the intent, `align` picks the axis. They
|
|
4978
|
+
* never collide, so consumers can mix them freely.
|
|
4979
|
+
*
|
|
4980
|
+
* Two boolean flags (`truncate`, `noWrap`) encode the most common one-line
|
|
4981
|
+
* patterns; `gutterBottom` adds the conventional bottom margin used when a
|
|
4982
|
+
* heading precedes a paragraph block (mirror of MUI's same flag).
|
|
4983
|
+
*/ const typographyVariants = tv({
|
|
4984
|
+
base: 'text-inherit',
|
|
4985
|
+
variants: {
|
|
4986
|
+
/*
|
|
4987
|
+
* `variant` is the type-scale axis.
|
|
4988
|
+
*
|
|
4989
|
+
* The default font-weight is baked into each variant (headings come
|
|
4990
|
+
* with semibold/bold by default). Consumers override per-instance via
|
|
4991
|
+
* the `weight` axis below — when set, `weight` wins because it's
|
|
4992
|
+
* declared later in the cn() chain and tailwind-merge resolves the
|
|
4993
|
+
* last `font-*` to win.
|
|
4994
|
+
*/ variant: {
|
|
4995
|
+
h1: 'text-5xl font-bold leading-[1.05] tracking-[-0.025em]',
|
|
4996
|
+
h2: 'text-4xl font-bold leading-[1.1] tracking-[-0.022em]',
|
|
4997
|
+
h3: 'text-3xl font-semibold leading-[1.15] tracking-[-0.02em]',
|
|
4998
|
+
h4: 'text-2xl font-semibold leading-snug tracking-[-0.015em]',
|
|
4999
|
+
h5: 'text-xl font-semibold leading-snug',
|
|
5000
|
+
h6: 'text-lg font-semibold leading-normal',
|
|
5001
|
+
subtitle1: 'text-base font-medium leading-relaxed',
|
|
5002
|
+
subtitle2: 'text-sm font-medium leading-relaxed',
|
|
5003
|
+
body1: 'text-base font-normal leading-relaxed',
|
|
5004
|
+
body2: 'text-sm font-normal leading-relaxed',
|
|
5005
|
+
caption: 'text-xs font-normal leading-normal',
|
|
5006
|
+
overline: 'text-xs font-semibold uppercase tracking-[0.12em] leading-normal'
|
|
5007
|
+
},
|
|
5008
|
+
/*
|
|
5009
|
+
* `color` is the intent axis. Pairs with the @dashforge/tw-theme
|
|
5010
|
+
* reactive colour vars so the choice survives theme patches and dark
|
|
5011
|
+
* mode flips. `inherit` is the escape hatch — used inside a Box that
|
|
5012
|
+
* has set its own color (e.g. `<Box variant="solid" color="primary">`
|
|
5013
|
+
* paints white text).
|
|
5014
|
+
*/ color: {
|
|
5015
|
+
inherit: 'text-inherit',
|
|
5016
|
+
primary: 'text-primary-700 dark:text-primary-400',
|
|
5017
|
+
secondary: 'text-secondary-700 dark:text-secondary-400',
|
|
5018
|
+
success: 'text-success-700 dark:text-success-400',
|
|
5019
|
+
warning: 'text-warning-700 dark:text-warning-400',
|
|
5020
|
+
danger: 'text-danger-700 dark:text-danger-400',
|
|
5021
|
+
info: 'text-info-700 dark:text-info-400',
|
|
5022
|
+
neutral: 'text-neutral-900 dark:text-neutral-100',
|
|
5023
|
+
muted: 'text-neutral-600 dark:text-neutral-400'
|
|
5024
|
+
},
|
|
5025
|
+
/*
|
|
5026
|
+
* `weight` overrides the variant's default weight. When unset, the
|
|
5027
|
+
* variant's own weight wins. When set, this axis appears LATER in the
|
|
5028
|
+
* cn() chain so tailwind-merge resolves to this value.
|
|
5029
|
+
*/ weight: {
|
|
5030
|
+
normal: 'font-normal',
|
|
5031
|
+
medium: 'font-medium',
|
|
5032
|
+
semibold: 'font-semibold',
|
|
5033
|
+
bold: 'font-bold',
|
|
5034
|
+
extrabold: 'font-extrabold'
|
|
5035
|
+
},
|
|
5036
|
+
align: {
|
|
5037
|
+
left: 'text-left',
|
|
5038
|
+
center: 'text-center',
|
|
5039
|
+
right: 'text-right',
|
|
5040
|
+
justify: 'text-justify'
|
|
5041
|
+
},
|
|
5042
|
+
/*
|
|
5043
|
+
* `truncate` collapses to a one-line ellipsis. `noWrap` is the looser
|
|
5044
|
+
* sibling — keeps the text on one line but lets it overflow without
|
|
5045
|
+
* the `…`. Mutually-exclusive intent-wise; if both are passed,
|
|
5046
|
+
* `truncate` wins (later in the cn() chain).
|
|
5047
|
+
*/ truncate: {
|
|
5048
|
+
true: 'truncate'
|
|
5049
|
+
},
|
|
5050
|
+
noWrap: {
|
|
5051
|
+
true: 'whitespace-nowrap'
|
|
5052
|
+
},
|
|
5053
|
+
/*
|
|
5054
|
+
* `gutterBottom` adds the conventional bottom margin used when a
|
|
5055
|
+
* heading precedes a paragraph block. Mirror of MUI's same prop —
|
|
5056
|
+
* familiar to developers crossing from the MUI side.
|
|
5057
|
+
*/ gutterBottom: {
|
|
5058
|
+
true: 'mb-3'
|
|
5059
|
+
}
|
|
5060
|
+
},
|
|
5061
|
+
defaultVariants: {
|
|
5062
|
+
variant: 'body1',
|
|
5063
|
+
color: 'inherit',
|
|
5064
|
+
align: 'left'
|
|
5065
|
+
}
|
|
5066
|
+
});
|
|
5067
|
+
|
|
5068
|
+
/**
|
|
5069
|
+
* Default HTML tag per variant. Headings get their semantic level (h1→h1
|
|
5070
|
+
* etc.); subtitle/body get `<p>` (block, paragraph semantics);
|
|
5071
|
+
* caption/overline get `<span>` (inline, no implicit block break).
|
|
5072
|
+
*
|
|
5073
|
+
* Override per-instance via the `as` prop — useful when the semantic
|
|
5074
|
+
* heading level should differ from the visual scale (e.g. a hero
|
|
5075
|
+
* rendered as `<h2>` but visually styled `h1`).
|
|
5076
|
+
*/ const VARIANT_TO_TAG = {
|
|
5077
|
+
h1: 'h1',
|
|
5078
|
+
h2: 'h2',
|
|
5079
|
+
h3: 'h3',
|
|
5080
|
+
h4: 'h4',
|
|
5081
|
+
h5: 'h5',
|
|
5082
|
+
h6: 'h6',
|
|
5083
|
+
subtitle1: 'p',
|
|
5084
|
+
subtitle2: 'p',
|
|
5085
|
+
body1: 'p',
|
|
5086
|
+
body2: 'p',
|
|
5087
|
+
caption: 'span',
|
|
5088
|
+
overline: 'span'
|
|
5089
|
+
};
|
|
5090
|
+
/**
|
|
5091
|
+
* `<Typography>` — semantic typed text, the foundation of every readable
|
|
5092
|
+
* surface in @dashforge/tw.
|
|
5093
|
+
*
|
|
5094
|
+
* Why this exists:
|
|
5095
|
+
* Tailwind ships a typographic scale (`text-xl`, `font-bold`,
|
|
5096
|
+
* `leading-relaxed`) but leaves the SEMANTIC HTML tag and the
|
|
5097
|
+
* intent-coloured palette to the consumer. That's fine for one-off
|
|
5098
|
+
* marketing surfaces, but at app scale it means every `<h2>` and every
|
|
5099
|
+
* body paragraph re-derives its own utility chain — and they drift.
|
|
5100
|
+
*
|
|
5101
|
+
* Typography moves that decision into a typed prop set: the visual
|
|
5102
|
+
* scale, the intent colour, the alignment, the truncation all live in
|
|
5103
|
+
* `tailwind-variants` and resolve to the same utility chain everywhere
|
|
5104
|
+
* in the app. The default HTML tag is inferred from `variant` so the
|
|
5105
|
+
* semantic layer follows the visual layer; `as` and `asChild` are the
|
|
5106
|
+
* two escape hatches when you need something else.
|
|
5107
|
+
*
|
|
5108
|
+
* Layering:
|
|
5109
|
+
* • Sits BENEATH every component that renders text — `<Button>`'s
|
|
5110
|
+
* label, `<TextField>`'s helper text, MDX prose in our own docs.
|
|
5111
|
+
* • Composes ON TOP of `@dashforge/tw-tokens` colour scales (so
|
|
5112
|
+
* `color="primary"` paints `text-primary-700` in light, `-400` in dark,
|
|
5113
|
+
* reactive to `setMode()`).
|
|
5114
|
+
* • Polymorphic via Radix Slot — pairs cleanly with router `<Link>`,
|
|
5115
|
+
* `<button>`, or `<a>` without injecting an extra wrapper element.
|
|
5116
|
+
*
|
|
5117
|
+
* Polymorphism rules — `as` vs `asChild`:
|
|
5118
|
+
* • `as` swaps the rendered tag (we still render the element ourselves).
|
|
5119
|
+
* • `asChild` removes our element entirely — the single React child
|
|
5120
|
+
* becomes the rendered tag, with our className/ref merged onto it.
|
|
5121
|
+
* This is the Radix Slot pattern; use when you need a router Link
|
|
5122
|
+
* to style as a heading.
|
|
5123
|
+
* When BOTH are passed, `asChild` wins. We considered making this a
|
|
5124
|
+
* compile-time error via discriminated unions, but the API surface
|
|
5125
|
+
* already has 8 axes and adding one more dimension to the props type
|
|
5126
|
+
* would inflate IntelliSense suggestions for marginal benefit. The
|
|
5127
|
+
* runtime preference is documented; the test asserts it.
|
|
5128
|
+
*/ const Typography = /*#__PURE__*/ forwardRef(function Typography(props, ref) {
|
|
5129
|
+
var _ref;
|
|
5130
|
+
const { variant = 'body1', color, weight, align, truncate, noWrap, gutterBottom, as, asChild = false, sx, children } = props, rest = _object_without_properties_loose(props, [
|
|
5131
|
+
"variant",
|
|
5132
|
+
"color",
|
|
5133
|
+
"weight",
|
|
5134
|
+
"align",
|
|
5135
|
+
"truncate",
|
|
5136
|
+
"noWrap",
|
|
5137
|
+
"gutterBottom",
|
|
5138
|
+
"as",
|
|
5139
|
+
"asChild",
|
|
5140
|
+
"sx",
|
|
5141
|
+
"children"
|
|
5142
|
+
]);
|
|
5143
|
+
const classes = cn(typographyVariants({
|
|
5144
|
+
variant,
|
|
5145
|
+
color,
|
|
5146
|
+
weight,
|
|
5147
|
+
align,
|
|
5148
|
+
truncate,
|
|
5149
|
+
noWrap,
|
|
5150
|
+
gutterBottom
|
|
5151
|
+
}), sx);
|
|
5152
|
+
// asChild wins over `as` when both are passed (see component header).
|
|
5153
|
+
if (asChild) {
|
|
5154
|
+
return jsx(Slot, _extends({
|
|
5155
|
+
ref: ref,
|
|
5156
|
+
className: classes
|
|
5157
|
+
}, rest, {
|
|
5158
|
+
children: children
|
|
5159
|
+
}));
|
|
5160
|
+
}
|
|
5161
|
+
// Resolve the rendered tag: explicit `as` > variant default > fallback.
|
|
5162
|
+
const Tag = (_ref = as != null ? as : VARIANT_TO_TAG[variant]) != null ? _ref : 'span';
|
|
5163
|
+
return jsx(Tag, _extends({
|
|
5164
|
+
ref: ref,
|
|
5165
|
+
className: classes
|
|
5166
|
+
}, rest, {
|
|
5167
|
+
children: children
|
|
5168
|
+
}));
|
|
5169
|
+
});
|
|
5170
|
+
Typography.displayName = 'Typography';
|
|
5171
|
+
|
|
5172
|
+
/**
|
|
5173
|
+
* `boxVariants` — the surface primitive for @dashforge/tw.
|
|
5174
|
+
*
|
|
5175
|
+
* Architectural choice (planned with the user — F9 deep dive):
|
|
5176
|
+
*
|
|
5177
|
+
* Box replaces FOUR overlapping concepts from MUI in one component:
|
|
5178
|
+
* • Box (typed div)
|
|
5179
|
+
* • Paper (surface with elevation)
|
|
5180
|
+
* • Card (Paper specialisation)
|
|
5181
|
+
* • Surface (Joy UI's outlined / soft / solid / plain variants)
|
|
5182
|
+
*
|
|
5183
|
+
* The reason for the consolidation: in MUI you have to compose two or
|
|
5184
|
+
* three of these to express even basic intent ("an outlined card with
|
|
5185
|
+
* warning tone"). Here, one `<Box variant="outlined" color="warning">`
|
|
5186
|
+
* says exactly that.
|
|
5187
|
+
*
|
|
5188
|
+
* Variant taxonomy (5 axes, intentionally non-overlapping with Stack/Grid):
|
|
5189
|
+
*
|
|
5190
|
+
* • plain — bare div + padding + radius. The escape hatch.
|
|
5191
|
+
* • outlined — 1px border + subtle bg tint. The "card lite".
|
|
5192
|
+
* • elevated — bg surface + shadow scale (0-5). The "floating panel".
|
|
5193
|
+
* • soft — semi-transparent intent bg + intent text. The "callout".
|
|
5194
|
+
* • solid — solid intent bg + contrasting text. The "CTA banner".
|
|
5195
|
+
*
|
|
5196
|
+
* `color` applies to outlined/soft/solid (each gets the 7 intent
|
|
5197
|
+
* variants). `elevated` is color-agnostic (always neutral surface +
|
|
5198
|
+
* shadow scale). `plain` is everything-agnostic.
|
|
5199
|
+
*
|
|
5200
|
+
* Spacing axes (p/px/py/m/mx/my): mapped explicitly to the 11 token
|
|
5201
|
+
* steps from @dashforge/tw-tokens (0, 0.5, 1, 2, 3, 4, 6, 8, 12, 16, 24).
|
|
5202
|
+
* Tailwind JIT requires literal class strings — building them dynamically
|
|
5203
|
+
* with template literals would purge them. The verbosity below is the
|
|
5204
|
+
* cost of keeping the bundle CSS-pure and rebuild-free.
|
|
5205
|
+
*
|
|
5206
|
+
* What this does NOT do (deliberate, see component docs):
|
|
5207
|
+
* • No display / flex / grid props → use Stack or Grid
|
|
5208
|
+
* • No position / overflow / z-index → use `sx`
|
|
5209
|
+
* • No animation / transition → use `sx`
|
|
5210
|
+
*
|
|
5211
|
+
* The "Box is not flex" rule is the spine of the layout layer. Without
|
|
5212
|
+
* it, every `<div>` in an app gravitates back to Box and the surface
|
|
5213
|
+
* vs layout distinction collapses — exactly the failure mode this
|
|
5214
|
+
* primitive exists to prevent.
|
|
5215
|
+
*/ const boxVariants = tv({
|
|
5216
|
+
base: 'block',
|
|
5217
|
+
variants: {
|
|
5218
|
+
/*
|
|
5219
|
+
* Surface variant. Compound with `color` for outlined / soft / solid;
|
|
5220
|
+
* standalone for plain / elevated.
|
|
5221
|
+
*/ variant: {
|
|
5222
|
+
plain: '',
|
|
5223
|
+
outlined: 'border',
|
|
5224
|
+
elevated: 'bg-white dark:bg-neutral-900',
|
|
5225
|
+
soft: '',
|
|
5226
|
+
solid: ''
|
|
5227
|
+
},
|
|
5228
|
+
/*
|
|
5229
|
+
* Intent color. Only meaningful when variant is outlined / soft /
|
|
5230
|
+
* solid (resolved via compoundVariants below). For plain / elevated
|
|
5231
|
+
* this axis is ignored at the visual level — but kept in the type
|
|
5232
|
+
* so the prop is always available without conditional typing.
|
|
5233
|
+
*/ color: {
|
|
5234
|
+
primary: '',
|
|
5235
|
+
secondary: '',
|
|
5236
|
+
success: '',
|
|
5237
|
+
warning: '',
|
|
5238
|
+
danger: '',
|
|
5239
|
+
info: '',
|
|
5240
|
+
neutral: ''
|
|
5241
|
+
},
|
|
5242
|
+
/*
|
|
5243
|
+
* Shadow scale — relevant for `variant='elevated'`. We keep elevation
|
|
5244
|
+
* as a separate axis (not folded into `variant`) so consumers can
|
|
5245
|
+
* dial it up/down without changing the variant. Default `0` = no
|
|
5246
|
+
* shadow (consistent with MUI's elevation=0).
|
|
5247
|
+
*/ elevation: {
|
|
5248
|
+
0: 'shadow-none',
|
|
5249
|
+
1: 'shadow-sm',
|
|
5250
|
+
2: 'shadow',
|
|
5251
|
+
3: 'shadow-md',
|
|
5252
|
+
4: 'shadow-lg',
|
|
5253
|
+
5: 'shadow-xl'
|
|
5254
|
+
},
|
|
5255
|
+
rounded: {
|
|
5256
|
+
none: 'rounded-none',
|
|
5257
|
+
sm: 'rounded-sm',
|
|
5258
|
+
md: 'rounded-md',
|
|
5259
|
+
lg: 'rounded-lg',
|
|
5260
|
+
xl: 'rounded-xl',
|
|
5261
|
+
'2xl': 'rounded-2xl',
|
|
5262
|
+
full: 'rounded-full'
|
|
5263
|
+
},
|
|
5264
|
+
/*
|
|
5265
|
+
* Spacing — six axes (p/px/py/m/mx/my), 11 token steps each.
|
|
5266
|
+
* Literals enumerated explicitly so Tailwind's JIT scanner finds
|
|
5267
|
+
* every class. Token steps mirror @dashforge/tw-tokens spacing scale.
|
|
5268
|
+
*/ p: {
|
|
5269
|
+
0: 'p-0',
|
|
5270
|
+
'0.5': 'p-0.5',
|
|
5271
|
+
1: 'p-1',
|
|
5272
|
+
2: 'p-2',
|
|
5273
|
+
3: 'p-3',
|
|
5274
|
+
4: 'p-4',
|
|
5275
|
+
6: 'p-6',
|
|
5276
|
+
8: 'p-8',
|
|
5277
|
+
12: 'p-12',
|
|
5278
|
+
16: 'p-16',
|
|
5279
|
+
24: 'p-24'
|
|
5280
|
+
},
|
|
5281
|
+
px: {
|
|
5282
|
+
0: 'px-0',
|
|
5283
|
+
'0.5': 'px-0.5',
|
|
5284
|
+
1: 'px-1',
|
|
5285
|
+
2: 'px-2',
|
|
5286
|
+
3: 'px-3',
|
|
5287
|
+
4: 'px-4',
|
|
5288
|
+
6: 'px-6',
|
|
5289
|
+
8: 'px-8',
|
|
5290
|
+
12: 'px-12',
|
|
5291
|
+
16: 'px-16',
|
|
5292
|
+
24: 'px-24'
|
|
5293
|
+
},
|
|
5294
|
+
py: {
|
|
5295
|
+
0: 'py-0',
|
|
5296
|
+
'0.5': 'py-0.5',
|
|
5297
|
+
1: 'py-1',
|
|
5298
|
+
2: 'py-2',
|
|
5299
|
+
3: 'py-3',
|
|
5300
|
+
4: 'py-4',
|
|
5301
|
+
6: 'py-6',
|
|
5302
|
+
8: 'py-8',
|
|
5303
|
+
12: 'py-12',
|
|
5304
|
+
16: 'py-16',
|
|
5305
|
+
24: 'py-24'
|
|
5306
|
+
},
|
|
5307
|
+
m: {
|
|
5308
|
+
0: 'm-0',
|
|
5309
|
+
'0.5': 'm-0.5',
|
|
5310
|
+
1: 'm-1',
|
|
5311
|
+
2: 'm-2',
|
|
5312
|
+
3: 'm-3',
|
|
5313
|
+
4: 'm-4',
|
|
5314
|
+
6: 'm-6',
|
|
5315
|
+
8: 'm-8',
|
|
5316
|
+
12: 'm-12',
|
|
5317
|
+
16: 'm-16',
|
|
5318
|
+
24: 'm-24'
|
|
5319
|
+
},
|
|
5320
|
+
mx: {
|
|
5321
|
+
0: 'mx-0',
|
|
5322
|
+
'0.5': 'mx-0.5',
|
|
5323
|
+
1: 'mx-1',
|
|
5324
|
+
2: 'mx-2',
|
|
5325
|
+
3: 'mx-3',
|
|
5326
|
+
4: 'mx-4',
|
|
5327
|
+
6: 'mx-6',
|
|
5328
|
+
8: 'mx-8',
|
|
5329
|
+
12: 'mx-12',
|
|
5330
|
+
16: 'mx-16',
|
|
5331
|
+
24: 'mx-24'
|
|
5332
|
+
},
|
|
5333
|
+
my: {
|
|
5334
|
+
0: 'my-0',
|
|
5335
|
+
'0.5': 'my-0.5',
|
|
5336
|
+
1: 'my-1',
|
|
5337
|
+
2: 'my-2',
|
|
5338
|
+
3: 'my-3',
|
|
5339
|
+
4: 'my-4',
|
|
5340
|
+
6: 'my-6',
|
|
5341
|
+
8: 'my-8',
|
|
5342
|
+
12: 'my-12',
|
|
5343
|
+
16: 'my-16',
|
|
5344
|
+
24: 'my-24'
|
|
5345
|
+
},
|
|
5346
|
+
fullWidth: {
|
|
5347
|
+
true: 'w-full'
|
|
5348
|
+
},
|
|
5349
|
+
fullHeight: {
|
|
5350
|
+
true: 'h-full'
|
|
5351
|
+
}
|
|
5352
|
+
},
|
|
5353
|
+
/*
|
|
5354
|
+
* Compound variants — where surface × color get their actual visual.
|
|
5355
|
+
* Twenty-one entries: 7 (outlined) + 7 (soft) + 7 (solid).
|
|
5356
|
+
* `plain` and `elevated` don't appear here (no color contribution).
|
|
5357
|
+
*
|
|
5358
|
+
* Dark-mode pair is baked in: light-mode picks the 50-300 steps,
|
|
5359
|
+
* dark-mode picks the 800-950 steps — both reactive to setMode()
|
|
5360
|
+
* via the @dashforge/tw-theme CSS variables.
|
|
5361
|
+
*/ compoundVariants: [
|
|
5362
|
+
// ─── outlined × color ─────────────────────────────────────────────
|
|
5363
|
+
{
|
|
5364
|
+
variant: 'outlined',
|
|
5365
|
+
color: 'primary',
|
|
5366
|
+
class: 'border-primary-300 bg-primary-50/40 dark:border-primary-800 dark:bg-primary-950/30'
|
|
5367
|
+
},
|
|
5368
|
+
{
|
|
5369
|
+
variant: 'outlined',
|
|
5370
|
+
color: 'secondary',
|
|
5371
|
+
class: 'border-secondary-300 bg-secondary-50/40 dark:border-secondary-800 dark:bg-secondary-950/30'
|
|
5372
|
+
},
|
|
5373
|
+
{
|
|
5374
|
+
variant: 'outlined',
|
|
5375
|
+
color: 'success',
|
|
5376
|
+
class: 'border-success-300 bg-success-50/40 dark:border-success-800 dark:bg-success-950/30'
|
|
5377
|
+
},
|
|
5378
|
+
{
|
|
5379
|
+
variant: 'outlined',
|
|
5380
|
+
color: 'warning',
|
|
5381
|
+
class: 'border-warning-300 bg-warning-50/40 dark:border-warning-800 dark:bg-warning-950/30'
|
|
5382
|
+
},
|
|
5383
|
+
{
|
|
5384
|
+
variant: 'outlined',
|
|
5385
|
+
color: 'danger',
|
|
5386
|
+
class: 'border-danger-300 bg-danger-50/40 dark:border-danger-800 dark:bg-danger-950/30'
|
|
5387
|
+
},
|
|
5388
|
+
{
|
|
5389
|
+
variant: 'outlined',
|
|
5390
|
+
color: 'info',
|
|
5391
|
+
class: 'border-info-300 bg-info-50/40 dark:border-info-800 dark:bg-info-950/30'
|
|
5392
|
+
},
|
|
5393
|
+
{
|
|
5394
|
+
variant: 'outlined',
|
|
5395
|
+
color: 'neutral',
|
|
5396
|
+
class: 'border-neutral-200 bg-white dark:border-neutral-700 dark:bg-neutral-900'
|
|
5397
|
+
},
|
|
5398
|
+
// ─── soft × color ─────────────────────────────────────────────────
|
|
5399
|
+
{
|
|
5400
|
+
variant: 'soft',
|
|
5401
|
+
color: 'primary',
|
|
5402
|
+
class: 'bg-primary-100 text-primary-900 dark:bg-primary-950/50 dark:text-primary-100'
|
|
5403
|
+
},
|
|
5404
|
+
{
|
|
5405
|
+
variant: 'soft',
|
|
5406
|
+
color: 'secondary',
|
|
5407
|
+
class: 'bg-secondary-100 text-secondary-900 dark:bg-secondary-950/50 dark:text-secondary-100'
|
|
5408
|
+
},
|
|
5409
|
+
{
|
|
5410
|
+
variant: 'soft',
|
|
5411
|
+
color: 'success',
|
|
5412
|
+
class: 'bg-success-100 text-success-900 dark:bg-success-950/50 dark:text-success-100'
|
|
5413
|
+
},
|
|
5414
|
+
{
|
|
5415
|
+
variant: 'soft',
|
|
5416
|
+
color: 'warning',
|
|
5417
|
+
class: 'bg-warning-100 text-warning-900 dark:bg-warning-950/50 dark:text-warning-100'
|
|
5418
|
+
},
|
|
5419
|
+
{
|
|
5420
|
+
variant: 'soft',
|
|
5421
|
+
color: 'danger',
|
|
5422
|
+
class: 'bg-danger-100 text-danger-900 dark:bg-danger-950/50 dark:text-danger-100'
|
|
5423
|
+
},
|
|
5424
|
+
{
|
|
5425
|
+
variant: 'soft',
|
|
5426
|
+
color: 'info',
|
|
5427
|
+
class: 'bg-info-100 text-info-900 dark:bg-info-950/50 dark:text-info-100'
|
|
5428
|
+
},
|
|
5429
|
+
{
|
|
5430
|
+
variant: 'soft',
|
|
5431
|
+
color: 'neutral',
|
|
5432
|
+
class: 'bg-neutral-100 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100'
|
|
5433
|
+
},
|
|
5434
|
+
// ─── solid × color ────────────────────────────────────────────────
|
|
5435
|
+
{
|
|
5436
|
+
variant: 'solid',
|
|
5437
|
+
color: 'primary',
|
|
5438
|
+
class: 'bg-primary-600 text-white dark:bg-primary-500'
|
|
5439
|
+
},
|
|
5440
|
+
{
|
|
5441
|
+
variant: 'solid',
|
|
5442
|
+
color: 'secondary',
|
|
5443
|
+
class: 'bg-secondary-600 text-white dark:bg-secondary-500'
|
|
5444
|
+
},
|
|
5445
|
+
{
|
|
5446
|
+
variant: 'solid',
|
|
5447
|
+
color: 'success',
|
|
5448
|
+
class: 'bg-success-600 text-white dark:bg-success-500'
|
|
5449
|
+
},
|
|
5450
|
+
{
|
|
5451
|
+
variant: 'solid',
|
|
5452
|
+
color: 'warning',
|
|
5453
|
+
class: 'bg-warning-500 text-white dark:bg-warning-600'
|
|
5454
|
+
},
|
|
5455
|
+
{
|
|
5456
|
+
variant: 'solid',
|
|
5457
|
+
color: 'danger',
|
|
5458
|
+
class: 'bg-danger-600 text-white dark:bg-danger-500'
|
|
5459
|
+
},
|
|
5460
|
+
{
|
|
5461
|
+
variant: 'solid',
|
|
5462
|
+
color: 'info',
|
|
5463
|
+
class: 'bg-info-600 text-white dark:bg-info-500'
|
|
5464
|
+
},
|
|
5465
|
+
{
|
|
5466
|
+
variant: 'solid',
|
|
5467
|
+
color: 'neutral',
|
|
5468
|
+
class: 'bg-neutral-900 text-white dark:bg-neutral-100 dark:text-neutral-900'
|
|
5469
|
+
}
|
|
5470
|
+
],
|
|
5471
|
+
defaultVariants: {
|
|
5472
|
+
variant: 'plain',
|
|
5473
|
+
color: 'neutral',
|
|
5474
|
+
elevation: 0,
|
|
5475
|
+
rounded: 'none'
|
|
5476
|
+
}
|
|
5477
|
+
});
|
|
5478
|
+
|
|
5479
|
+
/**
|
|
5480
|
+
* `<Box>` — the surface primitive of @dashforge/tw.
|
|
5481
|
+
*
|
|
5482
|
+
* What it IS:
|
|
5483
|
+
* A polymorphic container with typed surface variants (plain /
|
|
5484
|
+
* outlined / elevated / soft / solid) × 7 intent colours, plus
|
|
5485
|
+
* spacing / sizing / rounded / elevation as enumerated token-scale
|
|
5486
|
+
* props. Replaces MUI's Box + Paper + Card + Surface in one component.
|
|
5487
|
+
*
|
|
5488
|
+
* What it is NOT (deliberate, enforced by the prop set):
|
|
5489
|
+
* • Not a flex container → use <Stack>
|
|
5490
|
+
* • Not a grid container → use <Grid>
|
|
5491
|
+
* • Not a paragraph → use <Typography>
|
|
5492
|
+
* • Not a button → use <Button>
|
|
5493
|
+
*
|
|
5494
|
+
* The "Box is not flex" rule is the spine of the layout layer. Without
|
|
5495
|
+
* it, every `<div>` in an app gravitates back to Box and the surface
|
|
5496
|
+
* vs layout distinction collapses. Box's job is the SURFACE around
|
|
5497
|
+
* content; Stack/Grid's job is the ARRANGEMENT of content. Two
|
|
5498
|
+
* primitives, two responsibilities.
|
|
5499
|
+
*
|
|
5500
|
+
* When BOTH `as` and `asChild` are passed, `asChild` wins. Same rule
|
|
5501
|
+
* as Typography — documented to avoid the dimension-bloat of a
|
|
5502
|
+
* discriminated union over an 11-axis props type.
|
|
5503
|
+
*
|
|
5504
|
+
* Layering:
|
|
5505
|
+
* • Sits BENEATH every visual chrome — every card, every panel,
|
|
5506
|
+
* every section background.
|
|
5507
|
+
* • Composes ON TOP of @dashforge/tw-tokens (color + spacing + radius
|
|
5508
|
+
* + shadow scales) via the dashforgePreset CSS variables.
|
|
5509
|
+
* • Pairs with Typography for text content, Stack/Grid for layout
|
|
5510
|
+
* children inside it.
|
|
5511
|
+
*/ const Box = /*#__PURE__*/ forwardRef(function Box(props, ref) {
|
|
5512
|
+
const { variant, color, elevation, rounded, p, px, py, m, mx, my, fullWidth, fullHeight, as, asChild = false, sx, children } = props, rest = _object_without_properties_loose(props, [
|
|
5513
|
+
"variant",
|
|
5514
|
+
"color",
|
|
5515
|
+
"elevation",
|
|
5516
|
+
"rounded",
|
|
5517
|
+
"p",
|
|
5518
|
+
"px",
|
|
5519
|
+
"py",
|
|
5520
|
+
"m",
|
|
5521
|
+
"mx",
|
|
5522
|
+
"my",
|
|
5523
|
+
"fullWidth",
|
|
5524
|
+
"fullHeight",
|
|
5525
|
+
"as",
|
|
5526
|
+
"asChild",
|
|
5527
|
+
"sx",
|
|
5528
|
+
"children"
|
|
5529
|
+
]);
|
|
5530
|
+
const classes = cn(boxVariants({
|
|
5531
|
+
variant,
|
|
5532
|
+
color,
|
|
5533
|
+
elevation,
|
|
5534
|
+
rounded,
|
|
5535
|
+
p,
|
|
5536
|
+
px,
|
|
5537
|
+
py,
|
|
5538
|
+
m,
|
|
5539
|
+
mx,
|
|
5540
|
+
my,
|
|
5541
|
+
fullWidth,
|
|
5542
|
+
fullHeight
|
|
5543
|
+
}), sx);
|
|
5544
|
+
if (asChild) {
|
|
5545
|
+
return jsx(Slot, _extends({
|
|
5546
|
+
ref: ref,
|
|
5547
|
+
className: classes
|
|
5548
|
+
}, rest, {
|
|
5549
|
+
children: children
|
|
5550
|
+
}));
|
|
5551
|
+
}
|
|
5552
|
+
const Tag = as != null ? as : 'div';
|
|
5553
|
+
return jsx(Tag, _extends({
|
|
5554
|
+
ref: ref,
|
|
5555
|
+
className: classes
|
|
5556
|
+
}, rest, {
|
|
5557
|
+
children: children
|
|
5558
|
+
}));
|
|
5559
|
+
});
|
|
5560
|
+
Box.displayName = 'Box';
|
|
5561
|
+
|
|
5562
|
+
/**
|
|
5563
|
+
* `stackVariants` — flex container 1D, the layout primitive of @dashforge/tw.
|
|
5564
|
+
*
|
|
5565
|
+
* Architectural role (planned with the user — F9 deep dive):
|
|
5566
|
+
*
|
|
5567
|
+
* Stack is the ONLY way to do flex in this library. Box is the
|
|
5568
|
+
* surface (border / bg / shadow); Stack is the arrangement (direction
|
|
5569
|
+
* / align / justify / gap). Two primitives, two responsibilities,
|
|
5570
|
+
* zero overlap.
|
|
5571
|
+
*
|
|
5572
|
+
* This rules out the MUI failure mode where every `<Box display="flex"
|
|
5573
|
+
* gap={2}>` quietly becomes the de-facto flex container, drowning
|
|
5574
|
+
* the surface vs layout distinction. Here, if you see `<Stack>` in
|
|
5575
|
+
* the JSX, you KNOW it's flex; if you see `<Box>`, you KNOW it's
|
|
5576
|
+
* not. The component name carries the intent.
|
|
5577
|
+
*
|
|
5578
|
+
* Axes:
|
|
5579
|
+
* • direction — row / col (+ reverse variants)
|
|
5580
|
+
* • align / justify — cross-axis / main-axis alignment
|
|
5581
|
+
* • gap — token-scale step (mirror Box spacing scale)
|
|
5582
|
+
* • wrap — flex-wrap
|
|
5583
|
+
* • divider — runtime-only (handled in Stack.tsx, not here)
|
|
5584
|
+
*
|
|
5585
|
+
* Sizing (`fullWidth`, `fullHeight`) is duplicated from Box because
|
|
5586
|
+
* Stack often plays the role of a full-width strip / full-height
|
|
5587
|
+
* column — re-typing `sx="w-full"` every time would be friction.
|
|
5588
|
+
*/ const stackVariants = tv({
|
|
5589
|
+
base: 'flex',
|
|
5590
|
+
variants: {
|
|
5591
|
+
direction: {
|
|
5592
|
+
row: 'flex-row',
|
|
5593
|
+
col: 'flex-col',
|
|
5594
|
+
'row-reverse': 'flex-row-reverse',
|
|
5595
|
+
'col-reverse': 'flex-col-reverse'
|
|
5596
|
+
},
|
|
5597
|
+
align: {
|
|
5598
|
+
start: 'items-start',
|
|
5599
|
+
center: 'items-center',
|
|
5600
|
+
end: 'items-end',
|
|
5601
|
+
stretch: 'items-stretch',
|
|
5602
|
+
baseline: 'items-baseline'
|
|
5603
|
+
},
|
|
5604
|
+
justify: {
|
|
5605
|
+
start: 'justify-start',
|
|
5606
|
+
center: 'justify-center',
|
|
5607
|
+
end: 'justify-end',
|
|
5608
|
+
between: 'justify-between',
|
|
5609
|
+
around: 'justify-around',
|
|
5610
|
+
evenly: 'justify-evenly'
|
|
5611
|
+
},
|
|
5612
|
+
/*
|
|
5613
|
+
* `gap` — explicit literal mapping for the 11 token-scale steps.
|
|
5614
|
+
* Same set as Box's spacing axes (p, m, etc.) so muscle memory
|
|
5615
|
+
* carries over: `<Stack gap={4}>` aligns visually with `<Box p={4}>`.
|
|
5616
|
+
*/ gap: {
|
|
5617
|
+
0: 'gap-0',
|
|
5618
|
+
'0.5': 'gap-0.5',
|
|
5619
|
+
1: 'gap-1',
|
|
5620
|
+
2: 'gap-2',
|
|
5621
|
+
3: 'gap-3',
|
|
5622
|
+
4: 'gap-4',
|
|
5623
|
+
6: 'gap-6',
|
|
5624
|
+
8: 'gap-8',
|
|
5625
|
+
12: 'gap-12',
|
|
5626
|
+
16: 'gap-16',
|
|
5627
|
+
24: 'gap-24'
|
|
5628
|
+
},
|
|
5629
|
+
wrap: {
|
|
5630
|
+
true: 'flex-wrap'
|
|
5631
|
+
},
|
|
5632
|
+
fullWidth: {
|
|
5633
|
+
true: 'w-full'
|
|
5634
|
+
},
|
|
5635
|
+
fullHeight: {
|
|
5636
|
+
true: 'h-full'
|
|
5637
|
+
}
|
|
5638
|
+
},
|
|
5639
|
+
defaultVariants: {
|
|
5640
|
+
direction: 'col'
|
|
5641
|
+
}
|
|
5642
|
+
});
|
|
5643
|
+
|
|
5644
|
+
/**
|
|
5645
|
+
* Walk the children, inserting `divider` BETWEEN every consecutive pair.
|
|
5646
|
+
*
|
|
5647
|
+
* Implementation notes:
|
|
5648
|
+
* • `React.Children.toArray` assigns auto-keys but does NOT recursively
|
|
5649
|
+
* flatten Fragments — it treats them as opaque single children. So
|
|
5650
|
+
* `<Stack><><a/><b/></><c/></Stack>` is 2 boundaries (fragment + c),
|
|
5651
|
+
* yielding ONE divider. Hoist items out of the fragment when you
|
|
5652
|
+
* need a divider between them. Documented + asserted in the tests.
|
|
5653
|
+
* • Dividers are wrapped in `<Fragment>` with a deterministic key
|
|
5654
|
+
* derived from the boundary index — stable across re-renders so
|
|
5655
|
+
* React reconciles correctly when children re-order.
|
|
5656
|
+
* • If `divider` is a valid element, we `cloneElement` once per
|
|
5657
|
+
* boundary (cheaper than re-rendering the JSX expression N-1 times).
|
|
5658
|
+
* For string/number dividers we wrap in a span automatically.
|
|
5659
|
+
*/ function interleaveDividers(children, divider) {
|
|
5660
|
+
const items = Children.toArray(children);
|
|
5661
|
+
if (items.length <= 1) return items;
|
|
5662
|
+
const result = [];
|
|
5663
|
+
items.forEach((child, i)=>{
|
|
5664
|
+
result.push(child);
|
|
5665
|
+
if (i < items.length - 1) {
|
|
5666
|
+
const key = `df-stack-divider-${i}`;
|
|
5667
|
+
if (/*#__PURE__*/ isValidElement(divider)) {
|
|
5668
|
+
result.push(/*#__PURE__*/ cloneElement(divider, {
|
|
5669
|
+
key
|
|
5670
|
+
}));
|
|
5671
|
+
} else {
|
|
5672
|
+
result.push(jsx(Fragment, {
|
|
5673
|
+
children: divider
|
|
5674
|
+
}, key));
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
});
|
|
5678
|
+
return result;
|
|
5679
|
+
}
|
|
5680
|
+
/**
|
|
5681
|
+
* `<Stack>` — flex container 1D, the layout primitive.
|
|
5682
|
+
*
|
|
5683
|
+
* This is the ONLY component in @dashforge/tw that does flex. Box
|
|
5684
|
+
* doesn't, Grid does CSS Grid (not flex). The strict naming → engine
|
|
5685
|
+
* mapping is the whole point: when you read `<Stack>` in a JSX tree,
|
|
5686
|
+
* you instantly know it's flex. No `<Box display="flex" ...>` traps.
|
|
5687
|
+
*
|
|
5688
|
+
* Direction defaults to `'col'` (vertical stack) — the most common
|
|
5689
|
+
* case for forms, sidebars, settings panels. Pass `direction="row"`
|
|
5690
|
+
* for horizontal layouts (toolbars, button rows, breadcrumbs).
|
|
5691
|
+
*
|
|
5692
|
+
* The `divider` prop is the runtime-only piece: TV can't encode the
|
|
5693
|
+
* "render this between each child" logic as a class, so we walk the
|
|
5694
|
+
* children at render time. The walk is O(n); for n ≤ ~10 (typical
|
|
5695
|
+
* Stack content) the cost is negligible. For very long Stacks (1000+
|
|
5696
|
+
* items), prefer to render dividers as part of each child instead.
|
|
5697
|
+
*
|
|
5698
|
+
* When `asChild` is true, the divider prop is silently ignored — Slot
|
|
5699
|
+
* requires a single child, and the N-1 insertion has nowhere to act.
|
|
5700
|
+
*/ const Stack = /*#__PURE__*/ forwardRef(function Stack(props, ref) {
|
|
5701
|
+
const { direction, align, justify, gap, wrap, fullWidth, fullHeight, divider, as, asChild = false, sx, children } = props, rest = _object_without_properties_loose(props, [
|
|
5702
|
+
"direction",
|
|
5703
|
+
"align",
|
|
5704
|
+
"justify",
|
|
5705
|
+
"gap",
|
|
5706
|
+
"wrap",
|
|
5707
|
+
"fullWidth",
|
|
5708
|
+
"fullHeight",
|
|
5709
|
+
"divider",
|
|
5710
|
+
"as",
|
|
5711
|
+
"asChild",
|
|
5712
|
+
"sx",
|
|
5713
|
+
"children"
|
|
5714
|
+
]);
|
|
5715
|
+
const classes = cn(stackVariants({
|
|
5716
|
+
direction,
|
|
5717
|
+
align,
|
|
5718
|
+
justify,
|
|
5719
|
+
gap,
|
|
5720
|
+
wrap,
|
|
5721
|
+
fullWidth,
|
|
5722
|
+
fullHeight
|
|
5723
|
+
}), sx);
|
|
5724
|
+
if (asChild) {
|
|
5725
|
+
// Slot expects a single child; divider has no place here.
|
|
5726
|
+
return jsx(Slot, _extends({
|
|
5727
|
+
ref: ref,
|
|
5728
|
+
className: classes
|
|
5729
|
+
}, rest, {
|
|
5730
|
+
children: children
|
|
5731
|
+
}));
|
|
5732
|
+
}
|
|
5733
|
+
const Tag = as != null ? as : 'div';
|
|
5734
|
+
const content = divider != null ? interleaveDividers(children, divider) : children;
|
|
5735
|
+
return jsx(Tag, _extends({
|
|
5736
|
+
ref: ref,
|
|
5737
|
+
className: classes
|
|
5738
|
+
}, rest, {
|
|
5739
|
+
children: content
|
|
5740
|
+
}));
|
|
5741
|
+
});
|
|
5742
|
+
Stack.displayName = 'Stack';
|
|
5743
|
+
|
|
5744
|
+
/**
|
|
5745
|
+
* `gridVariants` — CSS Grid container + item, polymorphic in role.
|
|
5746
|
+
*
|
|
5747
|
+
* Architectural choice (planned with the user — F9 deep dive):
|
|
5748
|
+
*
|
|
5749
|
+
* Pattern is MUI Grid v2 from the API side (`<Grid container>` +
|
|
5750
|
+
* `<Grid xs={6}>`), but engine is REAL CSS Grid under the hood
|
|
5751
|
+
* (`display: grid` + `grid-template-columns: repeat(N, 1fr)` +
|
|
5752
|
+
* `col-span-*`). MUI v2 uses flexbox + basis percentages — historical
|
|
5753
|
+
* IE11 reasons. In 2026, CSS Grid is universally supported AND
|
|
5754
|
+
* Tailwind ships `grid-cols-*` / `col-span-*` natively, so flexbox
|
|
5755
|
+
* would be a downgrade.
|
|
5756
|
+
*
|
|
5757
|
+
* Two distinct shapes coexist in the same TV recipe:
|
|
5758
|
+
*
|
|
5759
|
+
* • container=true — display:grid + cols + gap + autoFlow
|
|
5760
|
+
* • container=false — col-span at each breakpoint (xs/sm/md/lg/xl)
|
|
5761
|
+
*
|
|
5762
|
+
* The discriminated union lives in grid.types.ts (TypeScript-level);
|
|
5763
|
+
* here the TV catalogue exposes every axis so the consumer's choice
|
|
5764
|
+
* triggers the right classes regardless of which role the component
|
|
5765
|
+
* is playing. Unused axes produce no classes (TV silently drops
|
|
5766
|
+
* undefined variants), so a container Grid never accidentally emits
|
|
5767
|
+
* `col-span-6` and an item Grid never accidentally emits `grid`.
|
|
5768
|
+
*
|
|
5769
|
+
* Mapping table size:
|
|
5770
|
+
* • cols : 6 entries (12, 6, 4, 3, 2, 1)
|
|
5771
|
+
* • autoFlow : 5 entries
|
|
5772
|
+
* • span axes (5×14) : 70 entries (xs/sm/md/lg/xl × {1..12, auto, full})
|
|
5773
|
+
* • gap / gapX / gapY : 33 entries (11 steps × 3 axes)
|
|
5774
|
+
*
|
|
5775
|
+
* Total: ~115 literal class names. All explicit, all Tailwind-scannable.
|
|
5776
|
+
* Bundle CSS impact when fully exercised: ~3 KB gz (mostly the
|
|
5777
|
+
* responsive col-span set).
|
|
5778
|
+
*/ const gridVariants = tv({
|
|
5779
|
+
base: '',
|
|
5780
|
+
variants: {
|
|
5781
|
+
// ─── CONTAINER role ────────────────────────────────────────────────
|
|
5782
|
+
container: {
|
|
5783
|
+
true: 'grid'
|
|
5784
|
+
},
|
|
5785
|
+
cols: {
|
|
5786
|
+
1: 'grid-cols-1',
|
|
5787
|
+
2: 'grid-cols-2',
|
|
5788
|
+
3: 'grid-cols-3',
|
|
5789
|
+
4: 'grid-cols-4',
|
|
5790
|
+
6: 'grid-cols-6',
|
|
5791
|
+
12: 'grid-cols-12'
|
|
5792
|
+
},
|
|
5793
|
+
autoFlow: {
|
|
5794
|
+
row: 'grid-flow-row',
|
|
5795
|
+
col: 'grid-flow-col',
|
|
5796
|
+
dense: 'grid-flow-dense',
|
|
5797
|
+
'row-dense': 'grid-flow-row-dense',
|
|
5798
|
+
'col-dense': 'grid-flow-col-dense'
|
|
5799
|
+
},
|
|
5800
|
+
/*
|
|
5801
|
+
* Container gap — token-scale step. Same set as Box/Stack so muscle
|
|
5802
|
+
* memory carries across primitives.
|
|
5803
|
+
*/ spacing: {
|
|
5804
|
+
0: 'gap-0',
|
|
5805
|
+
'0.5': 'gap-0.5',
|
|
5806
|
+
1: 'gap-1',
|
|
5807
|
+
2: 'gap-2',
|
|
5808
|
+
3: 'gap-3',
|
|
5809
|
+
4: 'gap-4',
|
|
5810
|
+
6: 'gap-6',
|
|
5811
|
+
8: 'gap-8',
|
|
5812
|
+
12: 'gap-12',
|
|
5813
|
+
16: 'gap-16',
|
|
5814
|
+
24: 'gap-24'
|
|
5815
|
+
},
|
|
5816
|
+
spacingX: {
|
|
5817
|
+
0: 'gap-x-0',
|
|
5818
|
+
'0.5': 'gap-x-0.5',
|
|
5819
|
+
1: 'gap-x-1',
|
|
5820
|
+
2: 'gap-x-2',
|
|
5821
|
+
3: 'gap-x-3',
|
|
5822
|
+
4: 'gap-x-4',
|
|
5823
|
+
6: 'gap-x-6',
|
|
5824
|
+
8: 'gap-x-8',
|
|
5825
|
+
12: 'gap-x-12',
|
|
5826
|
+
16: 'gap-x-16',
|
|
5827
|
+
24: 'gap-x-24'
|
|
5828
|
+
},
|
|
5829
|
+
spacingY: {
|
|
5830
|
+
0: 'gap-y-0',
|
|
5831
|
+
'0.5': 'gap-y-0.5',
|
|
5832
|
+
1: 'gap-y-1',
|
|
5833
|
+
2: 'gap-y-2',
|
|
5834
|
+
3: 'gap-y-3',
|
|
5835
|
+
4: 'gap-y-4',
|
|
5836
|
+
6: 'gap-y-6',
|
|
5837
|
+
8: 'gap-y-8',
|
|
5838
|
+
12: 'gap-y-12',
|
|
5839
|
+
16: 'gap-y-16',
|
|
5840
|
+
24: 'gap-y-24'
|
|
5841
|
+
},
|
|
5842
|
+
// ─── ITEM role — col-span at each breakpoint ──────────────────────
|
|
5843
|
+
/*
|
|
5844
|
+
* `xs` is the base breakpoint (no prefix) — Tailwind's mobile-first
|
|
5845
|
+
* convention. `sm/md/lg/xl` cascade up from there.
|
|
5846
|
+
*
|
|
5847
|
+
* Special values:
|
|
5848
|
+
* • 'auto' → col-auto (content-sized)
|
|
5849
|
+
* • 'full' → col-span-full (span all columns regardless of count)
|
|
5850
|
+
*/ xs: {
|
|
5851
|
+
1: 'col-span-1',
|
|
5852
|
+
2: 'col-span-2',
|
|
5853
|
+
3: 'col-span-3',
|
|
5854
|
+
4: 'col-span-4',
|
|
5855
|
+
5: 'col-span-5',
|
|
5856
|
+
6: 'col-span-6',
|
|
5857
|
+
7: 'col-span-7',
|
|
5858
|
+
8: 'col-span-8',
|
|
5859
|
+
9: 'col-span-9',
|
|
5860
|
+
10: 'col-span-10',
|
|
5861
|
+
11: 'col-span-11',
|
|
5862
|
+
12: 'col-span-12',
|
|
5863
|
+
auto: 'col-auto',
|
|
5864
|
+
full: 'col-span-full'
|
|
5865
|
+
},
|
|
5866
|
+
sm: {
|
|
5867
|
+
1: 'sm:col-span-1',
|
|
5868
|
+
2: 'sm:col-span-2',
|
|
5869
|
+
3: 'sm:col-span-3',
|
|
5870
|
+
4: 'sm:col-span-4',
|
|
5871
|
+
5: 'sm:col-span-5',
|
|
5872
|
+
6: 'sm:col-span-6',
|
|
5873
|
+
7: 'sm:col-span-7',
|
|
5874
|
+
8: 'sm:col-span-8',
|
|
5875
|
+
9: 'sm:col-span-9',
|
|
5876
|
+
10: 'sm:col-span-10',
|
|
5877
|
+
11: 'sm:col-span-11',
|
|
5878
|
+
12: 'sm:col-span-12',
|
|
5879
|
+
auto: 'sm:col-auto',
|
|
5880
|
+
full: 'sm:col-span-full'
|
|
5881
|
+
},
|
|
5882
|
+
md: {
|
|
5883
|
+
1: 'md:col-span-1',
|
|
5884
|
+
2: 'md:col-span-2',
|
|
5885
|
+
3: 'md:col-span-3',
|
|
5886
|
+
4: 'md:col-span-4',
|
|
5887
|
+
5: 'md:col-span-5',
|
|
5888
|
+
6: 'md:col-span-6',
|
|
5889
|
+
7: 'md:col-span-7',
|
|
5890
|
+
8: 'md:col-span-8',
|
|
5891
|
+
9: 'md:col-span-9',
|
|
5892
|
+
10: 'md:col-span-10',
|
|
5893
|
+
11: 'md:col-span-11',
|
|
5894
|
+
12: 'md:col-span-12',
|
|
5895
|
+
auto: 'md:col-auto',
|
|
5896
|
+
full: 'md:col-span-full'
|
|
5897
|
+
},
|
|
5898
|
+
lg: {
|
|
5899
|
+
1: 'lg:col-span-1',
|
|
5900
|
+
2: 'lg:col-span-2',
|
|
5901
|
+
3: 'lg:col-span-3',
|
|
5902
|
+
4: 'lg:col-span-4',
|
|
5903
|
+
5: 'lg:col-span-5',
|
|
5904
|
+
6: 'lg:col-span-6',
|
|
5905
|
+
7: 'lg:col-span-7',
|
|
5906
|
+
8: 'lg:col-span-8',
|
|
5907
|
+
9: 'lg:col-span-9',
|
|
5908
|
+
10: 'lg:col-span-10',
|
|
5909
|
+
11: 'lg:col-span-11',
|
|
5910
|
+
12: 'lg:col-span-12',
|
|
5911
|
+
auto: 'lg:col-auto',
|
|
5912
|
+
full: 'lg:col-span-full'
|
|
5913
|
+
},
|
|
5914
|
+
xl: {
|
|
5915
|
+
1: 'xl:col-span-1',
|
|
5916
|
+
2: 'xl:col-span-2',
|
|
5917
|
+
3: 'xl:col-span-3',
|
|
5918
|
+
4: 'xl:col-span-4',
|
|
5919
|
+
5: 'xl:col-span-5',
|
|
5920
|
+
6: 'xl:col-span-6',
|
|
5921
|
+
7: 'xl:col-span-7',
|
|
5922
|
+
8: 'xl:col-span-8',
|
|
5923
|
+
9: 'xl:col-span-9',
|
|
5924
|
+
10: 'xl:col-span-10',
|
|
5925
|
+
11: 'xl:col-span-11',
|
|
5926
|
+
12: 'xl:col-span-12',
|
|
5927
|
+
auto: 'xl:col-auto',
|
|
5928
|
+
full: 'xl:col-span-full'
|
|
5929
|
+
}
|
|
5930
|
+
}
|
|
5931
|
+
});
|
|
5932
|
+
|
|
5933
|
+
/**
|
|
5934
|
+
* `<Grid>` — CSS Grid container + item, polymorphic in role.
|
|
5935
|
+
*
|
|
5936
|
+
* API surface mirrors MUI Grid v2:
|
|
5937
|
+
* • <Grid container spacing={4} cols={12}> ← container
|
|
5938
|
+
* • <Grid xs={12} md={6}> ← item
|
|
5939
|
+
*
|
|
5940
|
+
* Engine is REAL CSS Grid (not flexbox, unlike MUI v2 internals).
|
|
5941
|
+
* Benefits over flexbox-based implementation:
|
|
5942
|
+
* • Native `gap` (no negative-margin tricks)
|
|
5943
|
+
* • `col-span-N` maps directly to `grid-column: span N`
|
|
5944
|
+
* • Vertical alignment without manual `align-items` per item
|
|
5945
|
+
* • Auto-flow for dense packing without DOM gymnastics
|
|
5946
|
+
*
|
|
5947
|
+
* TypeScript discriminated union (see grid.types.ts) makes mixing
|
|
5948
|
+
* impossible: `<Grid container xs={6}>` won't compile. Same for the
|
|
5949
|
+
* inverse. IntelliSense filters by role.
|
|
5950
|
+
*
|
|
5951
|
+
* Defaults at the component level (not at the TV level — see the
|
|
5952
|
+
* variants file for the rationale):
|
|
5953
|
+
* • container: cols defaults to 12 (MUI convention)
|
|
5954
|
+
* • item: xs defaults to 'full' so a forgotten prop doesn't
|
|
5955
|
+
* collapse the cell to 1/12th of a row
|
|
5956
|
+
*
|
|
5957
|
+
* `asChild` on a CONTAINER is technically allowed but rarely useful:
|
|
5958
|
+
* `display:grid` only applies to direct children, so the slotted
|
|
5959
|
+
* element has to already be a viable container parent. Use sparingly;
|
|
5960
|
+
* test asserts the wrapping still emits the right class chain.
|
|
5961
|
+
*/ const Grid = /*#__PURE__*/ forwardRef(function Grid(props, ref) {
|
|
5962
|
+
const { as, asChild = false, sx, children } = props, rest = _object_without_properties_loose(props, [
|
|
5963
|
+
"as",
|
|
5964
|
+
"asChild",
|
|
5965
|
+
"sx",
|
|
5966
|
+
"children"
|
|
5967
|
+
]);
|
|
5968
|
+
/*
|
|
5969
|
+
* Discriminated union runtime branch.
|
|
5970
|
+
*
|
|
5971
|
+
* TS narrows GridProps to ONE of the two shapes (container or item)
|
|
5972
|
+
* at the call site, but inside this function body we receive the
|
|
5973
|
+
* union — so we manually narrow via a single cast per branch.
|
|
5974
|
+
* `as GridContainerProps` / `as GridItemProps` is sound because
|
|
5975
|
+
* `container === true` is the literal discriminator declared in
|
|
5976
|
+
* the types file; the cast just makes TS read the right branch.
|
|
5977
|
+
*/ const isContainer = props.container === true;
|
|
5978
|
+
let classes;
|
|
5979
|
+
let containerPayload = null;
|
|
5980
|
+
let itemPayload = null;
|
|
5981
|
+
if (isContainer) {
|
|
5982
|
+
var _containerPayload_cols;
|
|
5983
|
+
containerPayload = props;
|
|
5984
|
+
/*
|
|
5985
|
+
* Container branch — pass ONLY container axes to TV. Default cols
|
|
5986
|
+
* to 12 (MUI v2 convention) so a forgotten prop doesn't collapse
|
|
5987
|
+
* to grid-cols-1. The spacing axes are passed straight through;
|
|
5988
|
+
* TV silently drops undefined values.
|
|
5989
|
+
*/ classes = cn(gridVariants({
|
|
5990
|
+
container: true,
|
|
5991
|
+
cols: (_containerPayload_cols = containerPayload.cols) != null ? _containerPayload_cols : 12,
|
|
5992
|
+
spacing: containerPayload.spacing,
|
|
5993
|
+
spacingX: containerPayload.spacingX,
|
|
5994
|
+
spacingY: containerPayload.spacingY,
|
|
5995
|
+
autoFlow: containerPayload.autoFlow
|
|
5996
|
+
}), sx);
|
|
5997
|
+
} else {
|
|
5998
|
+
var _itemPayload_xs;
|
|
5999
|
+
itemPayload = props;
|
|
6000
|
+
/*
|
|
6001
|
+
* Item branch — default `xs` to 'full' so a forgotten breakpoint
|
|
6002
|
+
* prop doesn't collapse the cell to grid-auto's minimum width.
|
|
6003
|
+
*/ classes = cn(gridVariants({
|
|
6004
|
+
xs: (_itemPayload_xs = itemPayload.xs) != null ? _itemPayload_xs : 'full',
|
|
6005
|
+
sm: itemPayload.sm,
|
|
6006
|
+
md: itemPayload.md,
|
|
6007
|
+
lg: itemPayload.lg,
|
|
6008
|
+
xl: itemPayload.xl
|
|
6009
|
+
}), sx);
|
|
6010
|
+
}
|
|
6011
|
+
/*
|
|
6012
|
+
* Strip role-specific props from `rest` before spreading on the DOM
|
|
6013
|
+
* element. Without this, React warns:
|
|
6014
|
+
* "React does not recognize the `cols` prop on a DOM element"
|
|
6015
|
+
*/ const domRest = _extends({}, rest);
|
|
6016
|
+
if (containerPayload) {
|
|
6017
|
+
delete domRest.cols;
|
|
6018
|
+
delete domRest.spacing;
|
|
6019
|
+
delete domRest.spacingX;
|
|
6020
|
+
delete domRest.spacingY;
|
|
6021
|
+
delete domRest.autoFlow;
|
|
6022
|
+
}
|
|
6023
|
+
if (itemPayload) {
|
|
6024
|
+
delete domRest.xs;
|
|
6025
|
+
delete domRest.sm;
|
|
6026
|
+
delete domRest.md;
|
|
6027
|
+
delete domRest.lg;
|
|
6028
|
+
delete domRest.xl;
|
|
6029
|
+
}
|
|
6030
|
+
// `container` discriminator itself must never reach the DOM.
|
|
6031
|
+
delete domRest.container;
|
|
6032
|
+
if (asChild) {
|
|
6033
|
+
return jsx(Slot, _extends({
|
|
6034
|
+
ref: ref,
|
|
6035
|
+
className: classes
|
|
6036
|
+
}, domRest, {
|
|
6037
|
+
children: children
|
|
6038
|
+
}));
|
|
6039
|
+
}
|
|
6040
|
+
const Tag = as != null ? as : 'div';
|
|
6041
|
+
return jsx(Tag, _extends({
|
|
6042
|
+
ref: ref,
|
|
6043
|
+
className: classes
|
|
6044
|
+
}, domRest, {
|
|
6045
|
+
children: children
|
|
6046
|
+
}));
|
|
6047
|
+
});
|
|
6048
|
+
Grid.displayName = 'Grid';
|
|
6049
|
+
|
|
6050
|
+
/**
|
|
6051
|
+
* `containerVariants` — centered max-width wrapper for page layouts.
|
|
6052
|
+
*
|
|
6053
|
+
* Architectural role:
|
|
6054
|
+
*
|
|
6055
|
+
* Every web app has the same pattern at the page-root level: a div
|
|
6056
|
+
* that's `mx-auto`, capped at some `max-w-*`, with responsive
|
|
6057
|
+
* horizontal padding. Without Container, every page rewrites:
|
|
6058
|
+
*
|
|
6059
|
+
* <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
6060
|
+
*
|
|
6061
|
+
* Container collapses that into one typed prop set, with size names
|
|
6062
|
+
* that mirror Tailwind's breakpoint vocabulary so the muscle memory
|
|
6063
|
+
* carries over (`size="lg"` ↔ `max-w-screen-lg`).
|
|
6064
|
+
*
|
|
6065
|
+
* Size axis — maps to Tailwind's `max-w-screen-*` aliases:
|
|
6066
|
+
* • sm → max-w-screen-sm (640px)
|
|
6067
|
+
* • md → max-w-screen-md (768px)
|
|
6068
|
+
* • lg → max-w-screen-lg (1024px) — most common doc/content cap
|
|
6069
|
+
* • xl → max-w-screen-xl (1280px) — default; comfortable for full apps
|
|
6070
|
+
* • 2xl → max-w-screen-2xl (1536px) — wide dashboards
|
|
6071
|
+
* • fluid → no max-width at all (full bleed, padding still applies)
|
|
6072
|
+
*
|
|
6073
|
+
* Padding axis (`px`):
|
|
6074
|
+
* • true (default) — responsive horizontal padding (px-4 sm:px-6 lg:px-8)
|
|
6075
|
+
* The canonical Tailwind responsive padding ramp. Designed to keep
|
|
6076
|
+
* edges from kissing the viewport on mobile and breathing more on
|
|
6077
|
+
* larger screens.
|
|
6078
|
+
* • false — no padding. Use when the consumer wants full bleed AND
|
|
6079
|
+
* handles edge padding inside (e.g. a hero section with its own
|
|
6080
|
+
* internal spacing scale).
|
|
6081
|
+
*
|
|
6082
|
+
* Center content axis (`centerContent`):
|
|
6083
|
+
* • true — turns the Container into a flex column with items-center
|
|
6084
|
+
* so the page content stacks centered horizontally. Common for
|
|
6085
|
+
* marketing pages, sign-in flows, "single artifact" layouts.
|
|
6086
|
+
* • false (default) — children flow normally (block stacking).
|
|
6087
|
+
*/ const containerVariants = tv({
|
|
6088
|
+
base: 'mx-auto w-full',
|
|
6089
|
+
variants: {
|
|
6090
|
+
size: {
|
|
6091
|
+
sm: 'max-w-screen-sm',
|
|
6092
|
+
md: 'max-w-screen-md',
|
|
6093
|
+
lg: 'max-w-screen-lg',
|
|
6094
|
+
xl: 'max-w-screen-xl',
|
|
6095
|
+
'2xl': 'max-w-screen-2xl',
|
|
6096
|
+
fluid: ''
|
|
6097
|
+
},
|
|
6098
|
+
/*
|
|
6099
|
+
* Responsive padding ramp. `false` skips it entirely so the
|
|
6100
|
+
* consumer can supply custom padding via `sx` when needed.
|
|
6101
|
+
*/ px: {
|
|
6102
|
+
true: 'px-4 sm:px-6 lg:px-8',
|
|
6103
|
+
false: ''
|
|
6104
|
+
},
|
|
6105
|
+
/*
|
|
6106
|
+
* Stacks children centered. Mutually compatible with all sizes —
|
|
6107
|
+
* a fluid container with centerContent is the canonical "marketing
|
|
6108
|
+
* hero" layout.
|
|
6109
|
+
*/ centerContent: {
|
|
6110
|
+
true: 'flex flex-col items-center',
|
|
6111
|
+
false: ''
|
|
6112
|
+
}
|
|
6113
|
+
},
|
|
6114
|
+
defaultVariants: {
|
|
6115
|
+
size: 'xl',
|
|
6116
|
+
px: true,
|
|
6117
|
+
centerContent: false
|
|
6118
|
+
}
|
|
6119
|
+
});
|
|
6120
|
+
|
|
6121
|
+
/**
|
|
6122
|
+
* `<Container>` — centered max-width wrapper for page-level layouts.
|
|
6123
|
+
*
|
|
6124
|
+
* The pattern is universal: every page-root `<div>` in a non-trivial
|
|
6125
|
+
* web app boils down to "mx-auto + max-w-X + responsive horizontal
|
|
6126
|
+
* padding". Container collapses that into a typed prop set so the
|
|
6127
|
+
* decision lives in ONE place per app section (the Container size),
|
|
6128
|
+
* not scattered as utility chains across every page file.
|
|
6129
|
+
*
|
|
6130
|
+
* Default size is `'xl'` (1280px) — comfortable for full app shells
|
|
6131
|
+
* with a left nav + main + optional right rail. Drop to `'lg'` (1024px)
|
|
6132
|
+
* for content-heavy docs/marketing, jump to `'2xl'` (1536px) for wide
|
|
6133
|
+
* dashboards.
|
|
6134
|
+
*
|
|
6135
|
+
* Composition pattern — Container at the page root, Stack/Grid inside:
|
|
6136
|
+
*
|
|
6137
|
+
* <Container size="lg" as="main">
|
|
6138
|
+
* <Stack gap={8}>
|
|
6139
|
+
* <Typography variant="h1">Page title</Typography>
|
|
6140
|
+
* <Grid container spacing={6}>
|
|
6141
|
+
* <Grid xs={12} md={6}>...</Grid>
|
|
6142
|
+
* </Grid>
|
|
6143
|
+
* </Stack>
|
|
6144
|
+
* </Container>
|
|
6145
|
+
*
|
|
6146
|
+
* Container handles the page chrome (centered, padded, capped width);
|
|
6147
|
+
* Stack/Grid handle the actual layout of children. Two concerns, two
|
|
6148
|
+
* primitives.
|
|
6149
|
+
*
|
|
6150
|
+
* Polymorphism rule (same as Typography/Box/Stack/Grid): when both
|
|
6151
|
+
* `as` and `asChild` are passed, `asChild` wins.
|
|
6152
|
+
*/ const Container = /*#__PURE__*/ forwardRef(function Container(props, ref) {
|
|
6153
|
+
const { size, px, centerContent, as, asChild = false, sx, children } = props, rest = _object_without_properties_loose(props, [
|
|
6154
|
+
"size",
|
|
6155
|
+
"px",
|
|
6156
|
+
"centerContent",
|
|
6157
|
+
"as",
|
|
6158
|
+
"asChild",
|
|
6159
|
+
"sx",
|
|
6160
|
+
"children"
|
|
6161
|
+
]);
|
|
6162
|
+
const classes = cn(containerVariants({
|
|
6163
|
+
size,
|
|
6164
|
+
px,
|
|
6165
|
+
centerContent
|
|
6166
|
+
}), sx);
|
|
6167
|
+
if (asChild) {
|
|
6168
|
+
return jsx(Slot, _extends({
|
|
6169
|
+
ref: ref,
|
|
6170
|
+
className: classes
|
|
6171
|
+
}, rest, {
|
|
6172
|
+
children: children
|
|
6173
|
+
}));
|
|
6174
|
+
}
|
|
6175
|
+
const Tag = as != null ? as : 'div';
|
|
6176
|
+
return jsx(Tag, _extends({
|
|
6177
|
+
ref: ref,
|
|
6178
|
+
className: classes
|
|
6179
|
+
}, rest, {
|
|
6180
|
+
children: children
|
|
6181
|
+
}));
|
|
6182
|
+
});
|
|
6183
|
+
Container.displayName = 'Container';
|
|
6184
|
+
|
|
6185
|
+
/**
|
|
6186
|
+
* `dividerVariants` — visual separator with two rendering modes (line-only
|
|
6187
|
+
* vs labeled). Two interconnected TV recipes:
|
|
6188
|
+
*
|
|
6189
|
+
* • `dividerVariants` — root container (block layout, alignment)
|
|
6190
|
+
* • `dividerLineVariants` — the actual line segments (border style,
|
|
6191
|
+
* color, orientation)
|
|
6192
|
+
*
|
|
6193
|
+
* Two recipes (not one with slots) because the labeled mode renders
|
|
6194
|
+
* THREE elements (left line · label · right line) while the line-only
|
|
6195
|
+
* mode is ONE element. Splitting keeps each TV catalogue small and
|
|
6196
|
+
* the type unions narrow.
|
|
6197
|
+
*
|
|
6198
|
+
* Mental model:
|
|
6199
|
+
* • Without `children` → renders an `<hr>` (or a div if vertical)
|
|
6200
|
+
* with the line styles applied directly.
|
|
6201
|
+
* • With `children` → renders a flex row with two `<span>` line
|
|
6202
|
+
* segments either side of the label. The label's flex-shrink keeps
|
|
6203
|
+
* it from being squashed; the line segments share the remaining
|
|
6204
|
+
* space according to `align`.
|
|
6205
|
+
*
|
|
6206
|
+
* a11y: the root always carries `role="separator"` + `aria-orientation`
|
|
6207
|
+
* — handled in Divider.tsx, not in TV (it's a prop, not a class).
|
|
6208
|
+
*/ /*
|
|
6209
|
+
* Root container — only relevant when label is present (flex layout).
|
|
6210
|
+
* For line-only, the root IS the line.
|
|
6211
|
+
*/ const dividerVariants = tv({
|
|
6212
|
+
base: 'flex items-center',
|
|
6213
|
+
variants: {
|
|
6214
|
+
orientation: {
|
|
6215
|
+
horizontal: 'w-full',
|
|
6216
|
+
vertical: 'h-full flex-col'
|
|
6217
|
+
},
|
|
6218
|
+
/*
|
|
6219
|
+
* Label alignment along the divider's main axis. Implemented by
|
|
6220
|
+
* setting the flex-basis of the two line segments asymmetrically
|
|
6221
|
+
* — handled in the variants for `dividerLineVariants` below via
|
|
6222
|
+
* compound logic. The root just needs the flex layout.
|
|
6223
|
+
*/ align: {
|
|
6224
|
+
start: '',
|
|
6225
|
+
center: '',
|
|
6226
|
+
end: ''
|
|
6227
|
+
}
|
|
6228
|
+
},
|
|
6229
|
+
defaultVariants: {
|
|
6230
|
+
orientation: 'horizontal',
|
|
6231
|
+
align: 'center'
|
|
6232
|
+
}
|
|
6233
|
+
});
|
|
6234
|
+
/*
|
|
6235
|
+
* Line segment(s).
|
|
6236
|
+
*
|
|
6237
|
+
* Border styles (solid/dashed/dotted) are applied as `border-t-{style}`
|
|
6238
|
+
* for horizontal, `border-l-{style}` for vertical. Color drives the
|
|
6239
|
+
* border-* color token to the intent.
|
|
6240
|
+
*
|
|
6241
|
+
* The `segment` axis distinguishes whether this is a line-only render
|
|
6242
|
+
* (full width) vs a labeled-mode segment (flex-1 grows to share space).
|
|
6243
|
+
*/ const dividerLineVariants = tv({
|
|
6244
|
+
base: '',
|
|
6245
|
+
variants: {
|
|
6246
|
+
orientation: {
|
|
6247
|
+
horizontal: 'h-0 border-t',
|
|
6248
|
+
vertical: 'w-0 border-l self-stretch'
|
|
6249
|
+
},
|
|
6250
|
+
variant: {
|
|
6251
|
+
solid: 'border-solid',
|
|
6252
|
+
dashed: 'border-dashed',
|
|
6253
|
+
dotted: 'border-dotted'
|
|
6254
|
+
},
|
|
6255
|
+
color: {
|
|
6256
|
+
neutral: 'border-neutral-200 dark:border-neutral-800',
|
|
6257
|
+
primary: 'border-primary-300 dark:border-primary-700',
|
|
6258
|
+
secondary: 'border-secondary-300 dark:border-secondary-700',
|
|
6259
|
+
success: 'border-success-300 dark:border-success-700',
|
|
6260
|
+
warning: 'border-warning-300 dark:border-warning-700',
|
|
6261
|
+
danger: 'border-danger-300 dark:border-danger-700',
|
|
6262
|
+
info: 'border-info-300 dark:border-info-700'
|
|
6263
|
+
},
|
|
6264
|
+
/*
|
|
6265
|
+
* `segment` controls whether the line spans full width (line-only
|
|
6266
|
+
* mode) or grows to fill space (labeled-mode flex segment).
|
|
6267
|
+
*/ segment: {
|
|
6268
|
+
full: 'w-full',
|
|
6269
|
+
grow: 'flex-1'
|
|
6270
|
+
}
|
|
6271
|
+
},
|
|
6272
|
+
defaultVariants: {
|
|
6273
|
+
orientation: 'horizontal',
|
|
6274
|
+
variant: 'solid',
|
|
6275
|
+
color: 'neutral',
|
|
6276
|
+
segment: 'full'
|
|
6277
|
+
}
|
|
6278
|
+
});
|
|
6279
|
+
|
|
6280
|
+
/**
|
|
6281
|
+
* `<Divider>` — visual separator. Two modes:
|
|
6282
|
+
*
|
|
6283
|
+
* 1. Line-only (no children):
|
|
6284
|
+
*
|
|
6285
|
+
* <Divider />
|
|
6286
|
+
* → <hr role="separator" aria-orientation="horizontal" />
|
|
6287
|
+
*
|
|
6288
|
+
* Renders a single horizontal line via `border-t-*` on an `<hr>`
|
|
6289
|
+
* (which by default has no `display`, no margin in our reset, just
|
|
6290
|
+
* acts as the border carrier).
|
|
6291
|
+
*
|
|
6292
|
+
* 2. Labeled (with children):
|
|
6293
|
+
*
|
|
6294
|
+
* <Divider><Typography variant="overline">OR</Typography></Divider>
|
|
6295
|
+
* → <div role="separator" aria-orientation="horizontal">
|
|
6296
|
+
* <span aria-hidden className="flex-1 border-t" />
|
|
6297
|
+
* <span className="px-3">OR</span>
|
|
6298
|
+
* <span aria-hidden className="flex-1 border-t" />
|
|
6299
|
+
* </div>
|
|
6300
|
+
*
|
|
6301
|
+
* Two line segments share the available space; the label sits
|
|
6302
|
+
* between them with horizontal padding for breathing room. Each
|
|
6303
|
+
* line segment is `aria-hidden` (the role="separator" on the root
|
|
6304
|
+
* conveys the separator semantics — duplicating it would confuse
|
|
6305
|
+
* screen readers).
|
|
6306
|
+
*
|
|
6307
|
+
* Alignment:
|
|
6308
|
+
* • align="start" → label hugs the left, right segment grows
|
|
6309
|
+
* • align="center" (default) → both segments equal
|
|
6310
|
+
* • align="end" → label hugs the right, left segment grows
|
|
6311
|
+
*
|
|
6312
|
+
* Implemented by setting `flex-basis: 0` to either segment to keep
|
|
6313
|
+
* it minimal. We use `basis-[2rem]` (32px short stub) rather than
|
|
6314
|
+
* `basis-0` so a tiny line still hints at the divider on the squashed
|
|
6315
|
+
* side — pure flex-1 vs basis-0 would collapse it invisibly.
|
|
6316
|
+
*
|
|
6317
|
+
* Vertical orientation:
|
|
6318
|
+
* The line orientation flips (border-l instead of border-t). Vertical
|
|
6319
|
+
* labeled dividers are RARE in practice — supported for parity but
|
|
6320
|
+
* the typical use is line-only between flex row items.
|
|
6321
|
+
*
|
|
6322
|
+
* a11y:
|
|
6323
|
+
* • Root element gets `role="separator"` (or implicit via `<hr>`)
|
|
6324
|
+
* • `aria-orientation` is set explicitly so AT knows the axis
|
|
6325
|
+
* • Labeled mode: label has no extra role; the visual layout speaks
|
|
6326
|
+
*/ const Divider = /*#__PURE__*/ forwardRef(function Divider(props, ref) {
|
|
6327
|
+
const { orientation = 'horizontal', align = 'center', variant = 'solid', color = 'neutral', children, flexItem, sx } = props, rest = _object_without_properties_loose(props, [
|
|
6328
|
+
"orientation",
|
|
6329
|
+
"align",
|
|
6330
|
+
"variant",
|
|
6331
|
+
"color",
|
|
6332
|
+
"children",
|
|
6333
|
+
"flexItem",
|
|
6334
|
+
"sx"
|
|
6335
|
+
]);
|
|
6336
|
+
// ─── Mode 1: line-only (no children) ─────────────────────────────
|
|
6337
|
+
if (children == null) {
|
|
6338
|
+
const lineClasses = cn(dividerLineVariants({
|
|
6339
|
+
orientation,
|
|
6340
|
+
variant,
|
|
6341
|
+
color,
|
|
6342
|
+
segment: 'full'
|
|
6343
|
+
}), sx);
|
|
6344
|
+
// Horizontal → <hr>: zero default styling once we strip the UA
|
|
6345
|
+
// default `<hr>` border via `border-0` (in `dividerLineVariants`
|
|
6346
|
+
// base) and apply our own `border-t-*`. Vertical → <div> because
|
|
6347
|
+
// <hr> can't be vertical cross-browser-consistently.
|
|
6348
|
+
if (orientation === 'horizontal') {
|
|
6349
|
+
return jsx("hr", _extends({
|
|
6350
|
+
ref: ref,
|
|
6351
|
+
role: "separator",
|
|
6352
|
+
"aria-orientation": "horizontal",
|
|
6353
|
+
className: cn('border-0', lineClasses)
|
|
6354
|
+
}, rest));
|
|
6355
|
+
}
|
|
6356
|
+
return jsx("div", _extends({
|
|
6357
|
+
ref: ref,
|
|
6358
|
+
role: "separator",
|
|
6359
|
+
"aria-orientation": "vertical",
|
|
6360
|
+
className: lineClasses
|
|
6361
|
+
}, rest));
|
|
6362
|
+
}
|
|
6363
|
+
// ─── Mode 2: labeled (with children) ─────────────────────────────
|
|
6364
|
+
const rootClasses = cn(dividerVariants({
|
|
6365
|
+
orientation,
|
|
6366
|
+
align
|
|
6367
|
+
}), sx);
|
|
6368
|
+
/*
|
|
6369
|
+
* Segment widths per `align`:
|
|
6370
|
+
* • center → both flex-1 (equal share)
|
|
6371
|
+
* • start → left short stub, right grows
|
|
6372
|
+
* • end → left grows, right short stub
|
|
6373
|
+
*
|
|
6374
|
+
* "Short stub" = 2rem so the divider visually exists on the squashed
|
|
6375
|
+
* side without dominating the label.
|
|
6376
|
+
*/ const isHorizontal = orientation === 'horizontal';
|
|
6377
|
+
const leftStubClass = align === 'start' ? isHorizontal ? 'basis-8 grow-0' : 'basis-8 grow-0' : 'flex-1';
|
|
6378
|
+
const rightStubClass = align === 'end' ? isHorizontal ? 'basis-8 grow-0' : 'basis-8 grow-0' : 'flex-1';
|
|
6379
|
+
const lineCommon = dividerLineVariants({
|
|
6380
|
+
orientation,
|
|
6381
|
+
variant,
|
|
6382
|
+
color,
|
|
6383
|
+
segment: 'grow'
|
|
6384
|
+
});
|
|
6385
|
+
const labelPadding = isHorizontal ? 'px-3' : 'py-3';
|
|
6386
|
+
return jsxs("div", _extends({
|
|
6387
|
+
ref: ref,
|
|
6388
|
+
role: "separator",
|
|
6389
|
+
"aria-orientation": orientation,
|
|
6390
|
+
className: rootClasses
|
|
6391
|
+
}, rest, {
|
|
6392
|
+
children: [
|
|
6393
|
+
jsx("span", {
|
|
6394
|
+
"aria-hidden": "true",
|
|
6395
|
+
className: cn(lineCommon, leftStubClass)
|
|
6396
|
+
}),
|
|
6397
|
+
jsx("span", {
|
|
6398
|
+
className: cn('shrink-0 text-sm text-neutral-500 dark:text-neutral-400', labelPadding),
|
|
6399
|
+
children: children
|
|
6400
|
+
}),
|
|
6401
|
+
jsx("span", {
|
|
6402
|
+
"aria-hidden": "true",
|
|
6403
|
+
className: cn(lineCommon, rightStubClass)
|
|
6404
|
+
})
|
|
6405
|
+
]
|
|
6406
|
+
}));
|
|
6407
|
+
});
|
|
6408
|
+
Divider.displayName = 'Divider';
|
|
6409
|
+
|
|
6410
|
+
/**
|
|
6411
|
+
* `<AspectRatio>` — locks the aspect ratio of its child container,
|
|
6412
|
+
* regardless of width. The classic use is responsive images and
|
|
6413
|
+
* embedded media: an `<img>` that takes 100% of the available width
|
|
6414
|
+
* but always renders at 16:9 (or 1:1, or whatever the source ratio is)
|
|
6415
|
+
* — no jumping layouts during image load, no whitespace below the
|
|
6416
|
+
* media, no JS measurement.
|
|
6417
|
+
*
|
|
6418
|
+
* Implementation: native CSS `aspect-ratio` property. Supported in
|
|
6419
|
+
* every browser shipped from 2021 onward (Chrome 88, Firefox 89,
|
|
6420
|
+
* Safari 15, Edge 88). No padding-bottom hack — that workaround
|
|
6421
|
+
* predates the native property and brings ugly absolute-positioning
|
|
6422
|
+
* requirements on the child.
|
|
6423
|
+
*
|
|
6424
|
+
* Why a component if it's "just one CSS property"?
|
|
6425
|
+
* Two reasons:
|
|
6426
|
+
* 1. Discoverability — `<AspectRatio ratio={16/9}>` documents the
|
|
6427
|
+
* intent at the call site. `style={{ aspectRatio: '16/9' }}` is
|
|
6428
|
+
* the same thing functionally, but harder to spot in a 200-line
|
|
6429
|
+
* component file.
|
|
6430
|
+
* 2. Composition — pairs naturally with `sx="rounded-xl overflow-hidden"`
|
|
6431
|
+
* for the canonical "rounded clipped media" pattern. Forgetting
|
|
6432
|
+
* the `overflow-hidden` is the #1 mistake we want to prevent
|
|
6433
|
+
* through documentation (it's in this component's MDX, at the
|
|
6434
|
+
* top of the Notes).
|
|
6435
|
+
*
|
|
6436
|
+
* Child contract:
|
|
6437
|
+
* The single child is expected to fill the container — typically
|
|
6438
|
+
* `<img>` / `<video>` with `className="w-full h-full object-cover"`.
|
|
6439
|
+
* We don't force this via CSS (the consumer might want a centered
|
|
6440
|
+
* icon instead of a filling image) — but it's the 99% case, and the
|
|
6441
|
+
* docs show it first.
|
|
6442
|
+
*/ const AspectRatio = /*#__PURE__*/ forwardRef(function AspectRatio(props, ref) {
|
|
6443
|
+
const { ratio = 1, as, sx, style, children } = props, rest = _object_without_properties_loose(props, [
|
|
6444
|
+
"ratio",
|
|
6445
|
+
"as",
|
|
6446
|
+
"sx",
|
|
6447
|
+
"style",
|
|
6448
|
+
"children"
|
|
6449
|
+
]);
|
|
6450
|
+
/*
|
|
6451
|
+
* Normalise to a CSS `aspect-ratio` string. The CSS property
|
|
6452
|
+
* accepts both `16/9` and `16 / 9` (with spaces), but for safety
|
|
6453
|
+
* we convert numbers to the canonical `N / 1` form — `aspectRatio: 1.7777`
|
|
6454
|
+
* works too, but produces an arbitrary-looking value in DevTools.
|
|
6455
|
+
*/ const aspectRatioValue = typeof ratio === 'number' ? `${ratio} / 1` : ratio;
|
|
6456
|
+
const mergedStyle = _extends({
|
|
6457
|
+
aspectRatio: aspectRatioValue
|
|
6458
|
+
}, style);
|
|
6459
|
+
const classes = cn('w-full', sx);
|
|
6460
|
+
const Tag = as != null ? as : 'div';
|
|
6461
|
+
return jsx(Tag, _extends({
|
|
6462
|
+
ref: ref,
|
|
6463
|
+
className: classes,
|
|
6464
|
+
style: mergedStyle
|
|
6465
|
+
}, rest, {
|
|
6466
|
+
children: children
|
|
6467
|
+
}));
|
|
6468
|
+
});
|
|
6469
|
+
AspectRatio.displayName = 'AspectRatio';
|
|
6470
|
+
|
|
6471
|
+
/**
|
|
6472
|
+
* `<VisuallyHidden>` — the accessibility primitive.
|
|
6473
|
+
*
|
|
6474
|
+
* Hides children from sighted users (zero pixels rendered, no layout
|
|
6475
|
+
* impact) while keeping them in the accessibility tree. Screen readers
|
|
6476
|
+
* speak the content; voice control software uses it for click targets;
|
|
6477
|
+
* keyboard users see nothing — same as if it weren't there.
|
|
6478
|
+
*
|
|
6479
|
+
* Implementation: Tailwind's built-in `sr-only` utility, which expands to:
|
|
6480
|
+
*
|
|
6481
|
+
* .sr-only {
|
|
6482
|
+
* position: absolute;
|
|
6483
|
+
* width: 1px;
|
|
6484
|
+
* height: 1px;
|
|
6485
|
+
* padding: 0;
|
|
6486
|
+
* margin: -1px;
|
|
6487
|
+
* overflow: hidden;
|
|
6488
|
+
* clip: rect(0,0,0,0);
|
|
6489
|
+
* white-space: nowrap;
|
|
6490
|
+
* border-width: 0;
|
|
6491
|
+
* }
|
|
6492
|
+
*
|
|
6493
|
+
* This is the canonical "visually-hidden but screen-reader-accessible"
|
|
6494
|
+
* pattern, also known as the WebAIM clip technique. Critically:
|
|
6495
|
+
*
|
|
6496
|
+
* • NOT `display: none` (removes from a11y tree)
|
|
6497
|
+
* • NOT `visibility: hidden` (also removes from a11y tree)
|
|
6498
|
+
* • NOT `opacity: 0` (technically still rendered, doesn't help AT)
|
|
6499
|
+
* • NOT `width/height: 0` (collapses, some AT skips it)
|
|
6500
|
+
*
|
|
6501
|
+
* Default tag is `<span>` (inline) — the 99% case is "label inside a
|
|
6502
|
+
* button or link". For block content, override with `as="div"`, but
|
|
6503
|
+
* be aware nesting block inside inline is invalid HTML.
|
|
6504
|
+
*
|
|
6505
|
+
* The component is intentionally tiny (~30 LoC total): one className,
|
|
6506
|
+
* one tag. The value is the COMPONENT NAME — `<VisuallyHidden>` reads
|
|
6507
|
+
* as an intentional a11y decision in code review, while
|
|
6508
|
+
* `className="sr-only"` looks like a typo or a forgotten utility.
|
|
6509
|
+
*/ const VisuallyHidden = /*#__PURE__*/ forwardRef(function VisuallyHidden(props, ref) {
|
|
6510
|
+
const { as, sx, children } = props, rest = _object_without_properties_loose(props, [
|
|
6511
|
+
"as",
|
|
6512
|
+
"sx",
|
|
6513
|
+
"children"
|
|
6514
|
+
]);
|
|
6515
|
+
const Tag = as != null ? as : 'span';
|
|
6516
|
+
const classes = cn('sr-only', sx);
|
|
6517
|
+
return jsx(Tag, _extends({
|
|
6518
|
+
ref: ref,
|
|
6519
|
+
className: classes
|
|
6520
|
+
}, rest, {
|
|
6521
|
+
children: children
|
|
6522
|
+
}));
|
|
6523
|
+
});
|
|
6524
|
+
VisuallyHidden.displayName = 'VisuallyHidden';
|
|
6525
|
+
|
|
4929
6526
|
/**
|
|
4930
6527
|
* @dashforge/tw
|
|
4931
6528
|
*
|
|
@@ -4953,6 +6550,6 @@ const SnackbarContext = /*#__PURE__*/ createContext(null);
|
|
|
4953
6550
|
*/ // ───── Components ─────
|
|
4954
6551
|
/**
|
|
4955
6552
|
* Package version (synced with `package.json` at publish time).
|
|
4956
|
-
*/ const VERSION = '0.1
|
|
6553
|
+
*/ const VERSION = '0.2.1-beta';
|
|
4957
6554
|
|
|
4958
|
-
export { AppShell, Autocomplete, Breadcrumbs, Button, Checkbox, ConfirmDialogProvider, DateTimePicker, LeftNav, NumberField, OTPField, RadioGroup, SnackbarProvider, Switch, TextField, Textarea, TopBar, VERSION, appShellVariants, autocompleteVariants, breadcrumbsVariants, buttonVariants, checkboxVariants, cn, confirmDialogVariants, dateTimePickerVariants, isoToInputValue, leftNavVariants, numberFieldVariants, otpFieldVariants, radioGroupVariants, snackbarVariants, switchVariants, textFieldVariants, textareaVariants, topBarVariants, useAccessState, useConfirm, useSnackbar };
|
|
6555
|
+
export { AppShell, AspectRatio, Autocomplete, Box, Breadcrumbs, Button, Checkbox, ConfirmDialogProvider, Container, DateTimePicker, Divider, Grid, LeftNav, NumberField, OTPField, RadioGroup, SnackbarProvider, Stack, Switch, TextField, Textarea, TopBar, Typography, VERSION, VisuallyHidden, appShellVariants, autocompleteVariants, boxVariants, breadcrumbsVariants, buttonVariants, checkboxVariants, cn, confirmDialogVariants, containerVariants, dateTimePickerVariants, dividerLineVariants, dividerVariants, gridVariants, isoToInputValue, leftNavVariants, numberFieldVariants, otpFieldVariants, radioGroupVariants, snackbarVariants, stackVariants, switchVariants, textFieldVariants, textareaVariants, topBarVariants, typographyVariants, useAccessState, useConfirm, useSnackbar };
|