@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.
Files changed (212) hide show
  1. package/README.md +100 -0
  2. package/components/Accordion/Accordion.d.ts +22 -0
  3. package/components/Accordion/Accordion.js +192 -0
  4. package/components/Accordion/AccordionContext.d.ts +5 -0
  5. package/components/Accordion/AccordionContext.js +23 -0
  6. package/components/Accordion/index.d.ts +2 -0
  7. package/components/Accordion/index.js +1 -0
  8. package/components/Accordion/types.d.ts +61 -0
  9. package/components/Accordion/types.js +1 -0
  10. package/components/Avatar/Avatar.d.ts +11 -0
  11. package/components/Avatar/Avatar.js +67 -0
  12. package/components/Avatar/AvatarGroup.d.ts +11 -0
  13. package/components/Avatar/AvatarGroup.js +45 -0
  14. package/components/Avatar/index.d.ts +9 -0
  15. package/components/Avatar/index.js +7 -0
  16. package/components/Avatar/types.d.ts +44 -0
  17. package/components/Avatar/types.js +12 -0
  18. package/components/Button/Button.d.ts +4 -0
  19. package/components/Button/Button.js +33 -0
  20. package/components/Button/index.d.ts +2 -0
  21. package/components/Button/index.js +1 -0
  22. package/components/Button/types.d.ts +127 -0
  23. package/components/Button/types.js +1 -0
  24. package/components/Card/Card.d.ts +29 -0
  25. package/components/Card/Card.js +47 -0
  26. package/components/Card/index.d.ts +2 -0
  27. package/components/Card/index.js +1 -0
  28. package/components/Chip/Chip.d.ts +24 -0
  29. package/components/Chip/Chip.js +37 -0
  30. package/components/Chip/index.d.ts +2 -0
  31. package/components/Chip/index.js +1 -0
  32. package/components/Chips/Chips.d.ts +31 -0
  33. package/components/Chips/Chips.js +21 -0
  34. package/components/Chips/index.d.ts +2 -0
  35. package/components/Chips/index.js +1 -0
  36. package/components/ContentIndicator/ContentIndicator.d.ts +2 -0
  37. package/components/ContentIndicator/ContentIndicator.js +21 -0
  38. package/components/ContentIndicator/index.d.ts +2 -0
  39. package/components/ContentIndicator/index.js +1 -0
  40. package/components/ContentIndicator/types.d.ts +57 -0
  41. package/components/ContentIndicator/types.js +1 -0
  42. package/components/Dropdown/Dropdown.d.ts +31 -0
  43. package/components/Dropdown/Dropdown.js +219 -0
  44. package/components/Dropdown/DropdownContext.d.ts +3 -0
  45. package/components/Dropdown/DropdownContext.js +9 -0
  46. package/components/Dropdown/index.d.ts +2 -0
  47. package/components/Dropdown/index.js +1 -0
  48. package/components/Dropdown/types.d.ts +102 -0
  49. package/components/Dropdown/types.js +8 -0
  50. package/components/Icon/Icon.d.ts +22 -0
  51. package/components/Icon/Icon.js +24 -0
  52. package/components/Icon/index.d.ts +2 -0
  53. package/components/Icon/index.js +1 -0
  54. package/components/IconButton/IconButton.d.ts +2 -0
  55. package/components/IconButton/IconButton.js +50 -0
  56. package/components/IconButton/index.d.ts +2 -0
  57. package/components/IconButton/index.js +1 -0
  58. package/components/IconButton/types.d.ts +79 -0
  59. package/components/IconButton/types.js +1 -0
  60. package/components/Modal/Modal.d.ts +52 -0
  61. package/components/Modal/Modal.js +133 -0
  62. package/components/Modal/context.d.ts +6 -0
  63. package/components/Modal/context.js +9 -0
  64. package/components/Modal/index.d.ts +2 -0
  65. package/components/Modal/index.js +1 -0
  66. package/components/Notice/Notice.d.ts +93 -0
  67. package/components/Notice/Notice.js +144 -0
  68. package/components/Notice/index.d.ts +2 -0
  69. package/components/Notice/index.js +1 -0
  70. package/components/OverlapStack/OverlapStack.d.ts +44 -0
  71. package/components/OverlapStack/OverlapStack.js +41 -0
  72. package/components/OverlapStack/index.d.ts +2 -0
  73. package/components/OverlapStack/index.js +1 -0
  74. package/components/Pager/Pager.d.ts +26 -0
  75. package/components/Pager/Pager.js +151 -0
  76. package/components/Pager/index.d.ts +2 -0
  77. package/components/Pager/index.js +1 -0
  78. package/components/Progress/Progress.d.ts +2 -0
  79. package/components/Progress/Progress.js +100 -0
  80. package/components/Progress/index.d.ts +4 -0
  81. package/components/Progress/index.js +2 -0
  82. package/components/Progress/types.d.ts +251 -0
  83. package/components/Progress/types.js +1 -0
  84. package/components/Progress/useProgressSegments.d.ts +40 -0
  85. package/components/Progress/useProgressSegments.js +42 -0
  86. package/components/Rating/Rating.d.ts +32 -0
  87. package/components/Rating/Rating.js +74 -0
  88. package/components/Rating/index.d.ts +2 -0
  89. package/components/Rating/index.js +1 -0
  90. package/components/SegmentedControl/SegmentedControl.d.ts +10 -0
  91. package/components/SegmentedControl/SegmentedControl.js +183 -0
  92. package/components/SegmentedControl/SegmentedControlContext.d.ts +3 -0
  93. package/components/SegmentedControl/SegmentedControlContext.js +9 -0
  94. package/components/SegmentedControl/index.d.ts +2 -0
  95. package/components/SegmentedControl/index.js +1 -0
  96. package/components/SegmentedControl/types.d.ts +63 -0
  97. package/components/SegmentedControl/types.js +1 -0
  98. package/components/Sidebar/Sidebar.d.ts +17 -0
  99. package/components/Sidebar/Sidebar.js +107 -0
  100. package/components/Sidebar/index.d.ts +2 -0
  101. package/components/Sidebar/index.js +1 -0
  102. package/components/Sidebar/types.d.ts +65 -0
  103. package/components/Sidebar/types.js +4 -0
  104. package/components/StepIndicator/StepIndicator.d.ts +2 -0
  105. package/components/StepIndicator/StepIndicator.js +64 -0
  106. package/components/StepIndicator/index.d.ts +2 -0
  107. package/components/StepIndicator/index.js +1 -0
  108. package/components/StepIndicator/types.d.ts +68 -0
  109. package/components/StepIndicator/types.js +1 -0
  110. package/components/StepList/StepList.d.ts +12 -0
  111. package/components/StepList/StepList.js +59 -0
  112. package/components/StepList/StepListContext.d.ts +3 -0
  113. package/components/StepList/StepListContext.js +9 -0
  114. package/components/StepList/index.d.ts +2 -0
  115. package/components/StepList/index.js +1 -0
  116. package/components/StepList/types.d.ts +91 -0
  117. package/components/StepList/types.js +4 -0
  118. package/components/Table/BulkActionsBar.d.ts +12 -0
  119. package/components/Table/BulkActionsBar.js +9 -0
  120. package/components/Table/DataTable.d.ts +35 -0
  121. package/components/Table/DataTable.js +184 -0
  122. package/components/Table/Pagination.d.ts +13 -0
  123. package/components/Table/Pagination.js +13 -0
  124. package/components/Table/index.d.ts +2 -0
  125. package/components/Table/index.js +1 -0
  126. package/components/Tabs/Tabs.d.ts +23 -0
  127. package/components/Tabs/Tabs.js +309 -0
  128. package/components/Tabs/TabsContext.d.ts +3 -0
  129. package/components/Tabs/TabsContext.js +12 -0
  130. package/components/Tabs/index.d.ts +2 -0
  131. package/components/Tabs/index.js +1 -0
  132. package/components/Tabs/types.d.ts +75 -0
  133. package/components/Tabs/types.js +1 -0
  134. package/components/Toolbar/Toolbar.d.ts +18 -0
  135. package/components/Toolbar/Toolbar.js +241 -0
  136. package/components/Toolbar/index.d.ts +2 -0
  137. package/components/Toolbar/index.js +1 -0
  138. package/components/Toolbar/types.d.ts +28 -0
  139. package/components/Toolbar/types.js +1 -0
  140. package/components/Tooltip/Tooltip.d.ts +15 -0
  141. package/components/Tooltip/Tooltip.js +166 -0
  142. package/components/Tooltip/TooltipContext.d.ts +15 -0
  143. package/components/Tooltip/TooltipContext.js +25 -0
  144. package/components/Tooltip/index.d.ts +2 -0
  145. package/components/Tooltip/index.js +1 -0
  146. package/components/Tooltip/types.d.ts +85 -0
  147. package/components/Tooltip/types.js +8 -0
  148. package/components/index.d.ts +52 -0
  149. package/components/index.js +26 -0
  150. package/constants.d.ts +16 -0
  151. package/constants.js +16 -0
  152. package/icons/cred/index.d.ts +31 -0
  153. package/icons/cred/index.js +136 -0
  154. package/icons/icons.svg +155 -0
  155. package/icons/lms/index.d.ts +21 -0
  156. package/icons/lms/index.js +81 -0
  157. package/icons/manifest.json +1226 -0
  158. package/icons/player/index.d.ts +55 -0
  159. package/icons/player/index.js +268 -0
  160. package/icons/reaction/index.d.ts +79 -0
  161. package/icons/reaction/index.js +400 -0
  162. package/icons/registry.d.ts +316 -0
  163. package/icons/registry.js +163 -0
  164. package/icons/system/index.d.ts +155 -0
  165. package/icons/system/index.js +818 -0
  166. package/package.json +121 -0
  167. package/styles/all.css +1 -0
  168. package/styles/all.expanded.css +4137 -0
  169. package/styles/all.expanded.unlayered.css +4137 -0
  170. package/styles/all.unlayered.css +1 -0
  171. package/styles/components/_bundle.scss +51 -0
  172. package/styles/components/index.scss +1 -0
  173. package/styles/components/input/index.scss +248 -0
  174. package/styles/index.scss +71 -0
  175. package/styles/system/_constants.scss +12 -0
  176. package/styles/system/_motion.scss +47 -0
  177. package/styles/system/_palette-fns.scss +10 -0
  178. package/styles/system/_palettes.scss +80 -0
  179. package/styles/system/_tokens.scss +249 -0
  180. package/styles/system/index.scss +4 -0
  181. package/styles/utilities/_index.scss +373 -0
  182. package/tui-manifest.json +1858 -0
  183. package/types/index.d.ts +2 -0
  184. package/types/index.js +1 -0
  185. package/types/index.ts +2 -0
  186. package/types/sizes.d.ts +17 -0
  187. package/types/sizes.js +10 -0
  188. package/types/sizes.ts +21 -0
  189. package/types/svg.d.ts +5 -0
  190. package/types/themes.d.ts +14 -0
  191. package/types/themes.js +9 -0
  192. package/types/themes.ts +17 -0
  193. package/utils/color/contrast.d.ts +33 -0
  194. package/utils/color/contrast.js +88 -0
  195. package/utils/color-scheme.d.ts +25 -0
  196. package/utils/color-scheme.js +55 -0
  197. package/utils/compose-refs.d.ts +17 -0
  198. package/utils/compose-refs.js +38 -0
  199. package/utils/cx.d.ts +12 -0
  200. package/utils/cx.js +14 -0
  201. package/utils/focus-trap.d.ts +40 -0
  202. package/utils/focus-trap.js +93 -0
  203. package/utils/index.d.ts +10 -0
  204. package/utils/index.js +16 -0
  205. package/utils/math.d.ts +4 -0
  206. package/utils/math.js +19 -0
  207. package/utils/merge-props.d.ts +25 -0
  208. package/utils/merge-props.js +60 -0
  209. package/utils/polymorphic.d.ts +28 -0
  210. package/utils/polymorphic.js +44 -0
  211. package/utils/portal.d.ts +11 -0
  212. package/utils/portal.js +105 -0
@@ -0,0 +1,68 @@
1
+ import type { IconName } from '../../icons/registry';
2
+ import type { Size } from '../../types';
3
+ export type { Size };
4
+ /**
5
+ * Step status states.
6
+ * - `not-started`: Empty ring (value = 0)
7
+ * - `in-progress`: Ring with progress fill (0 < value < 100)
8
+ * - `complete`: Solid filled circle with checkmark (value = 100)
9
+ * - `locked`: Ring with lock icon (overrides value-based inference)
10
+ */
11
+ export type StepStatus = 'not-started' | 'in-progress' | 'complete' | 'locked';
12
+ export type StepIndicatorProps = {
13
+ /**
14
+ * Progress value (0-100).
15
+ *
16
+ * Status is inferred from value by default:
17
+ * - `value = 0` → not-started
18
+ * - `0 < value < 100` → in-progress
19
+ * - `value >= 100` → complete
20
+ *
21
+ * Use the `status` prop to override inference (e.g., for locked steps).
22
+ * @default 0
23
+ */
24
+ value?: number;
25
+ /**
26
+ * Override the inferred status.
27
+ *
28
+ * By default, status is derived from `value`. Set this explicitly for:
29
+ * - `'locked'` — Shows lock icon regardless of value
30
+ * - Force a specific state when value doesn't reflect actual status
31
+ */
32
+ status?: StepStatus;
33
+ /**
34
+ * Custom icon to display instead of the default status icons.
35
+ *
36
+ * - Shows for `not-started` and `in-progress` states
37
+ * - `complete` always shows checkmark (completion is universal)
38
+ * - `locked` always shows lock (access restriction is universal)
39
+ *
40
+ * Use for content-type indicators like quizzes, readings, videos.
41
+ * @example icon="lms/question-mark"
42
+ */
43
+ icon?: IconName;
44
+ /**
45
+ * Size of the indicator.
46
+ * - `'xs'`: 16px circle
47
+ * - `'sm'`: 24px circle (default)
48
+ * - `'md'`: 32px circle
49
+ * - `'lg'`: 40px circle
50
+ * @default 'sm'
51
+ */
52
+ size?: Size;
53
+ /**
54
+ * Show percentage text in center (only visible for in-progress status).
55
+ * @default false
56
+ */
57
+ showValue?: boolean;
58
+ /**
59
+ * Accessible label for the indicator.
60
+ * Used as aria-label when the indicator conveys unique information.
61
+ * If omitted, defaults to "Step status: {status}".
62
+ */
63
+ label?: string;
64
+ /**
65
+ * Additional CSS class names.
66
+ */
67
+ className?: string;
68
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import type { StepListProps, StepListItemProps } from './types';
2
+ declare function StepListItem(props: StepListItemProps): import("react/jsx-runtime").JSX.Element;
3
+ declare namespace StepListItem {
4
+ var displayName: string;
5
+ }
6
+ type StepListCompound = {
7
+ (props: StepListProps): React.JSX.Element;
8
+ displayName?: string;
9
+ Item: typeof StepListItem;
10
+ };
11
+ export declare const StepList: StepListCompound;
12
+ export {};
@@ -0,0 +1,59 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import { cx } from '../../utils/cx.js';
4
+ import { StepListContext, useStepListContext } from './StepListContext.js';
5
+ import { StepIndicator } from '../StepIndicator/index.js';
6
+ // =============================================================================
7
+ // StepList (Root)
8
+ // =============================================================================
9
+ function StepListRoot(props) {
10
+ const { ariaLabel, ariaLabelledBy, ariaCurrent = 'step', current, onSelect, children, className, } = props;
11
+ // Dev warning for missing nav label
12
+ if (import.meta.env.DEV && !ariaLabel && !ariaLabelledBy) {
13
+ console.warn('StepList: Navigation landmark requires a label. ' +
14
+ 'Provide either `ariaLabel` or `ariaLabelledBy` prop for screen reader users.');
15
+ }
16
+ const rootClassName = cx('tui-steplist', className);
17
+ // Memoize context value to prevent unnecessary re-renders of all items
18
+ const contextValue = useMemo(() => ({ current, ariaCurrent, onSelect }), [current, ariaCurrent, onSelect]);
19
+ return (_jsx(StepListContext.Provider, { value: contextValue, children: _jsx("nav", { className: rootClassName, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, children: _jsx("ul", { role: "list", className: "tui-steplist__list", children: children }) }) }));
20
+ }
21
+ // =============================================================================
22
+ // StepList.Item
23
+ // =============================================================================
24
+ function StepListItem(props) {
25
+ const { value, status = 'not-started', progress, icon, href, disabled = false, children, className, } = props;
26
+ const { current, ariaCurrent, onSelect } = useStepListContext();
27
+ const isCurrent = current === value;
28
+ // Compute StepIndicator value based on status and progress
29
+ const indicatorValue = status === 'complete'
30
+ ? 100
31
+ : status === 'in-progress'
32
+ ? progress ?? 50
33
+ : 0;
34
+ const handleClick = (event) => {
35
+ if (disabled) {
36
+ event.preventDefault();
37
+ return;
38
+ }
39
+ onSelect?.(value);
40
+ };
41
+ const itemClassName = cx('tui-steplist__item', isCurrent && 'is-current', disabled && 'is-disabled', `is-status-${status}`, className);
42
+ // Shared content for both anchor and button variants
43
+ const content = (_jsxs(_Fragment, { children: [_jsx(StepIndicator, { value: indicatorValue, status: status, icon: icon, size: "xs", className: "tui-steplist__indicator" }), _jsx("span", { className: "tui-steplist__content", children: children })] }));
44
+ // Render as anchor if href provided (and not disabled)
45
+ // Disabled anchors have href removed to prevent navigation
46
+ if (href && !disabled) {
47
+ return (_jsx("li", { children: _jsx("a", { href: href, className: itemClassName, "aria-current": isCurrent ? ariaCurrent : undefined, onClick: handleClick, children: content }) }));
48
+ }
49
+ // Render disabled anchor as span-like element (no href = not navigable)
50
+ if (href && disabled) {
51
+ return (_jsx("li", { children: _jsx("a", { className: itemClassName, "aria-current": isCurrent ? ariaCurrent : undefined, "aria-disabled": "true", tabIndex: -1, role: "link", onClick: handleClick, children: content }) }));
52
+ }
53
+ // Otherwise render as button
54
+ return (_jsx("li", { children: _jsx("button", { type: "button", className: itemClassName, "aria-current": isCurrent ? ariaCurrent : undefined, disabled: disabled, onClick: handleClick, children: content }) }));
55
+ }
56
+ StepListItem.displayName = 'StepList.Item';
57
+ export const StepList = StepListRoot;
58
+ StepList.displayName = 'StepList';
59
+ StepList.Item = StepListItem;
@@ -0,0 +1,3 @@
1
+ import type { StepListContextValue } from './types';
2
+ export declare const StepListContext: import("react").Context<StepListContextValue | null>;
3
+ export declare function useStepListContext(): StepListContextValue;
@@ -0,0 +1,9 @@
1
+ import { createContext, useContext } from 'react';
2
+ export const StepListContext = createContext(null);
3
+ export function useStepListContext() {
4
+ const context = useContext(StepListContext);
5
+ if (!context) {
6
+ throw new Error('StepList.Item must be used within a StepList');
7
+ }
8
+ return context;
9
+ }
@@ -0,0 +1,2 @@
1
+ export { StepList } from './StepList';
2
+ export type { StepListProps, StepListItemProps, StepStatus, } from './types';
@@ -0,0 +1 @@
1
+ export { StepList } from './StepList.js';
@@ -0,0 +1,91 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { StepStatus } from '../StepIndicator/types';
3
+ import type { IconName } from '../../icons/registry';
4
+ export type { StepStatus };
5
+ export type StepListProps = {
6
+ /**
7
+ * Accessible label for the navigation landmark.
8
+ * Use either `ariaLabel` or `ariaLabelledBy`, not both.
9
+ */
10
+ ariaLabel?: string;
11
+ /**
12
+ * ID of a visible heading that labels the navigation.
13
+ * Use either `ariaLabel` or `ariaLabelledBy`, not both.
14
+ */
15
+ ariaLabelledBy?: string;
16
+ /**
17
+ * What `aria-current` value to use for the current item.
18
+ * - `'step'`: For step-by-step progressions (default)
19
+ * - `'page'`: For page navigation
20
+ * @default 'step'
21
+ */
22
+ ariaCurrent?: 'step' | 'page';
23
+ /**
24
+ * Value of the currently active step.
25
+ * The matching StepList.Item will receive `aria-current` and current styling.
26
+ */
27
+ current?: string;
28
+ /**
29
+ * Callback when a step is selected (clicked or activated).
30
+ */
31
+ onSelect?: (value: string) => void;
32
+ /**
33
+ * StepList.Item children.
34
+ */
35
+ children: ReactNode;
36
+ /**
37
+ * Additional CSS class names.
38
+ */
39
+ className?: string;
40
+ };
41
+ export type StepListItemProps = {
42
+ /**
43
+ * Unique identifier for this step.
44
+ * Used to determine if this item is current and passed to `onSelect` callback.
45
+ */
46
+ value: string;
47
+ /**
48
+ * Step status — determines the indicator appearance.
49
+ * - `'not-started'`: Empty ring
50
+ * - `'in-progress'`: Ring with progress fill
51
+ * - `'complete'`: Filled circle with checkmark
52
+ * - `'locked'`: Ring with lock icon
53
+ * @default 'not-started'
54
+ */
55
+ status?: StepStatus;
56
+ /**
57
+ * Progress value (0-100), only used when `status='in-progress'`.
58
+ */
59
+ progress?: number;
60
+ /**
61
+ * Custom icon to display in the indicator.
62
+ * - Only shown for `not-started` status
63
+ * - `complete` always shows checkmark, `locked` always shows lock
64
+ * - `in-progress` shows progress ring (icon ignored)
65
+ * @example icon="lms/question-mark"
66
+ */
67
+ icon?: IconName;
68
+ /**
69
+ * URL to navigate to. When provided, renders as an anchor element.
70
+ */
71
+ href?: string;
72
+ /**
73
+ * Prevent interaction with this step.
74
+ * - Buttons: uses native `disabled` attribute
75
+ * - Anchors: uses `aria-disabled="true"` + `tabIndex={-1}` + prevents click
76
+ */
77
+ disabled?: boolean;
78
+ /**
79
+ * Step label/content.
80
+ */
81
+ children: ReactNode;
82
+ /**
83
+ * Additional CSS class names.
84
+ */
85
+ className?: string;
86
+ };
87
+ export type StepListContextValue = {
88
+ current: string | undefined;
89
+ ariaCurrent: 'step' | 'page';
90
+ onSelect: ((value: string) => void) | undefined;
91
+ };
@@ -0,0 +1,4 @@
1
+ // =============================================================================
2
+ // StepList Types
3
+ // =============================================================================
4
+ export {};
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ type BulkActionsBarProps = {
3
+ count: number;
4
+ onClear?: () => void;
5
+ clearLabel?: React.ReactNode;
6
+ children?: React.ReactNode;
7
+ className?: string;
8
+ /** ID of the table element this toolbar controls */
9
+ 'aria-controls'?: string;
10
+ };
11
+ export declare function BulkActionsBar({ count, onClear, clearLabel, children, className, 'aria-controls': ariaControls, }: BulkActionsBarProps): import("react/jsx-runtime").JSX.Element | null;
12
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { cx } from '../../utils/cx.js';
4
+ export function BulkActionsBar({ count, onClear, clearLabel = 'Clear', children, className, 'aria-controls': ariaControls, }) {
5
+ if (count === 0)
6
+ return null;
7
+ const label = `Bulk actions for ${count} selected row${count === 1 ? '' : 's'}`;
8
+ return (_jsxs("div", { role: "toolbar", "aria-label": label, "aria-controls": ariaControls, className: cx('tui-table__bulkActions', className), children: [_jsxs("span", { className: "tui-table__bulkActions__count", role: "status", children: [count, " selected"] }), _jsxs("div", { className: "tui-table__bulkActions__actions", children: [children, onClear && (_jsx("button", { type: "button", onClick: onClear, className: "tui-table__bulkActions__clear tui-button is-style-outline is-size-xs", children: clearLabel }))] })] }));
9
+ }
@@ -0,0 +1,35 @@
1
+ import * as React from 'react';
2
+ import type { ColumnDef, SortingState, Table as TanTable, RowSelectionState, Updater } from '@tanstack/react-table';
3
+ type RowId = string | number;
4
+ type BulkSelectionRenderArgs<T> = {
5
+ selectedRows: T[];
6
+ selectedRowIds: RowId[];
7
+ clearSelection: () => void;
8
+ selectedCount?: number;
9
+ };
10
+ export type DataTableProps<T> = {
11
+ data: T[];
12
+ columns: ColumnDef<T, unknown>[];
13
+ className?: string;
14
+ 'aria-label'?: string;
15
+ 'aria-labelledby'?: string;
16
+ defaultSorting?: SortingState;
17
+ loading?: boolean;
18
+ loadingMessage?: React.ReactNode;
19
+ emptyMessage?: React.ReactNode;
20
+ renderPagination?: (table: TanTable<T>) => React.ReactNode;
21
+ paginationMode?: 'simple' | 'ends' | 'full' | 'smart';
22
+ paginationMaxNumbers?: number;
23
+ paginationNavStyle?: 'text' | 'icon';
24
+ pageSizeOptions?: number[];
25
+ initialPageSize?: number;
26
+ enableRowSelection?: boolean;
27
+ getRowId?: (row: T, index: number) => string | number;
28
+ getRowLabelForSelection?: (row: T, index: number) => string;
29
+ rowSelection?: RowSelectionState;
30
+ onRowSelectionChange?: (updater: Updater<RowSelectionState>) => void;
31
+ onSelectionChange?: (selectedRows: T[], selectedIds: (string | number)[]) => void;
32
+ renderBulkBar?: (args: BulkSelectionRenderArgs<T>) => React.ReactNode;
33
+ };
34
+ export declare function DataTable<T>({ data, columns, className, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, defaultSorting, loading, loadingMessage, emptyMessage, renderPagination, paginationMode, paginationMaxNumbers, paginationNavStyle, pageSizeOptions, initialPageSize, enableRowSelection, getRowId, getRowLabelForSelection, onSelectionChange, renderBulkBar, rowSelection, onRowSelectionChange, }: DataTableProps<T>): import("react/jsx-runtime").JSX.Element;
35
+ export {};
@@ -0,0 +1,184 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { cx } from '../../utils/cx.js';
4
+ import { useReactTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel, flexRender } from '@tanstack/react-table';
5
+ import { BulkActionsBar } from './BulkActionsBar.js';
6
+ import { Pagination } from './Pagination.js';
7
+ export function DataTable({ data, columns, className, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, defaultSorting = [], loading = false, loadingMessage = 'Loading...', emptyMessage = 'No results', renderPagination, paginationMode = 'smart', paginationMaxNumbers = 7, paginationNavStyle = 'text', pageSizeOptions = [10, 20, 50], initialPageSize,
8
+ // selection
9
+ enableRowSelection = false, getRowId, getRowLabelForSelection, onSelectionChange, renderBulkBar, rowSelection, onRowSelectionChange, }) {
10
+ const tableId = React.useId();
11
+ const [sorting, setSorting] = React.useState(defaultSorting);
12
+ const [uncontrolledRowSelection, setUncontrolledRowSelection] = React.useState({});
13
+ // Anchor row for shift-range selection (ref to avoid recreating selectionColumn)
14
+ const lastSelectedRowIdRef = React.useRef(null);
15
+ const isSelectionControlled = typeof rowSelection !== 'undefined';
16
+ const rowSelectionState = isSelectionControlled ? rowSelection : uncontrolledRowSelection;
17
+ const handleRowSelectionChange = React.useCallback((updater) => {
18
+ if (!isSelectionControlled) {
19
+ setUncontrolledRowSelection(prev => typeof updater === 'function' ? updater(prev) : updater);
20
+ }
21
+ onRowSelectionChange?.(updater);
22
+ }, [isSelectionControlled, onRowSelectionChange]);
23
+ const defaultPageSize = initialPageSize ?? pageSizeOptions[0] ?? 10;
24
+ // build columns, inject selection column only when enabled
25
+ const selectionColumn = React.useMemo(() => ({
26
+ id: '__select',
27
+ size: 32,
28
+ header: ({ table }) => {
29
+ const isAllSelected = table.getIsAllPageRowsSelected();
30
+ const isSomeSelected = table.getIsSomePageRowsSelected();
31
+ const isIndeterminate = isSomeSelected && !isAllSelected;
32
+ return (_jsx("input", { type: "checkbox", "aria-label": "Select all rows on this page", "aria-checked": isIndeterminate ? 'mixed' : isAllSelected, checked: isAllSelected, ref: (el) => {
33
+ if (el)
34
+ el.indeterminate = isIndeterminate;
35
+ }, onChange: table.getToggleAllPageRowsSelectedHandler() }));
36
+ },
37
+ cell: ({ row, table }) => {
38
+ const idx = row.index;
39
+ const original = row.original;
40
+ const labelText = getRowLabelForSelection?.(original, idx) ?? `Select row ${idx + 1}`;
41
+ const handleChange = (e) => {
42
+ const willSelect = e.target.checked;
43
+ const isShift = e.nativeEvent.shiftKey;
44
+ if (isShift && lastSelectedRowIdRef.current) {
45
+ const visibleRows = table.getRowModel().rows;
46
+ const idxOf = (id) => visibleRows.findIndex((r) => r.id === id);
47
+ const a = idxOf(lastSelectedRowIdRef.current);
48
+ const b = idxOf(row.id);
49
+ if (a !== -1 && b !== -1) {
50
+ const [start, end] = a < b ? [a, b] : [b, a];
51
+ table.setRowSelection((prev) => {
52
+ const next = { ...prev };
53
+ for (let i = start; i <= end; i++) {
54
+ const id = visibleRows[i].id;
55
+ if (willSelect)
56
+ next[id] = true;
57
+ else
58
+ delete next[id];
59
+ }
60
+ return next;
61
+ });
62
+ }
63
+ }
64
+ else {
65
+ row.toggleSelected(willSelect);
66
+ }
67
+ lastSelectedRowIdRef.current = row.id;
68
+ };
69
+ const isSelected = row.getIsSelected();
70
+ const isSomeSelected = row.getIsSomeSelected();
71
+ return (_jsx("input", { type: "checkbox", "aria-label": labelText, "aria-checked": isSomeSelected ? 'mixed' : isSelected, checked: isSelected, ref: (el) => {
72
+ if (el)
73
+ el.indeterminate = isSomeSelected;
74
+ }, onChange: handleChange }));
75
+ },
76
+ enableSorting: false,
77
+ enableHiding: false,
78
+ }), [getRowLabelForSelection]);
79
+ const effectiveColumns = React.useMemo(() => {
80
+ if (!enableRowSelection)
81
+ return columns;
82
+ return [selectionColumn, ...columns];
83
+ }, [enableRowSelection, columns, selectionColumn]);
84
+ const table = useReactTable({
85
+ data,
86
+ columns: effectiveColumns,
87
+ state: {
88
+ sorting,
89
+ rowSelection: rowSelectionState,
90
+ },
91
+ onSortingChange: setSorting,
92
+ onRowSelectionChange: handleRowSelectionChange,
93
+ getCoreRowModel: getCoreRowModel(),
94
+ getSortedRowModel: getSortedRowModel(),
95
+ getPaginationRowModel: getPaginationRowModel(),
96
+ getRowId: getRowId
97
+ ? (row, index) => String(getRowId(row, index))
98
+ : undefined, // may be undefined, tanstack handles it
99
+ enableRowSelection,
100
+ initialState: {
101
+ pagination: {
102
+ pageSize: defaultPageSize,
103
+ },
104
+ },
105
+ });
106
+ // Clear range anchor when pagination or sorting changes
107
+ const currentPageIndex = table.getState().pagination.pageIndex;
108
+ const currentSorting = table.getState().sorting;
109
+ React.useEffect(() => {
110
+ lastSelectedRowIdRef.current = null;
111
+ }, [currentPageIndex, currentSorting]);
112
+ const rows = table.getRowModel().rows;
113
+ const isEmpty = !loading && rows.length === 0;
114
+ // Derive selection with stable references for callback
115
+ const selectedRowModel = table.getSelectedRowModel();
116
+ const selectedRows = React.useMemo(() => selectedRowModel.rows.map((r) => r.original), [selectedRowModel.rows]);
117
+ const selectedRowIds = React.useMemo(() => Object.entries(rowSelectionState)
118
+ .filter(([, selected]) => selected)
119
+ .map(([id]) => id), [rowSelectionState]);
120
+ const selectedCount = selectedRows.length;
121
+ // Notify parent of selection changes (skip initial mount)
122
+ const hasMountedRef = React.useRef(false);
123
+ const onSelectionChangeRef = React.useRef(onSelectionChange);
124
+ onSelectionChangeRef.current = onSelectionChange;
125
+ React.useEffect(() => {
126
+ if (!hasMountedRef.current) {
127
+ hasMountedRef.current = true;
128
+ return;
129
+ }
130
+ onSelectionChangeRef.current?.(selectedRows, selectedRowIds);
131
+ }, [selectedRows, selectedRowIds]);
132
+ // pagination visibility
133
+ const pageCount = table.getPageCount();
134
+ const showPager = !isEmpty && pageCount > 1;
135
+ // bulk bar visibility
136
+ const showBulkBar = enableRowSelection && selectedRows.length > 0;
137
+ // Live region announcement for sort/page changes
138
+ const [announcement, setAnnouncement] = React.useState('');
139
+ const prevPageRef = React.useRef(currentPageIndex);
140
+ const prevSortRef = React.useRef(currentSorting);
141
+ React.useEffect(() => {
142
+ // Skip initial mount
143
+ if (prevPageRef.current === currentPageIndex && prevSortRef.current === currentSorting) {
144
+ return;
145
+ }
146
+ if (prevPageRef.current !== currentPageIndex) {
147
+ setAnnouncement(`Page ${currentPageIndex + 1} of ${pageCount}`);
148
+ }
149
+ else if (prevSortRef.current !== currentSorting && currentSorting.length > 0) {
150
+ const sort = currentSorting[0];
151
+ const col = columns.find(c => 'accessorKey' in c && c.accessorKey === sort.id);
152
+ const colName = col && typeof col.header === 'string' ? col.header : sort.id;
153
+ setAnnouncement(`Sorted by ${colName}, ${sort.desc ? 'descending' : 'ascending'}`);
154
+ }
155
+ prevPageRef.current = currentPageIndex;
156
+ prevSortRef.current = currentSorting;
157
+ }, [currentPageIndex, currentSorting, pageCount, columns]);
158
+ return (_jsxs("div", { className: cx('tui-table-container', className), children: [_jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "tui-visually-hidden", children: announcement }), showBulkBar && (renderBulkBar ? (renderBulkBar({
159
+ selectedRows,
160
+ selectedRowIds,
161
+ clearSelection: () => table.resetRowSelection(),
162
+ selectedCount
163
+ })) : (_jsx(BulkActionsBar, { count: selectedCount, onClear: () => table.resetRowSelection(), "aria-controls": tableId }))), _jsxs("table", { id: tableId, className: "tui-table", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-busy": loading || undefined, children: [_jsx("thead", { className: "tui-table__head", children: table.getHeaderGroups().map((headerGroup) => (_jsx("tr", { className: "tui-table__row tui-table__row--head", children: headerGroup.headers.map((header) => {
164
+ const canSort = header.column.getCanSort();
165
+ const sortDirection = header.column.getIsSorted();
166
+ const headerDef = header.column.columnDef.header;
167
+ const headerContent = flexRender(headerDef, header.getContext());
168
+ // Use header string for aria-label, fallback to column id
169
+ const headerLabel = typeof headerDef === 'string' ? headerDef : header.id;
170
+ return (_jsx("th", { className: "tui-table__cell tui-table__cell--head", "aria-sort": canSort
171
+ ? sortDirection === 'asc'
172
+ ? 'ascending'
173
+ : sortDirection === 'desc'
174
+ ? 'descending'
175
+ : 'none'
176
+ : undefined, children: canSort ? (_jsxs("button", { className: "tui-table__sortButton", onClick: header.column.getToggleSortingHandler(), "aria-label": `Sort by ${headerLabel}`, children: [headerContent, _jsx("span", { className: "tui-table__sortButton__icon", "aria-hidden": "true", children: sortDirection === 'asc'
177
+ ? '▲'
178
+ : sortDirection === 'desc'
179
+ ? '▼'
180
+ : '' })] })) : (headerContent) }, header.id));
181
+ }) }, headerGroup.id))) }), _jsx("tbody", { className: "tui-table__body", "aria-live": loading ? 'polite' : undefined, children: loading ? (_jsx("tr", { className: "tui-table__row tui-table__row--loading", children: _jsx("td", { className: "tui-table__cell tui-table__cell--body", colSpan: table.getAllLeafColumns().length, children: loadingMessage }) })) : isEmpty ? (_jsx("tr", { className: "tui-table__row tui-table__row--empty", children: _jsx("td", { className: "tui-table__cell tui-table__cell--body", colSpan: table.getAllLeafColumns().length, role: "status", children: emptyMessage }) })) : (rows.map((row) => (_jsx("tr", { className: cx('tui-table__row', 'tui-table__row--body', row.getIsSelected() && 'is-selected'), "data-selected": row.getIsSelected() ? 'true' : 'false', children: row.getVisibleCells().map((cell) => (_jsx("td", { className: "tui-table__cell tui-table__cell--body", children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))) }, row.id)))) })] }), renderPagination
182
+ ? renderPagination(table)
183
+ : (_jsx(Pagination, { table: table, mode: paginationMode, showPager: showPager, maxNumbers: paginationMaxNumbers, navStyle: paginationNavStyle, pageSizeOptions: pageSizeOptions }))] }));
184
+ }
@@ -0,0 +1,13 @@
1
+ import type { Table } from '@tanstack/react-table';
2
+ import type { PagerMode } from '../Pager';
3
+ type Props<T> = {
4
+ table: Table<T>;
5
+ pageSizeOptions?: number[];
6
+ showPager?: boolean;
7
+ className?: string;
8
+ mode?: PagerMode;
9
+ maxNumbers?: number;
10
+ navStyle?: 'text' | 'icon';
11
+ };
12
+ export declare function Pagination<T>({ table, pageSizeOptions, showPager, className, mode, maxNumbers, navStyle, }: Props<T>): import("react/jsx-runtime").JSX.Element | null;
13
+ export {};
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from '../../utils/cx.js';
3
+ import { Pager } from '../Pager/index.js';
4
+ export function Pagination({ table, pageSizeOptions = [10, 20, 50], showPager = true, className, mode = 'smart', maxNumbers = 7, navStyle = 'text', }) {
5
+ const { pageIndex, pageSize } = table.getState().pagination;
6
+ const pageCount = Math.max(1, table.getPageCount());
7
+ const currentPage = Math.min(pageCount, Math.max(1, pageIndex + 1));
8
+ // Hide if no rows
9
+ if (table.getRowModel().rows.length === 0) {
10
+ return null;
11
+ }
12
+ return (_jsx(Pager, { currentPage: currentPage, totalPages: pageCount, onPageChange: (page) => table.setPageIndex(page - 1), pageSize: pageSize, pageSizeOptions: pageSizeOptions, onPageSizeChange: (size) => table.setPageSize(size), mode: mode, maxNumbers: maxNumbers, navStyle: navStyle, hidden: !showPager, className: cx('tui-table__pagination', className) }));
13
+ }
@@ -0,0 +1,2 @@
1
+ export { DataTable } from './DataTable';
2
+ export type { DataTableProps } from './DataTable';
@@ -0,0 +1 @@
1
+ export { DataTable } from './DataTable.js';
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import type { TabsProps, TabsListProps, TabProps, TabPanelProps } from './types';
3
+ type TabsCompound = {
4
+ (props: TabsProps): React.JSX.Element;
5
+ displayName?: string;
6
+ List: typeof TabsList;
7
+ Tab: typeof Tab;
8
+ Panel: typeof TabPanel;
9
+ };
10
+ declare function TabsList({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, children, }: TabsListProps): import("react/jsx-runtime").JSX.Element;
11
+ declare namespace TabsList {
12
+ var displayName: string;
13
+ }
14
+ declare function Tab({ value, icon, disabled, className, children }: TabProps): import("react/jsx-runtime").JSX.Element;
15
+ declare namespace Tab {
16
+ var displayName: string;
17
+ }
18
+ declare function TabPanel({ value, className, children }: TabPanelProps): import("react/jsx-runtime").JSX.Element;
19
+ declare namespace TabPanel {
20
+ var displayName: string;
21
+ }
22
+ export declare const Tabs: TabsCompound;
23
+ export {};