@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,219 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React, { useCallback, useEffect, useId, useMemo, useRef, useState, cloneElement, isValidElement, Children, } from 'react';
3
+ import { useFloating, offset, flip, shift, autoUpdate, FloatingPortal, useDismiss, useInteractions, useListNavigation, useRole, FloatingFocusManager, } from '@floating-ui/react';
4
+ import { cx } from '../../utils/cx.js';
5
+ import { getPortalRootFor } from '../../utils/portal.js';
6
+ import { Button } from '../Button/index.js';
7
+ import { DropdownContext, useDropdownContext } from './DropdownContext.js';
8
+ import { toPlacement } from './types.js';
9
+ // =============================================================================
10
+ // Dropdown Root
11
+ // =============================================================================
12
+ function DropdownRoot({ open: controlledOpen, onOpenChange, defaultOpen = false, children, }) {
13
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
14
+ const [activeIndex, setActiveIndex] = useState(null);
15
+ const isControlled = controlledOpen !== undefined;
16
+ const open = isControlled ? controlledOpen : uncontrolledOpen;
17
+ const setOpen = useCallback((nextOpen) => {
18
+ if (!isControlled) {
19
+ setUncontrolledOpen(nextOpen);
20
+ }
21
+ onOpenChange?.(nextOpen);
22
+ if (!nextOpen) {
23
+ setActiveIndex(null);
24
+ }
25
+ }, [isControlled, onOpenChange]);
26
+ const triggerRef = useRef(null);
27
+ const contentId = useId();
28
+ // Focus restoration: track if we need to restore focus on close
29
+ const shouldRestoreFocus = useRef(false);
30
+ const prevOpen = useRef(open);
31
+ useEffect(() => {
32
+ // When opening, mark that we should restore focus on close
33
+ if (open && !prevOpen.current) {
34
+ shouldRestoreFocus.current = true;
35
+ }
36
+ // When closing, restore focus to trigger
37
+ if (!open && prevOpen.current && shouldRestoreFocus.current) {
38
+ triggerRef.current?.focus();
39
+ shouldRestoreFocus.current = false;
40
+ }
41
+ prevOpen.current = open;
42
+ }, [open]);
43
+ const contextValue = useMemo(() => ({
44
+ open,
45
+ setOpen,
46
+ triggerRef,
47
+ contentId,
48
+ activeIndex,
49
+ setActiveIndex,
50
+ }), [open, setOpen, contentId, activeIndex]);
51
+ return (_jsx(DropdownContext.Provider, { value: contextValue, children: children }));
52
+ }
53
+ DropdownRoot.displayName = 'Dropdown';
54
+ // =============================================================================
55
+ // DropdownTrigger
56
+ // =============================================================================
57
+ function DropdownTriggerComponent({ asChild = false, children }) {
58
+ const { open, setOpen, triggerRef, contentId } = useDropdownContext();
59
+ const handleClick = useCallback(() => {
60
+ setOpen(!open);
61
+ }, [open, setOpen]);
62
+ const handleKeyDown = useCallback((e) => {
63
+ if (e.key === 'Enter' || e.key === ' ') {
64
+ e.preventDefault();
65
+ setOpen(!open);
66
+ }
67
+ else if (e.key === 'ArrowDown') {
68
+ e.preventDefault();
69
+ setOpen(true);
70
+ }
71
+ else if (e.key === 'ArrowUp') {
72
+ e.preventDefault();
73
+ setOpen(true);
74
+ }
75
+ }, [open, setOpen]);
76
+ // Merge child ref with our triggerRef
77
+ const setRefs = useCallback((node) => {
78
+ triggerRef.current = node;
79
+ if (asChild && isValidElement(children)) {
80
+ const childRef = children.ref;
81
+ if (typeof childRef === 'function') {
82
+ childRef(node);
83
+ }
84
+ else if (childRef && typeof childRef === 'object') {
85
+ childRef.current = node;
86
+ }
87
+ }
88
+ }, [asChild, children, triggerRef]);
89
+ const triggerProps = {
90
+ onClick: handleClick,
91
+ onKeyDown: handleKeyDown,
92
+ 'aria-haspopup': 'menu',
93
+ 'aria-expanded': open,
94
+ 'aria-controls': open ? contentId : undefined,
95
+ 'data-dropdown-open': open || undefined,
96
+ };
97
+ if (asChild && isValidElement(children)) {
98
+ return cloneElement(children, {
99
+ ...triggerProps,
100
+ ref: setRefs,
101
+ });
102
+ }
103
+ // Non-asChild wrapper needs proper button semantics
104
+ return (_jsx("span", { ref: setRefs, role: "button", tabIndex: 0, ...triggerProps, style: { display: 'inline-flex' }, children: children }));
105
+ }
106
+ DropdownTriggerComponent.displayName = 'Dropdown.Trigger';
107
+ // =============================================================================
108
+ // DropdownContent
109
+ // =============================================================================
110
+ function DropdownContentComponent({ side = 'bottom', align = 'start', sideOffset = 4, className, style, children, }) {
111
+ const { open, setOpen, triggerRef, contentId, activeIndex, setActiveIndex } = useDropdownContext();
112
+ const listRef = useRef([]);
113
+ const { refs, floatingStyles, context } = useFloating({
114
+ placement: toPlacement(side, align),
115
+ open,
116
+ onOpenChange: setOpen,
117
+ middleware: [offset(sideOffset), flip(), shift({ padding: 8 })],
118
+ whileElementsMounted: autoUpdate,
119
+ });
120
+ // Sync reference element
121
+ useEffect(() => {
122
+ if (triggerRef.current) {
123
+ refs.setReference(triggerRef.current);
124
+ }
125
+ }, [triggerRef, refs]);
126
+ // Collect disabled indices from children for keyboard navigation
127
+ const disabledIndices = useMemo(() => {
128
+ const indices = [];
129
+ Children.forEach(children, (child, index) => {
130
+ if (isValidElement(child)) {
131
+ const props = child.props;
132
+ if (props.disabled) {
133
+ indices.push(index);
134
+ }
135
+ }
136
+ });
137
+ return indices;
138
+ }, [children]);
139
+ const dismiss = useDismiss(context);
140
+ const role = useRole(context, { role: 'menu' });
141
+ const listNavigation = useListNavigation(context, {
142
+ listRef,
143
+ activeIndex,
144
+ onNavigate: setActiveIndex,
145
+ loop: true,
146
+ disabledIndices,
147
+ focusItemOnOpen: true,
148
+ });
149
+ const { getFloatingProps, getItemProps } = useInteractions([
150
+ dismiss,
151
+ role,
152
+ listNavigation,
153
+ ]);
154
+ // Get portal root inside .tui-interface
155
+ const portalRoot = getPortalRootFor(triggerRef.current);
156
+ if (!open)
157
+ return null;
158
+ // Clone children to inject item props and role
159
+ const items = Children.map(children, (child, index) => {
160
+ if (!isValidElement(child))
161
+ return child;
162
+ const childProps = child.props;
163
+ const isDisabled = childProps.disabled === true;
164
+ const existingRole = childProps.role;
165
+ return cloneElement(child, {
166
+ ...getItemProps({
167
+ ref: (node) => {
168
+ listRef.current[index] = node;
169
+ },
170
+ // Disabled items get tabIndex -1 always
171
+ tabIndex: isDisabled ? -1 : (activeIndex === index ? 0 : -1),
172
+ }),
173
+ // Add menuitem role if not already specified
174
+ role: existingRole || 'menuitem',
175
+ 'aria-disabled': isDisabled || undefined,
176
+ 'data-active': activeIndex === index || undefined,
177
+ });
178
+ });
179
+ return (_jsx(FloatingPortal, { root: portalRoot, children: _jsx(FloatingFocusManager, { context: context, modal: false, initialFocus: -1, children: _jsx("div", { ref: refs.setFloating, id: contentId, className: cx('tui-dropdown', className), style: {
180
+ ...floatingStyles,
181
+ ...style,
182
+ }, ...getFloatingProps(), children: items }) }) }));
183
+ }
184
+ DropdownContentComponent.displayName = 'Dropdown.Content';
185
+ // =============================================================================
186
+ // DropdownItem
187
+ // =============================================================================
188
+ /**
189
+ * Dropdown menu item. Renders a Button internally with ghost variant.
190
+ * When href is provided, renders as an anchor.
191
+ */
192
+ function DropdownItemComponent({ onSelect, href, target = '_self', disabled = false, keepOpen = false, className, children, ...props }) {
193
+ const { setOpen, triggerRef } = useDropdownContext();
194
+ const handleClick = useCallback(() => {
195
+ if (disabled)
196
+ return;
197
+ onSelect?.();
198
+ if (!keepOpen) {
199
+ setOpen(false);
200
+ // Explicitly restore focus after programmatic close
201
+ // Small delay to let the dropdown unmount first
202
+ requestAnimationFrame(() => {
203
+ triggerRef.current?.focus();
204
+ });
205
+ }
206
+ }, [disabled, onSelect, keepOpen, setOpen, triggerRef]);
207
+ return (_jsx(Button, { variant: "ghost", href: disabled ? undefined : href, target: href ? target : undefined, disabled: disabled, onClick: handleClick, className: cx('tui-dropdown__item', className), ...props, children: children }));
208
+ }
209
+ DropdownItemComponent.displayName = 'Dropdown.Item';
210
+ export const Dropdown = DropdownRoot;
211
+ Dropdown.Trigger = DropdownTriggerComponent;
212
+ Dropdown.Content = DropdownContentComponent;
213
+ Dropdown.Item = DropdownItemComponent;
214
+ // Named exports for direct imports
215
+ export const DropdownTrigger = DropdownTriggerComponent;
216
+ export const DropdownContent = DropdownContentComponent;
217
+ export const DropdownItem = DropdownItemComponent;
218
+ // Hook for advanced use cases (custom items that need to close the dropdown)
219
+ export { useDropdownContext as useDropdown } from './DropdownContext.js';
@@ -0,0 +1,3 @@
1
+ import type { DropdownContextValue } from './types';
2
+ export declare const DropdownContext: import("react").Context<DropdownContextValue | null>;
3
+ export declare function useDropdownContext(): DropdownContextValue;
@@ -0,0 +1,9 @@
1
+ import { createContext, useContext } from 'react';
2
+ export const DropdownContext = createContext(null);
3
+ export function useDropdownContext() {
4
+ const context = useContext(DropdownContext);
5
+ if (!context) {
6
+ throw new Error('Dropdown components must be used within a Dropdown');
7
+ }
8
+ return context;
9
+ }
@@ -0,0 +1,2 @@
1
+ export { Dropdown, DropdownTrigger, DropdownContent, DropdownItem, useDropdown, } from './Dropdown';
2
+ export type { DropdownProps, DropdownTriggerProps, DropdownContentProps, DropdownItemProps, } from './types';
@@ -0,0 +1 @@
1
+ export { Dropdown, DropdownTrigger, DropdownContent, DropdownItem, useDropdown, } from './Dropdown.js';
@@ -0,0 +1,102 @@
1
+ import type { Placement } from '@floating-ui/react';
2
+ /**
3
+ * Dropdown placement sides.
4
+ */
5
+ export type Side = 'top' | 'right' | 'bottom' | 'left';
6
+ /**
7
+ * Dropdown alignment relative to trigger.
8
+ */
9
+ export type Align = 'start' | 'center' | 'end';
10
+ export type DropdownProps = {
11
+ /**
12
+ * Controlled open state.
13
+ */
14
+ open?: boolean;
15
+ /**
16
+ * Callback when open state changes.
17
+ */
18
+ onOpenChange?: (open: boolean) => void;
19
+ /**
20
+ * Default open state for uncontrolled usage.
21
+ * @default false
22
+ */
23
+ defaultOpen?: boolean;
24
+ children: React.ReactNode;
25
+ };
26
+ export type DropdownTriggerProps = {
27
+ /**
28
+ * When true, merges props onto the child element instead of wrapping in a span.
29
+ * Child must be a single React element that accepts ref and event handlers.
30
+ * @default false
31
+ */
32
+ asChild?: boolean;
33
+ children: React.ReactNode;
34
+ };
35
+ export type DropdownContentProps = {
36
+ /**
37
+ * Preferred side of the trigger to place the dropdown.
38
+ * @default 'bottom'
39
+ */
40
+ side?: Side;
41
+ /**
42
+ * Alignment along the side.
43
+ * @default 'start'
44
+ */
45
+ align?: Align;
46
+ /**
47
+ * Distance from the trigger in pixels.
48
+ * @default 4
49
+ */
50
+ sideOffset?: number;
51
+ /**
52
+ * Additional CSS class names.
53
+ */
54
+ className?: string;
55
+ /**
56
+ * Inline styles (use sparingly, prefer className).
57
+ */
58
+ style?: React.CSSProperties;
59
+ children: React.ReactNode;
60
+ };
61
+ export type DropdownItemProps = {
62
+ /**
63
+ * Click handler for the item.
64
+ */
65
+ onSelect?: () => void;
66
+ /**
67
+ * URL to navigate to. When provided, renders as an anchor.
68
+ */
69
+ href?: string;
70
+ /**
71
+ * Link target (only applies when href is provided).
72
+ * @default '_self'
73
+ */
74
+ target?: string;
75
+ /**
76
+ * Whether the item is disabled.
77
+ * @default false
78
+ */
79
+ disabled?: boolean;
80
+ /**
81
+ * When true, keeps the dropdown open after selection.
82
+ * @default false
83
+ */
84
+ keepOpen?: boolean;
85
+ /**
86
+ * Additional CSS class names.
87
+ */
88
+ className?: string;
89
+ children: React.ReactNode;
90
+ };
91
+ export type DropdownContextValue = {
92
+ open: boolean;
93
+ setOpen: (open: boolean) => void;
94
+ triggerRef: React.RefObject<HTMLElement | null>;
95
+ contentId: string;
96
+ activeIndex: number | null;
97
+ setActiveIndex: (index: number | null) => void;
98
+ };
99
+ /**
100
+ * Convert side + align to Floating UI placement.
101
+ */
102
+ export declare function toPlacement(side: Side, align: Align): Placement;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Convert side + align to Floating UI placement.
3
+ */
4
+ export function toPlacement(side, align) {
5
+ if (align === 'center')
6
+ return side;
7
+ return `${side}-${align}`;
8
+ }
@@ -0,0 +1,22 @@
1
+ import * as React from 'react';
2
+ import type { IconName } from '../../icons/registry';
3
+ import type { SizeExtended } from '../../types';
4
+ export interface IconProps {
5
+ /** Icon name from registry (e.g., "system/check") */
6
+ name?: IconName;
7
+ /** Emoji or unicode character (alternative to name) */
8
+ emoji?: string;
9
+ /** Accessible label for informative icons. Required if icon conveys meaning. */
10
+ label?: string;
11
+ /** Icon size. Defaults to inherit from parent font-size. */
12
+ size?: SizeExtended;
13
+ className?: string;
14
+ }
15
+ /**
16
+ * Icon component for SVG icons from the registry or emoji characters.
17
+ *
18
+ * Accessibility:
19
+ * - Decorative icons (no label): automatically hidden from screen readers
20
+ * - Informative icons: provide a `label` prop for screen reader announcement
21
+ */
22
+ export declare const Icon: React.ForwardRefExoticComponent<IconProps & React.RefAttributes<HTMLSpanElement>>;
@@ -0,0 +1,24 @@
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 { iconRegistry } from '../../icons/registry.js';
5
+ /**
6
+ * Icon component for SVG icons from the registry or emoji characters.
7
+ *
8
+ * Accessibility:
9
+ * - Decorative icons (no label): automatically hidden from screen readers
10
+ * - Informative icons: provide a `label` prop for screen reader announcement
11
+ */
12
+ export const Icon = React.forwardRef(({ name, emoji, label, size, className }, ref) => {
13
+ const SvgIcon = name ? iconRegistry[name] : null;
14
+ // Dev warning for invalid icon name
15
+ if (import.meta.env.DEV && name && !SvgIcon) {
16
+ console.warn(`[Icon] Unknown icon name: "${name}". Check the registry.`);
17
+ }
18
+ // Decorative if no label provided
19
+ const isDecorative = !label;
20
+ return (_jsxs("span", { ref: ref, className: cx('tui-icon', size && `is-size-${size}`, className), ...(isDecorative
21
+ ? { 'aria-hidden': true }
22
+ : { role: 'img', 'aria-label': label }), children: [SvgIcon && _jsx(SvgIcon, { "aria-hidden": "true", focusable: "false" }), !SvgIcon && emoji] }));
23
+ });
24
+ Icon.displayName = 'Icon';
@@ -0,0 +1,2 @@
1
+ export { Icon } from './Icon';
2
+ export type { IconProps } from './Icon';
@@ -0,0 +1 @@
1
+ export { Icon } from './Icon.js';
@@ -0,0 +1,2 @@
1
+ import type { IconButtonProps } from './types';
2
+ export declare const IconButton: import("react").ForwardRefExoticComponent<IconButtonProps & import("react").RefAttributes<HTMLButtonElement | HTMLAnchorElement>>;
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { cx } from '../../utils/cx.js';
4
+ import { Icon } from '../Icon/index.js';
5
+ import { Tooltip } from '../Tooltip/index.js';
6
+ import { getSafeRel } from '../../utils/polymorphic.js';
7
+ // =============================================================================
8
+ // IconButton Component
9
+ // =============================================================================
10
+ //
11
+ // Square button containing only an icon. Requires an accessible label since
12
+ // there's no visible text. Use for toolbar actions, close buttons, navigation
13
+ // controls, etc.
14
+ //
15
+ // Renders as <button> by default, or <a> when href is provided.
16
+ // Set showTooltip to display the label on hover for sighted users.
17
+ //
18
+ // =============================================================================
19
+ export const IconButton = forwardRef((props, ref) => {
20
+ const { icon, label, size = 'sm', theme = 'secondary', variant = 'ghost', disabled = false, loading = false, pressed, showTooltip = false, tooltipSide = 'top', className, ...rest } = props;
21
+ const isLink = typeof rest.href === 'string';
22
+ const isDisabled = disabled || loading;
23
+ const classes = cx('tui-icon-button', `is-size-${size}`, `is-theme-${theme}`, `is-style-${variant}`, isDisabled && 'is-disabled', className);
24
+ const iconContent = (_jsxs(_Fragment, { children: [_jsx(Icon, { name: icon }), loading && _jsx("span", { className: "tui-icon-button__spinner", "aria-hidden": "true" })] }));
25
+ // Render the base element (button or anchor)
26
+ let element;
27
+ if (isLink) {
28
+ const { href, target, rel, tabIndex, onClick, ...anchorRest } = rest;
29
+ const safeRel = getSafeRel(target, rel);
30
+ const handleClick = (e) => {
31
+ if (isDisabled) {
32
+ e.preventDefault();
33
+ e.stopPropagation();
34
+ return;
35
+ }
36
+ onClick?.(e);
37
+ };
38
+ element = (_jsx("a", { ref: ref, href: isDisabled ? undefined : href, className: classes, "aria-label": label, "aria-disabled": isDisabled || undefined, "aria-busy": loading || undefined, tabIndex: isDisabled ? -1 : tabIndex, onClick: handleClick, target: target, rel: safeRel, ...anchorRest, children: iconContent }));
39
+ }
40
+ else {
41
+ const buttonRest = rest;
42
+ element = (_jsx("button", { ref: ref, type: buttonRest.type ?? 'button', className: classes, "aria-label": label, disabled: isDisabled, "aria-busy": loading || undefined, "aria-pressed": pressed, ...buttonRest, children: iconContent }));
43
+ }
44
+ // Wrap in tooltip if requested
45
+ if (showTooltip) {
46
+ return (_jsxs(Tooltip, { children: [_jsx(Tooltip.Trigger, { asChild: true, children: element }), _jsx(Tooltip.Content, { side: tooltipSide, children: label })] }));
47
+ }
48
+ return element;
49
+ });
50
+ IconButton.displayName = 'IconButton';
@@ -0,0 +1,2 @@
1
+ export { IconButton } from './IconButton';
2
+ export type { IconButtonProps, Size, Theme, Variant } from './types';
@@ -0,0 +1 @@
1
+ export { IconButton } from './IconButton.js';
@@ -0,0 +1,79 @@
1
+ import type { IconName } from '../../icons/registry';
2
+ import type { Size, ThemeIntent } from '../../types';
3
+ export type { Size };
4
+ export type Theme = ThemeIntent;
5
+ export type Variant = 'solid' | 'outline' | 'ghost';
6
+ type IconButtonBaseProps = {
7
+ /**
8
+ * Icon to display. Required — this is the button's visual content.
9
+ * @example icon="system/close"
10
+ */
11
+ icon: IconName;
12
+ /**
13
+ * Accessible label. Required — icon-only buttons must have a label
14
+ * for screen readers since there's no visible text.
15
+ * @example label="Close modal"
16
+ */
17
+ label: string;
18
+ /**
19
+ * Size of the button.
20
+ * - `'xs'`: 24px
21
+ * - `'sm'`: 32px (default)
22
+ * - `'md'`: 40px
23
+ * - `'lg'`: 48px
24
+ * @default 'sm'
25
+ */
26
+ size?: Size;
27
+ /**
28
+ * Semantic color theme.
29
+ * Note: Defaults to 'secondary' (unlike Button which defaults to 'primary')
30
+ * because IconButtons are typically used for toolbar/utility actions.
31
+ * @default 'secondary'
32
+ */
33
+ theme?: Theme;
34
+ /**
35
+ * Visual style variant.
36
+ * - `'solid'`: Filled background
37
+ * - `'outline'`: Border only, transparent background
38
+ * - `'ghost'`: No border or background, icon only
39
+ * @default 'ghost'
40
+ */
41
+ variant?: Variant;
42
+ /**
43
+ * Disabled state.
44
+ */
45
+ disabled?: boolean;
46
+ /**
47
+ * Loading state — shows spinner, disables interaction.
48
+ */
49
+ loading?: boolean;
50
+ /**
51
+ * Pressed state for toggle buttons.
52
+ * When true, applies aria-pressed="true" and active styling.
53
+ * @example pressed={isMuted}
54
+ */
55
+ pressed?: boolean;
56
+ /**
57
+ * Show the label as a tooltip on hover.
58
+ * Since icon-only buttons have no visible text, this helps sighted
59
+ * users discover what the button does.
60
+ * @default false
61
+ */
62
+ showTooltip?: boolean;
63
+ /**
64
+ * Tooltip placement side.
65
+ * @default 'top'
66
+ */
67
+ tooltipSide?: 'top' | 'right' | 'bottom' | 'left';
68
+ /**
69
+ * Additional CSS class names.
70
+ */
71
+ className?: string;
72
+ };
73
+ export type IconButtonAsButton = IconButtonBaseProps & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'aria-label'> & {
74
+ href?: undefined;
75
+ };
76
+ export type IconButtonAsAnchor = IconButtonBaseProps & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children' | 'aria-label'> & {
77
+ href: string;
78
+ };
79
+ export type IconButtonProps = IconButtonAsButton | IconButtonAsAnchor;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import type { SizeStandard } from '../../types';
3
+ type Size = SizeStandard;
4
+ export type ModalProps = {
5
+ isOpen: boolean;
6
+ onClose: () => void;
7
+ size?: Size;
8
+ stickyHead?: boolean;
9
+ stickyFoot?: boolean;
10
+ labelledBy?: string;
11
+ describedBy?: string;
12
+ initialFocusSelector?: string;
13
+ container?: Element | null;
14
+ showCloseButton?: boolean;
15
+ closeLabel?: string;
16
+ closeOnBackdropClick?: boolean;
17
+ closeOnEscape?: boolean;
18
+ children?: React.ReactNode;
19
+ };
20
+ declare function ModalRoot({ isOpen, onClose, size, stickyHead, stickyFoot, labelledBy, describedBy, initialFocusSelector, container, showCloseButton, closeLabel, closeOnBackdropClick, closeOnEscape, children, }: ModalProps): React.ReactPortal | null;
21
+ type ModalCloseProps = {
22
+ label?: string;
23
+ className?: string;
24
+ };
25
+ declare function ModalClose({ label, className }: ModalCloseProps): import("react/jsx-runtime").JSX.Element;
26
+ declare namespace ModalClose {
27
+ var displayName: string;
28
+ }
29
+ type ModalSlotProps = {
30
+ className?: string;
31
+ children?: React.ReactNode;
32
+ } & React.HTMLAttributes<HTMLDivElement>;
33
+ declare function ModalHead({ className, children, ...rest }: ModalSlotProps): import("react/jsx-runtime").JSX.Element;
34
+ declare namespace ModalHead {
35
+ var displayName: string;
36
+ }
37
+ declare function ModalBody({ className, children, ...rest }: ModalSlotProps): import("react/jsx-runtime").JSX.Element;
38
+ declare namespace ModalBody {
39
+ var displayName: string;
40
+ }
41
+ declare function ModalFoot({ className, children, ...rest }: ModalSlotProps): import("react/jsx-runtime").JSX.Element;
42
+ declare namespace ModalFoot {
43
+ var displayName: string;
44
+ }
45
+ type ModalCompound = typeof ModalRoot & {
46
+ Head: typeof ModalHead;
47
+ Body: typeof ModalBody;
48
+ Foot: typeof ModalFoot;
49
+ Close: typeof ModalClose;
50
+ };
51
+ export declare const Modal: ModalCompound;
52
+ export {};