@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
|
@@ -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,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 @@
|
|
|
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,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 @@
|
|
|
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 {};
|