@tangible/ui 0.0.1
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/README.md +100 -0
- package/components/Accordion/Accordion.d.ts +22 -0
- package/components/Accordion/Accordion.js +192 -0
- package/components/Accordion/AccordionContext.d.ts +5 -0
- package/components/Accordion/AccordionContext.js +23 -0
- package/components/Accordion/index.d.ts +2 -0
- package/components/Accordion/index.js +1 -0
- package/components/Accordion/types.d.ts +61 -0
- package/components/Accordion/types.js +1 -0
- package/components/Avatar/Avatar.d.ts +11 -0
- package/components/Avatar/Avatar.js +67 -0
- package/components/Avatar/AvatarGroup.d.ts +11 -0
- package/components/Avatar/AvatarGroup.js +45 -0
- package/components/Avatar/index.d.ts +9 -0
- package/components/Avatar/index.js +7 -0
- package/components/Avatar/types.d.ts +44 -0
- package/components/Avatar/types.js +12 -0
- package/components/Button/Button.d.ts +4 -0
- package/components/Button/Button.js +33 -0
- package/components/Button/index.d.ts +2 -0
- package/components/Button/index.js +1 -0
- package/components/Button/types.d.ts +127 -0
- package/components/Button/types.js +1 -0
- package/components/Card/Card.d.ts +29 -0
- package/components/Card/Card.js +47 -0
- package/components/Card/index.d.ts +2 -0
- package/components/Card/index.js +1 -0
- package/components/Chip/Chip.d.ts +24 -0
- package/components/Chip/Chip.js +37 -0
- package/components/Chip/index.d.ts +2 -0
- package/components/Chip/index.js +1 -0
- package/components/Chips/Chips.d.ts +31 -0
- package/components/Chips/Chips.js +21 -0
- package/components/Chips/index.d.ts +2 -0
- package/components/Chips/index.js +1 -0
- package/components/ContentIndicator/ContentIndicator.d.ts +2 -0
- package/components/ContentIndicator/ContentIndicator.js +21 -0
- package/components/ContentIndicator/index.d.ts +2 -0
- package/components/ContentIndicator/index.js +1 -0
- package/components/ContentIndicator/types.d.ts +57 -0
- package/components/ContentIndicator/types.js +1 -0
- package/components/Dropdown/Dropdown.d.ts +31 -0
- package/components/Dropdown/Dropdown.js +219 -0
- package/components/Dropdown/DropdownContext.d.ts +3 -0
- package/components/Dropdown/DropdownContext.js +9 -0
- package/components/Dropdown/index.d.ts +2 -0
- package/components/Dropdown/index.js +1 -0
- package/components/Dropdown/types.d.ts +102 -0
- package/components/Dropdown/types.js +8 -0
- package/components/Icon/Icon.d.ts +22 -0
- package/components/Icon/Icon.js +24 -0
- package/components/Icon/index.d.ts +2 -0
- package/components/Icon/index.js +1 -0
- package/components/IconButton/IconButton.d.ts +2 -0
- package/components/IconButton/IconButton.js +50 -0
- package/components/IconButton/index.d.ts +2 -0
- package/components/IconButton/index.js +1 -0
- package/components/IconButton/types.d.ts +79 -0
- package/components/IconButton/types.js +1 -0
- package/components/Modal/Modal.d.ts +52 -0
- package/components/Modal/Modal.js +133 -0
- package/components/Modal/context.d.ts +6 -0
- package/components/Modal/context.js +9 -0
- package/components/Modal/index.d.ts +2 -0
- package/components/Modal/index.js +1 -0
- package/components/Notice/Notice.d.ts +93 -0
- package/components/Notice/Notice.js +144 -0
- package/components/Notice/index.d.ts +2 -0
- package/components/Notice/index.js +1 -0
- package/components/OverlapStack/OverlapStack.d.ts +44 -0
- package/components/OverlapStack/OverlapStack.js +41 -0
- package/components/OverlapStack/index.d.ts +2 -0
- package/components/OverlapStack/index.js +1 -0
- package/components/Pager/Pager.d.ts +26 -0
- package/components/Pager/Pager.js +151 -0
- package/components/Pager/index.d.ts +2 -0
- package/components/Pager/index.js +1 -0
- package/components/Progress/Progress.d.ts +2 -0
- package/components/Progress/Progress.js +100 -0
- package/components/Progress/index.d.ts +4 -0
- package/components/Progress/index.js +2 -0
- package/components/Progress/types.d.ts +251 -0
- package/components/Progress/types.js +1 -0
- package/components/Progress/useProgressSegments.d.ts +40 -0
- package/components/Progress/useProgressSegments.js +42 -0
- package/components/Rating/Rating.d.ts +32 -0
- package/components/Rating/Rating.js +74 -0
- package/components/Rating/index.d.ts +2 -0
- package/components/Rating/index.js +1 -0
- package/components/SegmentedControl/SegmentedControl.d.ts +10 -0
- package/components/SegmentedControl/SegmentedControl.js +183 -0
- package/components/SegmentedControl/SegmentedControlContext.d.ts +3 -0
- package/components/SegmentedControl/SegmentedControlContext.js +9 -0
- package/components/SegmentedControl/index.d.ts +2 -0
- package/components/SegmentedControl/index.js +1 -0
- package/components/SegmentedControl/types.d.ts +63 -0
- package/components/SegmentedControl/types.js +1 -0
- package/components/Sidebar/Sidebar.d.ts +17 -0
- package/components/Sidebar/Sidebar.js +107 -0
- package/components/Sidebar/index.d.ts +2 -0
- package/components/Sidebar/index.js +1 -0
- package/components/Sidebar/types.d.ts +65 -0
- package/components/Sidebar/types.js +4 -0
- package/components/StepIndicator/StepIndicator.d.ts +2 -0
- package/components/StepIndicator/StepIndicator.js +64 -0
- package/components/StepIndicator/index.d.ts +2 -0
- package/components/StepIndicator/index.js +1 -0
- package/components/StepIndicator/types.d.ts +68 -0
- package/components/StepIndicator/types.js +1 -0
- package/components/StepList/StepList.d.ts +12 -0
- package/components/StepList/StepList.js +59 -0
- package/components/StepList/StepListContext.d.ts +3 -0
- package/components/StepList/StepListContext.js +9 -0
- package/components/StepList/index.d.ts +2 -0
- package/components/StepList/index.js +1 -0
- package/components/StepList/types.d.ts +91 -0
- package/components/StepList/types.js +4 -0
- package/components/Table/BulkActionsBar.d.ts +12 -0
- package/components/Table/BulkActionsBar.js +9 -0
- package/components/Table/DataTable.d.ts +35 -0
- package/components/Table/DataTable.js +184 -0
- package/components/Table/Pagination.d.ts +13 -0
- package/components/Table/Pagination.js +13 -0
- package/components/Table/index.d.ts +2 -0
- package/components/Table/index.js +1 -0
- package/components/Tabs/Tabs.d.ts +23 -0
- package/components/Tabs/Tabs.js +309 -0
- package/components/Tabs/TabsContext.d.ts +3 -0
- package/components/Tabs/TabsContext.js +12 -0
- package/components/Tabs/index.d.ts +2 -0
- package/components/Tabs/index.js +1 -0
- package/components/Tabs/types.d.ts +75 -0
- package/components/Tabs/types.js +1 -0
- package/components/Toolbar/Toolbar.d.ts +18 -0
- package/components/Toolbar/Toolbar.js +241 -0
- package/components/Toolbar/index.d.ts +2 -0
- package/components/Toolbar/index.js +1 -0
- package/components/Toolbar/types.d.ts +28 -0
- package/components/Toolbar/types.js +1 -0
- package/components/Tooltip/Tooltip.d.ts +15 -0
- package/components/Tooltip/Tooltip.js +166 -0
- package/components/Tooltip/TooltipContext.d.ts +15 -0
- package/components/Tooltip/TooltipContext.js +25 -0
- package/components/Tooltip/index.d.ts +2 -0
- package/components/Tooltip/index.js +1 -0
- package/components/Tooltip/types.d.ts +85 -0
- package/components/Tooltip/types.js +8 -0
- package/components/index.d.ts +52 -0
- package/components/index.js +26 -0
- package/constants.d.ts +16 -0
- package/constants.js +16 -0
- package/icons/cred/index.d.ts +31 -0
- package/icons/cred/index.js +136 -0
- package/icons/icons.svg +155 -0
- package/icons/lms/index.d.ts +21 -0
- package/icons/lms/index.js +81 -0
- package/icons/manifest.json +1226 -0
- package/icons/player/index.d.ts +55 -0
- package/icons/player/index.js +268 -0
- package/icons/reaction/index.d.ts +79 -0
- package/icons/reaction/index.js +400 -0
- package/icons/registry.d.ts +316 -0
- package/icons/registry.js +163 -0
- package/icons/system/index.d.ts +155 -0
- package/icons/system/index.js +818 -0
- package/package.json +121 -0
- package/styles/all.css +1 -0
- package/styles/all.expanded.css +4137 -0
- package/styles/all.expanded.unlayered.css +4137 -0
- package/styles/all.unlayered.css +1 -0
- package/styles/components/_bundle.scss +51 -0
- package/styles/components/index.scss +1 -0
- package/styles/components/input/index.scss +248 -0
- package/styles/index.scss +71 -0
- package/styles/system/_constants.scss +12 -0
- package/styles/system/_motion.scss +47 -0
- package/styles/system/_palette-fns.scss +10 -0
- package/styles/system/_palettes.scss +80 -0
- package/styles/system/_tokens.scss +249 -0
- package/styles/system/index.scss +4 -0
- package/styles/utilities/_index.scss +373 -0
- package/tui-manifest.json +1858 -0
- package/types/index.d.ts +2 -0
- package/types/index.js +1 -0
- package/types/index.ts +2 -0
- package/types/sizes.d.ts +17 -0
- package/types/sizes.js +10 -0
- package/types/sizes.ts +21 -0
- package/types/svg.d.ts +5 -0
- package/types/themes.d.ts +14 -0
- package/types/themes.js +9 -0
- package/types/themes.ts +17 -0
- package/utils/color/contrast.d.ts +33 -0
- package/utils/color/contrast.js +88 -0
- package/utils/color-scheme.d.ts +25 -0
- package/utils/color-scheme.js +55 -0
- package/utils/compose-refs.d.ts +17 -0
- package/utils/compose-refs.js +38 -0
- package/utils/cx.d.ts +12 -0
- package/utils/cx.js +14 -0
- package/utils/focus-trap.d.ts +40 -0
- package/utils/focus-trap.js +93 -0
- package/utils/index.d.ts +10 -0
- package/utils/index.js +16 -0
- package/utils/math.d.ts +4 -0
- package/utils/math.js +19 -0
- package/utils/merge-props.d.ts +25 -0
- package/utils/merge-props.js +60 -0
- package/utils/polymorphic.d.ts +28 -0
- package/utils/polymorphic.js +44 -0
- package/utils/portal.d.ts +11 -0
- package/utils/portal.js +105 -0
package/types/index.d.ts
ADDED
package/types/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/types/index.ts
ADDED
package/types/sizes.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared size type definitions for components.
|
|
3
|
+
*
|
|
4
|
+
* Components use different subsets of the size scale based on their use case:
|
|
5
|
+
* - Size: Standard range (xs→lg) for buttons, progress indicators
|
|
6
|
+
* - SizeExtended: Full range (xs→xxl) for icons and typography
|
|
7
|
+
* - SizeCompact: Smaller range (xs→md) for inline elements like chips
|
|
8
|
+
* - SizeStandard: Medium range (sm→lg) for containers like modals
|
|
9
|
+
*/
|
|
10
|
+
/** Standard size scale: xs, sm, md, lg */
|
|
11
|
+
export type Size = 'xs' | 'sm' | 'md' | 'lg';
|
|
12
|
+
/** Extended size scale for icons/typography: xs, sm, md, lg, xl, xxl */
|
|
13
|
+
export type SizeExtended = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
|
|
14
|
+
/** Compact size scale for inline elements: xs, sm, md */
|
|
15
|
+
export type SizeCompact = 'xs' | 'sm' | 'md';
|
|
16
|
+
/** Standard size scale for containers: sm, md, lg */
|
|
17
|
+
export type SizeStandard = 'sm' | 'md' | 'lg';
|
package/types/sizes.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared size type definitions for components.
|
|
3
|
+
*
|
|
4
|
+
* Components use different subsets of the size scale based on their use case:
|
|
5
|
+
* - Size: Standard range (xs→lg) for buttons, progress indicators
|
|
6
|
+
* - SizeExtended: Full range (xs→xxl) for icons and typography
|
|
7
|
+
* - SizeCompact: Smaller range (xs→md) for inline elements like chips
|
|
8
|
+
* - SizeStandard: Medium range (sm→lg) for containers like modals
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
package/types/sizes.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared size type definitions for components.
|
|
3
|
+
*
|
|
4
|
+
* Components use different subsets of the size scale based on their use case:
|
|
5
|
+
* - Size: Standard range (xs→lg) for buttons, progress indicators
|
|
6
|
+
* - SizeExtended: Full range (xs→xxl) for icons and typography
|
|
7
|
+
* - SizeCompact: Smaller range (xs→md) for inline elements like chips
|
|
8
|
+
* - SizeStandard: Medium range (sm→lg) for containers like modals
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Standard size scale: xs, sm, md, lg */
|
|
12
|
+
export type Size = 'xs' | 'sm' | 'md' | 'lg';
|
|
13
|
+
|
|
14
|
+
/** Extended size scale for icons/typography: xs, sm, md, lg, xl, xxl */
|
|
15
|
+
export type SizeExtended = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
|
|
16
|
+
|
|
17
|
+
/** Compact size scale for inline elements: xs, sm, md */
|
|
18
|
+
export type SizeCompact = 'xs' | 'sm' | 'md';
|
|
19
|
+
|
|
20
|
+
/** Standard size scale for containers: sm, md, lg */
|
|
21
|
+
export type SizeStandard = 'sm' | 'md' | 'lg';
|
package/types/svg.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared theme type definitions for components.
|
|
3
|
+
*
|
|
4
|
+
* Components use different subsets of the theme palette based on their purpose:
|
|
5
|
+
* - ThemeIntent: Core action themes (primary, secondary, danger) for buttons
|
|
6
|
+
* - Theme: Full palette including status colors for general use
|
|
7
|
+
* - ThemeStatus: Semantic status themes for notices and alerts
|
|
8
|
+
*/
|
|
9
|
+
/** Core action themes for buttons and interactive elements */
|
|
10
|
+
export type ThemeIntent = 'primary' | 'secondary' | 'danger';
|
|
11
|
+
/** Full theme palette including status colors */
|
|
12
|
+
export type Theme = 'primary' | 'secondary' | 'success' | 'warning' | 'danger';
|
|
13
|
+
/** Semantic status themes for notices, alerts, feedback */
|
|
14
|
+
export type ThemeStatus = 'info' | 'success' | 'warning' | 'danger';
|
package/types/themes.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared theme type definitions for components.
|
|
3
|
+
*
|
|
4
|
+
* Components use different subsets of the theme palette based on their purpose:
|
|
5
|
+
* - ThemeIntent: Core action themes (primary, secondary, danger) for buttons
|
|
6
|
+
* - Theme: Full palette including status colors for general use
|
|
7
|
+
* - ThemeStatus: Semantic status themes for notices and alerts
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
package/types/themes.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared theme type definitions for components.
|
|
3
|
+
*
|
|
4
|
+
* Components use different subsets of the theme palette based on their purpose:
|
|
5
|
+
* - ThemeIntent: Core action themes (primary, secondary, danger) for buttons
|
|
6
|
+
* - Theme: Full palette including status colors for general use
|
|
7
|
+
* - ThemeStatus: Semantic status themes for notices and alerts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Core action themes for buttons and interactive elements */
|
|
11
|
+
export type ThemeIntent = 'primary' | 'secondary' | 'danger';
|
|
12
|
+
|
|
13
|
+
/** Full theme palette including status colors */
|
|
14
|
+
export type Theme = 'primary' | 'secondary' | 'success' | 'warning' | 'danger';
|
|
15
|
+
|
|
16
|
+
/** Semantic status themes for notices, alerts, feedback */
|
|
17
|
+
export type ThemeStatus = 'info' | 'success' | 'warning' | 'danger';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type ContrastTone = 'light' | 'dark';
|
|
2
|
+
/**
|
|
3
|
+
* Determine whether light or dark foreground provides better contrast.
|
|
4
|
+
* Uses WCAG relative luminance with 0.179 threshold.
|
|
5
|
+
*
|
|
6
|
+
* @param hex - Background color in #rgb or #rrggbb format
|
|
7
|
+
* @returns 'light' for dark backgrounds, 'dark' for light backgrounds
|
|
8
|
+
*
|
|
9
|
+
* Invalid input returns 'dark' (assumes light background).
|
|
10
|
+
* Relies on tests to catch invalid usage - no runtime throws.
|
|
11
|
+
*
|
|
12
|
+
* Approved use cases:
|
|
13
|
+
* - Avatar initials / fallback glyphs
|
|
14
|
+
* - User-selected background chips (reactions, labels)
|
|
15
|
+
* - One-off inline visuals where no semantic token exists
|
|
16
|
+
*
|
|
17
|
+
* NOT for core component theming - use semantic tokens instead.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getContrastTone(hex: string): ContrastTone;
|
|
20
|
+
/**
|
|
21
|
+
* Get a concrete foreground color value for a given background.
|
|
22
|
+
*
|
|
23
|
+
* @param hex - Background color in #rgb or #rrggbb format
|
|
24
|
+
* @param options.light - Color for dark backgrounds (default '#fff')
|
|
25
|
+
* @param options.dark - Color for light backgrounds (default '#000')
|
|
26
|
+
* @returns The appropriate foreground color
|
|
27
|
+
*
|
|
28
|
+
* Invalid input returns options.dark (assumes light background).
|
|
29
|
+
*/
|
|
30
|
+
export declare function getContrastForeground(hex: string, options?: {
|
|
31
|
+
light?: string;
|
|
32
|
+
dark?: string;
|
|
33
|
+
}): string;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Supports #rgb, #rrggbb, #rgba, #rrggbbaa (alpha is ignored for contrast calc)
|
|
2
|
+
const HEX_PATTERN = /^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
3
|
+
const LUMINANCE_THRESHOLD = 0.179;
|
|
4
|
+
/**
|
|
5
|
+
* Parse hex color to RGB components (0-255).
|
|
6
|
+
* Supports #rgb, #rrggbb, #rgba, #rrggbbaa formats.
|
|
7
|
+
* Alpha channel is ignored (contrast is calculated on opaque color).
|
|
8
|
+
* Trims whitespace (common in user input).
|
|
9
|
+
* Returns null for invalid input.
|
|
10
|
+
*/
|
|
11
|
+
function parseHex(hex) {
|
|
12
|
+
const match = hex.trim().match(HEX_PATTERN);
|
|
13
|
+
if (!match)
|
|
14
|
+
return null;
|
|
15
|
+
const value = match[1];
|
|
16
|
+
if (value.length === 3 || value.length === 4) {
|
|
17
|
+
// #rgb or #rgba -> extract RGB only
|
|
18
|
+
return [
|
|
19
|
+
parseInt(value[0] + value[0], 16),
|
|
20
|
+
parseInt(value[1] + value[1], 16),
|
|
21
|
+
parseInt(value[2] + value[2], 16),
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
// #rrggbb or #rrggbbaa -> extract RGB only
|
|
25
|
+
return [
|
|
26
|
+
parseInt(value.slice(0, 2), 16),
|
|
27
|
+
parseInt(value.slice(2, 4), 16),
|
|
28
|
+
parseInt(value.slice(4, 6), 16),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Apply sRGB gamma correction to a channel value.
|
|
33
|
+
* Uses 0.04045 threshold (correct sRGB spec, not the common 0.03928 bug).
|
|
34
|
+
*/
|
|
35
|
+
function gammaCorrect(channel) {
|
|
36
|
+
const srgb = channel / 255;
|
|
37
|
+
return srgb <= 0.04045
|
|
38
|
+
? srgb / 12.92
|
|
39
|
+
: Math.pow((srgb + 0.055) / 1.055, 2.4);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Calculate WCAG relative luminance.
|
|
43
|
+
*/
|
|
44
|
+
function getRelativeLuminance(r, g, b) {
|
|
45
|
+
return (0.2126 * gammaCorrect(r) +
|
|
46
|
+
0.7152 * gammaCorrect(g) +
|
|
47
|
+
0.0722 * gammaCorrect(b));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Determine whether light or dark foreground provides better contrast.
|
|
51
|
+
* Uses WCAG relative luminance with 0.179 threshold.
|
|
52
|
+
*
|
|
53
|
+
* @param hex - Background color in #rgb or #rrggbb format
|
|
54
|
+
* @returns 'light' for dark backgrounds, 'dark' for light backgrounds
|
|
55
|
+
*
|
|
56
|
+
* Invalid input returns 'dark' (assumes light background).
|
|
57
|
+
* Relies on tests to catch invalid usage - no runtime throws.
|
|
58
|
+
*
|
|
59
|
+
* Approved use cases:
|
|
60
|
+
* - Avatar initials / fallback glyphs
|
|
61
|
+
* - User-selected background chips (reactions, labels)
|
|
62
|
+
* - One-off inline visuals where no semantic token exists
|
|
63
|
+
*
|
|
64
|
+
* NOT for core component theming - use semantic tokens instead.
|
|
65
|
+
*/
|
|
66
|
+
export function getContrastTone(hex) {
|
|
67
|
+
const rgb = parseHex(hex);
|
|
68
|
+
// Invalid input: return 'dark' fallback (assumes light background)
|
|
69
|
+
if (!rgb)
|
|
70
|
+
return 'dark';
|
|
71
|
+
const luminance = getRelativeLuminance(...rgb);
|
|
72
|
+
return luminance <= LUMINANCE_THRESHOLD ? 'light' : 'dark';
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get a concrete foreground color value for a given background.
|
|
76
|
+
*
|
|
77
|
+
* @param hex - Background color in #rgb or #rrggbb format
|
|
78
|
+
* @param options.light - Color for dark backgrounds (default '#fff')
|
|
79
|
+
* @param options.dark - Color for light backgrounds (default '#000')
|
|
80
|
+
* @returns The appropriate foreground color
|
|
81
|
+
*
|
|
82
|
+
* Invalid input returns options.dark (assumes light background).
|
|
83
|
+
*/
|
|
84
|
+
export function getContrastForeground(hex, options = {}) {
|
|
85
|
+
const { light = '#fff', dark = '#000' } = options;
|
|
86
|
+
const tone = getContrastTone(hex);
|
|
87
|
+
return tone === 'light' ? light : dark;
|
|
88
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type ColorScheme = 'light' | 'dark' | 'auto';
|
|
2
|
+
/**
|
|
3
|
+
* Set color scheme on a .tui-interface element.
|
|
4
|
+
*
|
|
5
|
+
* - 'light': Removes data-theme attribute (default state)
|
|
6
|
+
* - 'dark': Sets data-theme="dark"
|
|
7
|
+
* - 'auto': Sets data-theme="auto" (respects prefers-color-scheme)
|
|
8
|
+
*
|
|
9
|
+
* Note: 'auto' falls back to light if matchMedia is unsupported.
|
|
10
|
+
*/
|
|
11
|
+
export declare function setColorScheme(scheme: ColorScheme, root?: Element | null): void;
|
|
12
|
+
/**
|
|
13
|
+
* Get current color scheme from a .tui-interface element.
|
|
14
|
+
* Returns 'light' if no data-theme attribute present.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getColorScheme(root?: Element | null): ColorScheme;
|
|
17
|
+
/**
|
|
18
|
+
* Get the resolved color scheme (what's actually displayed).
|
|
19
|
+
* For 'auto', checks prefers-color-scheme media query.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getResolvedColorScheme(root?: Element | null): 'light' | 'dark';
|
|
22
|
+
/**
|
|
23
|
+
* Toggle between light and dark (skips auto).
|
|
24
|
+
*/
|
|
25
|
+
export declare function toggleColorScheme(root?: Element | null): ColorScheme;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { INTERFACE_SELECTOR } from '../constants.js';
|
|
2
|
+
const ATTR = 'data-theme';
|
|
3
|
+
/**
|
|
4
|
+
* Set color scheme on a .tui-interface element.
|
|
5
|
+
*
|
|
6
|
+
* - 'light': Removes data-theme attribute (default state)
|
|
7
|
+
* - 'dark': Sets data-theme="dark"
|
|
8
|
+
* - 'auto': Sets data-theme="auto" (respects prefers-color-scheme)
|
|
9
|
+
*
|
|
10
|
+
* Note: 'auto' falls back to light if matchMedia is unsupported.
|
|
11
|
+
*/
|
|
12
|
+
export function setColorScheme(scheme, root) {
|
|
13
|
+
const el = root ?? document.querySelector(INTERFACE_SELECTOR);
|
|
14
|
+
if (!el)
|
|
15
|
+
return;
|
|
16
|
+
if (scheme === 'light') {
|
|
17
|
+
el.removeAttribute(ATTR);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
el.setAttribute(ATTR, scheme);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get current color scheme from a .tui-interface element.
|
|
25
|
+
* Returns 'light' if no data-theme attribute present.
|
|
26
|
+
*/
|
|
27
|
+
export function getColorScheme(root) {
|
|
28
|
+
const el = root ?? document.querySelector(INTERFACE_SELECTOR);
|
|
29
|
+
const attr = el?.getAttribute(ATTR);
|
|
30
|
+
return attr === 'dark' || attr === 'auto' ? attr : 'light';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get the resolved color scheme (what's actually displayed).
|
|
34
|
+
* For 'auto', checks prefers-color-scheme media query.
|
|
35
|
+
*/
|
|
36
|
+
export function getResolvedColorScheme(root) {
|
|
37
|
+
const scheme = getColorScheme(root);
|
|
38
|
+
if (scheme === 'auto') {
|
|
39
|
+
// Fallback to light if matchMedia unavailable
|
|
40
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
|
|
41
|
+
return 'light';
|
|
42
|
+
}
|
|
43
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
44
|
+
}
|
|
45
|
+
return scheme;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Toggle between light and dark (skips auto).
|
|
49
|
+
*/
|
|
50
|
+
export function toggleColorScheme(root) {
|
|
51
|
+
const current = getColorScheme(root);
|
|
52
|
+
const next = current === 'dark' ? 'light' : 'dark';
|
|
53
|
+
setColorScheme(next, root);
|
|
54
|
+
return next;
|
|
55
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Ref, RefCallback } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Combines multiple refs into a single ref callback.
|
|
4
|
+
* Useful for components that need to forward refs while also maintaining
|
|
5
|
+
* an internal ref, or when using asChild pattern with ref composition.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const MyComponent = forwardRef((props, forwardedRef) => {
|
|
9
|
+
* const internalRef = useRef(null);
|
|
10
|
+
* return <div ref={composeRefs(forwardedRef, internalRef)} />;
|
|
11
|
+
* });
|
|
12
|
+
*/
|
|
13
|
+
export declare function composeRefs<T>(...refs: (Ref<T> | undefined | null)[]): RefCallback<T>;
|
|
14
|
+
/**
|
|
15
|
+
* Sets a single ref value. Handles both callback refs and ref objects.
|
|
16
|
+
*/
|
|
17
|
+
export declare function setRef<T>(ref: Ref<T> | undefined | null, value: T | null): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Combines multiple refs into a single ref callback.
|
|
3
|
+
* Useful for components that need to forward refs while also maintaining
|
|
4
|
+
* an internal ref, or when using asChild pattern with ref composition.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const MyComponent = forwardRef((props, forwardedRef) => {
|
|
8
|
+
* const internalRef = useRef(null);
|
|
9
|
+
* return <div ref={composeRefs(forwardedRef, internalRef)} />;
|
|
10
|
+
* });
|
|
11
|
+
*/
|
|
12
|
+
export function composeRefs(...refs) {
|
|
13
|
+
return (node) => {
|
|
14
|
+
refs.forEach((ref) => {
|
|
15
|
+
if (!ref)
|
|
16
|
+
return;
|
|
17
|
+
if (typeof ref === 'function') {
|
|
18
|
+
ref(node);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
ref.current = node;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Sets a single ref value. Handles both callback refs and ref objects.
|
|
28
|
+
*/
|
|
29
|
+
export function setRef(ref, value) {
|
|
30
|
+
if (!ref)
|
|
31
|
+
return;
|
|
32
|
+
if (typeof ref === 'function') {
|
|
33
|
+
ref(value);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
ref.current = value;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/utils/cx.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple class name joiner - replaces clsx dependency
|
|
3
|
+
*
|
|
4
|
+
* Joins class name arguments, filtering out falsy values.
|
|
5
|
+
* Supports string, boolean, undefined, and null arguments.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* cx('base', 'modifier') // 'base modifier'
|
|
9
|
+
* cx('base', false && 'hidden') // 'base'
|
|
10
|
+
* cx('base', isActive && 'is-active', className) // 'base is-active custom'
|
|
11
|
+
*/
|
|
12
|
+
export declare function cx(...args: (string | boolean | undefined | null)[]): string;
|
package/utils/cx.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple class name joiner - replaces clsx dependency
|
|
3
|
+
*
|
|
4
|
+
* Joins class name arguments, filtering out falsy values.
|
|
5
|
+
* Supports string, boolean, undefined, and null arguments.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* cx('base', 'modifier') // 'base modifier'
|
|
9
|
+
* cx('base', false && 'hidden') // 'base'
|
|
10
|
+
* cx('base', isActive && 'is-active', className) // 'base is-active custom'
|
|
11
|
+
*/
|
|
12
|
+
export function cx(...args) {
|
|
13
|
+
return args.filter(Boolean).join(' ');
|
|
14
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS selector for focusable elements.
|
|
3
|
+
* Used by focus trap and initial focus logic.
|
|
4
|
+
*/
|
|
5
|
+
export declare const FOCUSABLE_SELECTOR: string;
|
|
6
|
+
/**
|
|
7
|
+
* Options for the focus trap hook.
|
|
8
|
+
*/
|
|
9
|
+
export type UseFocusTrapOptions = {
|
|
10
|
+
/** Whether the trap is currently active */
|
|
11
|
+
isActive: boolean;
|
|
12
|
+
/** Optional callback when Escape is pressed */
|
|
13
|
+
onEscape?: () => void;
|
|
14
|
+
/** Whether Escape should trigger onEscape (default: true) */
|
|
15
|
+
escapeDeactivates?: boolean;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Traps focus within a container element.
|
|
19
|
+
* Tab and Shift+Tab cycle through focusable elements inside the container.
|
|
20
|
+
* If no focusable elements exist, focus returns to the container itself.
|
|
21
|
+
*
|
|
22
|
+
* @param containerRef - Ref to the container element
|
|
23
|
+
* @param options - Configuration options
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* const dialogRef = useRef<HTMLDivElement>(null);
|
|
28
|
+
* useFocusTrap(dialogRef, { isActive: isOpen, onEscape: onClose });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function useFocusTrap(containerRef: React.RefObject<HTMLElement | null>, options: UseFocusTrapOptions): void;
|
|
32
|
+
/**
|
|
33
|
+
* Finds the first focusable element within a container.
|
|
34
|
+
* Optionally accepts a custom selector to try first.
|
|
35
|
+
*
|
|
36
|
+
* @param container - The container element to search within
|
|
37
|
+
* @param preferredSelector - Optional selector to try before fallback
|
|
38
|
+
* @returns The found element, or the container if nothing found
|
|
39
|
+
*/
|
|
40
|
+
export declare function getInitialFocus(container: HTMLElement, preferredSelector?: string): HTMLElement;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* CSS selector for focusable elements.
|
|
4
|
+
* Used by focus trap and initial focus logic.
|
|
5
|
+
*/
|
|
6
|
+
export const FOCUSABLE_SELECTOR = [
|
|
7
|
+
'[data-autofocus]',
|
|
8
|
+
'a[href]',
|
|
9
|
+
'area[href]',
|
|
10
|
+
'button:not([disabled])',
|
|
11
|
+
'input:not([disabled]):not([type="hidden"])',
|
|
12
|
+
'select:not([disabled])',
|
|
13
|
+
'textarea:not([disabled])',
|
|
14
|
+
'iframe',
|
|
15
|
+
'audio[controls]',
|
|
16
|
+
'video[controls]',
|
|
17
|
+
'[contenteditable]:not([contenteditable="false"])',
|
|
18
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
19
|
+
].join(',');
|
|
20
|
+
/**
|
|
21
|
+
* Traps focus within a container element.
|
|
22
|
+
* Tab and Shift+Tab cycle through focusable elements inside the container.
|
|
23
|
+
* If no focusable elements exist, focus returns to the container itself.
|
|
24
|
+
*
|
|
25
|
+
* @param containerRef - Ref to the container element
|
|
26
|
+
* @param options - Configuration options
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* const dialogRef = useRef<HTMLDivElement>(null);
|
|
31
|
+
* useFocusTrap(dialogRef, { isActive: isOpen, onEscape: onClose });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function useFocusTrap(containerRef, options) {
|
|
35
|
+
const { isActive, onEscape, escapeDeactivates = true } = options;
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!isActive)
|
|
38
|
+
return;
|
|
39
|
+
const container = containerRef.current;
|
|
40
|
+
if (!container)
|
|
41
|
+
return;
|
|
42
|
+
const handleKeyDown = (e) => {
|
|
43
|
+
// Escape handling
|
|
44
|
+
if (e.key === 'Escape' && escapeDeactivates && onEscape) {
|
|
45
|
+
onEscape();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// Tab trap
|
|
49
|
+
if (e.key !== 'Tab')
|
|
50
|
+
return;
|
|
51
|
+
const nodes = Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((n) => n.offsetParent !== null || n === document.activeElement);
|
|
52
|
+
if (nodes.length === 0) {
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
container.focus();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const first = nodes[0];
|
|
58
|
+
const last = nodes[nodes.length - 1];
|
|
59
|
+
const active = document.activeElement;
|
|
60
|
+
if (e.shiftKey) {
|
|
61
|
+
if (active === first || !container.contains(active)) {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
last.focus();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
if (active === last || !container.contains(active)) {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
first.focus();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
74
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
75
|
+
}, [isActive, containerRef, onEscape, escapeDeactivates]);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Finds the first focusable element within a container.
|
|
79
|
+
* Optionally accepts a custom selector to try first.
|
|
80
|
+
*
|
|
81
|
+
* @param container - The container element to search within
|
|
82
|
+
* @param preferredSelector - Optional selector to try before fallback
|
|
83
|
+
* @returns The found element, or the container if nothing found
|
|
84
|
+
*/
|
|
85
|
+
export function getInitialFocus(container, preferredSelector) {
|
|
86
|
+
if (preferredSelector) {
|
|
87
|
+
const preferred = container.querySelector(preferredSelector);
|
|
88
|
+
if (preferred)
|
|
89
|
+
return preferred;
|
|
90
|
+
}
|
|
91
|
+
const firstFocusable = container.querySelector(FOCUSABLE_SELECTOR);
|
|
92
|
+
return firstFocusable ?? container;
|
|
93
|
+
}
|
package/utils/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { setColorScheme, getColorScheme, getResolvedColorScheme, toggleColorScheme } from './color-scheme';
|
|
2
|
+
export type { ColorScheme } from './color-scheme';
|
|
3
|
+
export { getPortalRoot, getPortalRootFor } from './portal';
|
|
4
|
+
export { useFocusTrap, getInitialFocus, FOCUSABLE_SELECTOR } from './focus-trap';
|
|
5
|
+
export { isAnchor, getSafeRel, getDisabledAnchorProps, getDisabledButtonProps, } from './polymorphic';
|
|
6
|
+
export { composeRefs } from './compose-refs';
|
|
7
|
+
export { mergeProps } from './merge-props';
|
|
8
|
+
export { cx } from './cx';
|
|
9
|
+
export { getContrastTone, getContrastForeground } from './color/contrast';
|
|
10
|
+
export type { ContrastTone } from './color/contrast';
|
package/utils/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Color scheme utilities
|
|
2
|
+
export { setColorScheme, getColorScheme, getResolvedColorScheme, toggleColorScheme } from './color-scheme.js';
|
|
3
|
+
// Portal utilities
|
|
4
|
+
export { getPortalRoot, getPortalRootFor } from './portal.js';
|
|
5
|
+
// Focus trap utilities
|
|
6
|
+
export { useFocusTrap, getInitialFocus, FOCUSABLE_SELECTOR } from './focus-trap.js';
|
|
7
|
+
// Polymorphic component utilities
|
|
8
|
+
export { isAnchor, getSafeRel, getDisabledAnchorProps, getDisabledButtonProps, } from './polymorphic.js';
|
|
9
|
+
// Ref composition
|
|
10
|
+
export { composeRefs } from './compose-refs.js';
|
|
11
|
+
// Prop merging
|
|
12
|
+
export { mergeProps } from './merge-props.js';
|
|
13
|
+
// Class name joining (replaces clsx)
|
|
14
|
+
export { cx } from './cx.js';
|
|
15
|
+
// Contrast utilities (for user-selected colors)
|
|
16
|
+
export { getContrastTone, getContrastForeground } from './color/contrast.js';
|
package/utils/math.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const clamp: (value: number, min?: number, max?: number) => number;
|
|
2
|
+
export declare const normalize: (value: number, currentScaleMin: number, currentScaleMax: number, newScaleMin?: number, newScaleMax?: number) => number;
|
|
3
|
+
export declare const clampedNormalize: (value: number, currentScaleMin: number, currentScaleMax: number, newScaleMin?: number, newScaleMax?: number) => number;
|
|
4
|
+
export declare const exponentialNormalize: (value: number, currentScaleMin: number, currentScaleMax: number, newScaleMin?: number, newScaleMax?: number, exponent?: number) => number;
|
package/utils/math.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const clamp = (value, min = 0, max = 1) => {
|
|
2
|
+
if (min > max) {
|
|
3
|
+
[min, max] = [max, min];
|
|
4
|
+
}
|
|
5
|
+
return Math.max(min, Math.min(max, value));
|
|
6
|
+
};
|
|
7
|
+
export const normalize = (value, currentScaleMin, currentScaleMax, newScaleMin = 0, newScaleMax = 1) => {
|
|
8
|
+
const standardNormalization = (value - currentScaleMin) / (currentScaleMax - currentScaleMin);
|
|
9
|
+
return ((newScaleMax - newScaleMin) * standardNormalization + newScaleMin);
|
|
10
|
+
};
|
|
11
|
+
export const clampedNormalize = (value, currentScaleMin, currentScaleMax, newScaleMin = 0, newScaleMax = 1) => {
|
|
12
|
+
return clamp(normalize(value, currentScaleMin, currentScaleMax, newScaleMin, newScaleMax), newScaleMin, newScaleMax);
|
|
13
|
+
};
|
|
14
|
+
// Exponential interpolation, input is mapped onto a curved line rather than a straight one
|
|
15
|
+
export const exponentialNormalize = (value, currentScaleMin, currentScaleMax, newScaleMin = 0, newScaleMax = 1, exponent = 2) => {
|
|
16
|
+
const normalizedInput = (value - currentScaleMin) / (currentScaleMax - currentScaleMin);
|
|
17
|
+
const exponentialOutput = Math.pow(normalizedInput, exponent);
|
|
18
|
+
return (newScaleMin + (newScaleMax - newScaleMin) * exponentialOutput);
|
|
19
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type AnyProps = Record<string, unknown>;
|
|
2
|
+
/**
|
|
3
|
+
* Merges two sets of props, composing event handlers so both fire.
|
|
4
|
+
* Useful for extending component props without clobbering existing values.
|
|
5
|
+
*
|
|
6
|
+
* Merge rules:
|
|
7
|
+
* - Event handlers (on*): Both called in sequence (slotProps first, then overrideProps)
|
|
8
|
+
* - className: Concatenated with a space
|
|
9
|
+
* - style: Shallow merged (overrideProps wins conflicts)
|
|
10
|
+
* - Other props: overrideProps takes precedence
|
|
11
|
+
*
|
|
12
|
+
* Note: For asChild patterns where parent props must not be overridden
|
|
13
|
+
* (e.g., data-* attributes for registration), spread them after the merge:
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // Event handler composition
|
|
17
|
+
* mergeProps({ onClick: logClick }, { onClick: doAction })
|
|
18
|
+
* // Result: { onClick: [calls logClick, then doAction] }
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // Ensuring parent attribute wins
|
|
22
|
+
* cloneElement(child, { ...child.props, 'data-toolbar-item': '' })
|
|
23
|
+
*/
|
|
24
|
+
export declare function mergeProps<T extends AnyProps, U extends AnyProps>(slotProps: T, overrideProps: U): T & U;
|
|
25
|
+
export {};
|