@tuuject/koi-react 0.0.0-stage → 0.1.0

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 (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -2
  3. package/dist/alert/Alert.d.ts +13 -0
  4. package/dist/alert/Alert.js +16 -0
  5. package/dist/avatar/Avatar.d.ts +7 -0
  6. package/dist/avatar/Avatar.js +10 -0
  7. package/dist/badge/Badge.d.ts +7 -0
  8. package/dist/badge/Badge.js +8 -0
  9. package/dist/button/Button.d.ts +22 -0
  10. package/dist/button/Button.js +28 -0
  11. package/dist/card/Card.d.ts +9 -0
  12. package/dist/card/Card.js +8 -0
  13. package/dist/chat-bubble/ChatBubble.d.ts +7 -0
  14. package/dist/chat-bubble/ChatBubble.js +7 -0
  15. package/dist/checkbox/Checkbox.d.ts +18 -0
  16. package/dist/checkbox/Checkbox.js +25 -0
  17. package/dist/dialog/Dialog.d.ts +13 -0
  18. package/dist/dialog/Dialog.js +38 -0
  19. package/dist/divider/Divider.d.ts +6 -0
  20. package/dist/divider/Divider.js +17 -0
  21. package/dist/field/Field.d.ts +14 -0
  22. package/dist/field/Field.js +19 -0
  23. package/dist/field/context.d.ts +5 -0
  24. package/dist/field/context.js +2 -0
  25. package/dist/icons.d.ts +26 -0
  26. package/dist/icons.js +46 -0
  27. package/dist/index.d.ts +31 -0
  28. package/dist/index.js +30 -0
  29. package/dist/input/Input.d.ts +9 -0
  30. package/dist/input/Input.js +15 -0
  31. package/dist/link/Link.d.ts +11 -0
  32. package/dist/link/Link.js +17 -0
  33. package/dist/pagination/Pagination.d.ts +7 -0
  34. package/dist/pagination/Pagination.js +26 -0
  35. package/dist/panda/buildinfo.json +10442 -0
  36. package/dist/panda/lib.json +18 -0
  37. package/dist/panda/preset.mjs +3907 -0
  38. package/dist/progress/Progress.d.ts +7 -0
  39. package/dist/progress/Progress.js +17 -0
  40. package/dist/prose/Prose.d.ts +10 -0
  41. package/dist/prose/Prose.js +10 -0
  42. package/dist/radio-group/RadioGroup.d.ts +15 -0
  43. package/dist/radio-group/RadioGroup.js +26 -0
  44. package/dist/select/Select.d.ts +11 -0
  45. package/dist/select/Select.js +21 -0
  46. package/dist/select/SelectPanel.d.ts +24 -0
  47. package/dist/select/SelectPanel.js +105 -0
  48. package/dist/shape.d.ts +16 -0
  49. package/dist/shape.js +17 -0
  50. package/dist/skeleton/Skeleton.d.ts +8 -0
  51. package/dist/skeleton/Skeleton.js +8 -0
  52. package/dist/spinner/Spinner.d.ts +7 -0
  53. package/dist/spinner/Spinner.js +13 -0
  54. package/dist/switch/Switch.d.ts +22 -0
  55. package/dist/switch/Switch.js +23 -0
  56. package/dist/table/DataTable.d.ts +12 -0
  57. package/dist/table/DataTable.js +12 -0
  58. package/dist/table/Table.d.ts +38 -0
  59. package/dist/table/Table.js +45 -0
  60. package/dist/tabs/Tabs.d.ts +11 -0
  61. package/dist/tabs/Tabs.js +20 -0
  62. package/dist/tag/Tag.d.ts +6 -0
  63. package/dist/tag/Tag.js +15 -0
  64. package/dist/textarea/Textarea.d.ts +9 -0
  65. package/dist/textarea/Textarea.js +14 -0
  66. package/dist/toast/Toast.d.ts +23 -0
  67. package/dist/toast/Toast.js +80 -0
  68. package/dist/tooltip/Tooltip.d.ts +9 -0
  69. package/dist/tooltip/Tooltip.js +29 -0
  70. package/dist/types.d.ts +11 -0
  71. package/dist/types.js +1 -0
  72. package/package.json +218 -3
  73. package/styled-system/css/conditions.d.ts +7 -0
  74. package/styled-system/css/conditions.js +32 -0
  75. package/styled-system/css/css.d.ts +22 -0
  76. package/styled-system/css/css.js +63 -0
  77. package/styled-system/css/cva.d.ts +5 -0
  78. package/styled-system/css/cva.js +51 -0
  79. package/styled-system/css/cx.d.ts +6 -0
  80. package/styled-system/css/cx.js +16 -0
  81. package/styled-system/css/first-that-works.d.ts +22 -0
  82. package/styled-system/css/first-that-works.js +1 -0
  83. package/styled-system/css/index.d.ts +8 -0
  84. package/styled-system/css/index.js +8 -0
  85. package/styled-system/css/keyframes.d.ts +5 -0
  86. package/styled-system/css/keyframes.js +8 -0
  87. package/styled-system/css/position-try.d.ts +5 -0
  88. package/styled-system/css/position-try.js +11 -0
  89. package/styled-system/css/sva.d.ts +3 -0
  90. package/styled-system/css/sva.js +49 -0
  91. package/styled-system/css/view-transition.d.ts +7 -0
  92. package/styled-system/css/view-transition.js +18 -0
  93. package/styled-system/helpers.d.ts +67 -0
  94. package/styled-system/helpers.js +624 -0
  95. package/styled-system/jsx/aspect-ratio.d.ts +7 -0
  96. package/styled-system/jsx/aspect-ratio.js +11 -0
  97. package/styled-system/jsx/bleed.d.ts +7 -0
  98. package/styled-system/jsx/bleed.js +11 -0
  99. package/styled-system/jsx/box.d.ts +7 -0
  100. package/styled-system/jsx/box.js +11 -0
  101. package/styled-system/jsx/center.d.ts +7 -0
  102. package/styled-system/jsx/center.js +11 -0
  103. package/styled-system/jsx/circle.d.ts +7 -0
  104. package/styled-system/jsx/circle.js +11 -0
  105. package/styled-system/jsx/container.d.ts +7 -0
  106. package/styled-system/jsx/container.js +11 -0
  107. package/styled-system/jsx/cq.d.ts +7 -0
  108. package/styled-system/jsx/cq.js +11 -0
  109. package/styled-system/jsx/create-recipe-context.d.ts +32 -0
  110. package/styled-system/jsx/create-recipe-context.js +38 -0
  111. package/styled-system/jsx/create-slot-recipe-context.d.ts +48 -0
  112. package/styled-system/jsx/create-slot-recipe-context.js +116 -0
  113. package/styled-system/jsx/divider.d.ts +7 -0
  114. package/styled-system/jsx/divider.js +11 -0
  115. package/styled-system/jsx/factory.d.ts +3 -0
  116. package/styled-system/jsx/factory.js +91 -0
  117. package/styled-system/jsx/flex.d.ts +7 -0
  118. package/styled-system/jsx/flex.js +11 -0
  119. package/styled-system/jsx/float.d.ts +7 -0
  120. package/styled-system/jsx/float.js +11 -0
  121. package/styled-system/jsx/grid-item.d.ts +7 -0
  122. package/styled-system/jsx/grid-item.js +11 -0
  123. package/styled-system/jsx/grid.d.ts +7 -0
  124. package/styled-system/jsx/grid.js +11 -0
  125. package/styled-system/jsx/helper.d.ts +34 -0
  126. package/styled-system/jsx/helper.js +83 -0
  127. package/styled-system/jsx/hstack.d.ts +7 -0
  128. package/styled-system/jsx/hstack.js +11 -0
  129. package/styled-system/jsx/index.d.ts +25 -0
  130. package/styled-system/jsx/index.js +24 -0
  131. package/styled-system/jsx/is-valid-prop.d.ts +7 -0
  132. package/styled-system/jsx/is-valid-prop.js +12 -0
  133. package/styled-system/jsx/link-overlay.d.ts +7 -0
  134. package/styled-system/jsx/link-overlay.js +11 -0
  135. package/styled-system/jsx/spacer.d.ts +7 -0
  136. package/styled-system/jsx/spacer.js +11 -0
  137. package/styled-system/jsx/square.d.ts +7 -0
  138. package/styled-system/jsx/square.js +11 -0
  139. package/styled-system/jsx/stack.d.ts +7 -0
  140. package/styled-system/jsx/stack.js +11 -0
  141. package/styled-system/jsx/visually-hidden.d.ts +7 -0
  142. package/styled-system/jsx/visually-hidden.js +11 -0
  143. package/styled-system/jsx/vstack.d.ts +7 -0
  144. package/styled-system/jsx/vstack.js +11 -0
  145. package/styled-system/jsx/wrap.d.ts +7 -0
  146. package/styled-system/jsx/wrap.js +11 -0
  147. package/styled-system/patterns/aspect-ratio.d.ts +19 -0
  148. package/styled-system/patterns/aspect-ratio.js +37 -0
  149. package/styled-system/patterns/bleed.d.ts +20 -0
  150. package/styled-system/patterns/bleed.js +30 -0
  151. package/styled-system/patterns/box.d.ts +17 -0
  152. package/styled-system/patterns/box.js +16 -0
  153. package/styled-system/patterns/center.d.ts +19 -0
  154. package/styled-system/patterns/center.js +22 -0
  155. package/styled-system/patterns/circle.d.ts +19 -0
  156. package/styled-system/patterns/circle.js +26 -0
  157. package/styled-system/patterns/container.d.ts +17 -0
  158. package/styled-system/patterns/container.js +26 -0
  159. package/styled-system/patterns/cq.d.ts +21 -0
  160. package/styled-system/patterns/cq.js +24 -0
  161. package/styled-system/patterns/divider.d.ts +22 -0
  162. package/styled-system/patterns/divider.js +31 -0
  163. package/styled-system/patterns/flex.d.ts +25 -0
  164. package/styled-system/patterns/flex.js +27 -0
  165. package/styled-system/patterns/float.d.ts +23 -0
  166. package/styled-system/patterns/float.js +84 -0
  167. package/styled-system/patterns/grid-item.d.ts +24 -0
  168. package/styled-system/patterns/grid-item.js +26 -0
  169. package/styled-system/patterns/grid.d.ts +24 -0
  170. package/styled-system/patterns/grid.js +30 -0
  171. package/styled-system/patterns/hstack.d.ts +20 -0
  172. package/styled-system/patterns/hstack.js +27 -0
  173. package/styled-system/patterns/index.d.ts +20 -0
  174. package/styled-system/patterns/index.js +20 -0
  175. package/styled-system/patterns/link-overlay.d.ts +17 -0
  176. package/styled-system/patterns/link-overlay.js +25 -0
  177. package/styled-system/patterns/runtime.js +25 -0
  178. package/styled-system/patterns/spacer.d.ts +20 -0
  179. package/styled-system/patterns/spacer.js +26 -0
  180. package/styled-system/patterns/square.d.ts +19 -0
  181. package/styled-system/patterns/square.js +25 -0
  182. package/styled-system/patterns/stack.d.ts +22 -0
  183. package/styled-system/patterns/stack.js +30 -0
  184. package/styled-system/patterns/visually-hidden.d.ts +17 -0
  185. package/styled-system/patterns/visually-hidden.js +19 -0
  186. package/styled-system/patterns/vstack.d.ts +20 -0
  187. package/styled-system/patterns/vstack.js +27 -0
  188. package/styled-system/patterns/wrap.d.ts +23 -0
  189. package/styled-system/patterns/wrap.js +26 -0
  190. package/styled-system/recipes/alert.d.ts +19 -0
  191. package/styled-system/recipes/alert.js +5 -0
  192. package/styled-system/recipes/avatar.d.ts +19 -0
  193. package/styled-system/recipes/avatar.js +5 -0
  194. package/styled-system/recipes/badge.d.ts +18 -0
  195. package/styled-system/recipes/badge.js +5 -0
  196. package/styled-system/recipes/button.d.ts +20 -0
  197. package/styled-system/recipes/button.js +5 -0
  198. package/styled-system/recipes/card.d.ts +19 -0
  199. package/styled-system/recipes/card.js +5 -0
  200. package/styled-system/recipes/chat-bubble.d.ts +16 -0
  201. package/styled-system/recipes/chat-bubble.js +5 -0
  202. package/styled-system/recipes/checkbox.d.ts +16 -0
  203. package/styled-system/recipes/checkbox.js +5 -0
  204. package/styled-system/recipes/dialog.d.ts +18 -0
  205. package/styled-system/recipes/dialog.js +5 -0
  206. package/styled-system/recipes/field.d.ts +16 -0
  207. package/styled-system/recipes/field.js +5 -0
  208. package/styled-system/recipes/index.d.ts +27 -0
  209. package/styled-system/recipes/index.js +27 -0
  210. package/styled-system/recipes/input.d.ts +18 -0
  211. package/styled-system/recipes/input.js +5 -0
  212. package/styled-system/recipes/link.d.ts +18 -0
  213. package/styled-system/recipes/link.js +5 -0
  214. package/styled-system/recipes/pagination.d.ts +19 -0
  215. package/styled-system/recipes/pagination.js +5 -0
  216. package/styled-system/recipes/progress.d.ts +18 -0
  217. package/styled-system/recipes/progress.js +5 -0
  218. package/styled-system/recipes/prose.d.ts +16 -0
  219. package/styled-system/recipes/prose.js +5 -0
  220. package/styled-system/recipes/radio-group.d.ts +18 -0
  221. package/styled-system/recipes/radio-group.js +5 -0
  222. package/styled-system/recipes/runtime.js +133 -0
  223. package/styled-system/recipes/select-panel.d.ts +18 -0
  224. package/styled-system/recipes/select-panel.js +5 -0
  225. package/styled-system/recipes/select.d.ts +18 -0
  226. package/styled-system/recipes/select.js +5 -0
  227. package/styled-system/recipes/separator.d.ts +16 -0
  228. package/styled-system/recipes/separator.js +5 -0
  229. package/styled-system/recipes/skeleton.d.ts +14 -0
  230. package/styled-system/recipes/skeleton.js +5 -0
  231. package/styled-system/recipes/spinner.d.ts +16 -0
  232. package/styled-system/recipes/spinner.js +5 -0
  233. package/styled-system/recipes/switch-recipe.d.ts +16 -0
  234. package/styled-system/recipes/switch-recipe.js +5 -0
  235. package/styled-system/recipes/table.d.ts +20 -0
  236. package/styled-system/recipes/table.js +5 -0
  237. package/styled-system/recipes/tabs.d.ts +18 -0
  238. package/styled-system/recipes/tabs.js +5 -0
  239. package/styled-system/recipes/tag.d.ts +18 -0
  240. package/styled-system/recipes/tag.js +5 -0
  241. package/styled-system/recipes/textarea.d.ts +14 -0
  242. package/styled-system/recipes/textarea.js +5 -0
  243. package/styled-system/recipes/toast.d.ts +20 -0
  244. package/styled-system/recipes/toast.js +5 -0
  245. package/styled-system/recipes/tooltip.d.ts +16 -0
  246. package/styled-system/recipes/tooltip.js +5 -0
  247. package/styled-system/tokens/index.d.ts +8 -0
  248. package/styled-system/tokens/index.js +18 -0
  249. package/styled-system/types/index.d.ts +5 -0
  250. package/styled-system/types/jsx.d.ts +80 -0
  251. package/styled-system/types/pattern.d.ts +26 -0
  252. package/styled-system/types/recipe.d.ts +77 -0
  253. package/styled-system/types/system.d.ts +1645 -0
  254. package/styled-system/types/tokens.d.ts +47 -0
@@ -0,0 +1,7 @@
1
+ import { type ProgressOwnProps } from '@tuuject/koi-core';
2
+ import type { ComponentPropsWithRef } from 'react';
3
+ export type { ProgressSize } from '@tuuject/koi-core';
4
+ export interface ProgressProps extends ProgressOwnProps, Omit<ComponentPropsWithRef<'div'>, keyof ProgressOwnProps | 'children'> {
5
+ }
6
+ /** A rounded progress bar. Base UI's root is the `role="progressbar"` element and carries the aria-value attributes. */
7
+ export declare function Progress({ value, max, label, showValue, size, className, ...rest }: ProgressProps): import("react").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Progress as BaseProgress } from '@base-ui/react/progress';
3
+ import { strings } from '@tuuject/koi-core';
4
+ import { cx } from '@tuuject/koi-react/styled-system/css';
5
+ import { progress } from '@tuuject/koi-react/styled-system/recipes';
6
+ /** A rounded progress bar. Base UI's root is the `role="progressbar"` element and carries the aria-value attributes. */
7
+ export function Progress({ value, max, label, showValue, size = 'md', className, ...rest }) {
8
+ const s = progress({ size });
9
+ const known = value != null && Number.isFinite(value);
10
+ // `max || 100` as legacy, so max={0} can't divide by zero.
11
+ const top = max || 100;
12
+ const pct = known ? Math.max(0, Math.min(100, (value / top) * 100)) : 0;
13
+ const shownValue = showValue && known;
14
+ return (_jsxs(BaseProgress.Root, { value: known ? value : null, max: top, "aria-label": label ? undefined : strings.progress,
15
+ // Base UI's default text is English ("indeterminate progress"); the percentage stays as it formats it.
16
+ getAriaValueText: (formatted) => (known ? formatted : strings.inProgress), ...rest, className: cx(s.root, className), children: [label || shownValue ? (_jsxs("div", { className: s.head, children: [label ? _jsx(BaseProgress.Label, { className: s.label, children: label }) : null, shownValue ? _jsxs("span", { className: s.value, children: [Math.round(pct), "%"] }) : null] })) : null, _jsx(BaseProgress.Track, { className: s.track, children: _jsx(BaseProgress.Indicator, { className: s.indicator }) })] }));
17
+ }
@@ -0,0 +1,10 @@
1
+ import type { ProseOwnProps } from '@tuuject/koi-core';
2
+ import type { ComponentPropsWithRef } from 'react';
3
+ export type { ProseSize } from '@tuuject/koi-core';
4
+ export interface ProseProps extends ProseOwnProps, Omit<ComponentPropsWithRef<'div'>, keyof ProseOwnProps> {
5
+ }
6
+ /**
7
+ * Styles rendered rich text (markdown output, CMS HTML) in Koi's long-form type. Everything inside is sized in em,
8
+ * so `size` (or the consumer's own font size) scales the whole block. Wrap embedded components in `koi-not-prose`.
9
+ */
10
+ export declare function Prose({ size, className, ...rest }: ProseProps): import("react").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from '@tuuject/koi-react/styled-system/css';
3
+ import { prose } from '@tuuject/koi-react/styled-system/recipes';
4
+ /**
5
+ * Styles rendered rich text (markdown output, CMS HTML) in Koi's long-form type. Everything inside is sized in em,
6
+ * so `size` (or the consumer's own font size) scales the whole block. Wrap embedded components in `koi-not-prose`.
7
+ */
8
+ export function Prose({ size = 'md', className, ...rest }) {
9
+ return _jsx("div", { ...rest, className: cx(prose({ size }), className) });
10
+ }
@@ -0,0 +1,15 @@
1
+ import type { RadioGroupOwnProps } from '@tuuject/koi-core';
2
+ import { type ComponentPropsWithRef } from 'react';
3
+ export type { RadioOption } from '@tuuject/koi-core';
4
+ export interface RadioGroupProps extends RadioGroupOwnProps<Event>, Omit<ComponentPropsWithRef<'div'>, keyof RadioGroupOwnProps | 'defaultValue' | 'children' | 'aria-label'> {
5
+ /** Submitted with the form as `name=value`. */
6
+ name?: string;
7
+ /** A choice must be made before the form submits. Taken from the Field when omitted. */
8
+ required?: boolean;
9
+ }
10
+ /**
11
+ * 2–5 mutually exclusive options, all visible. Arrow keys move the choice; Tab enters and leaves the group.
12
+ * Each radio is Base UI's `<span role="radio">` plus a hidden `<input type="radio">`; the whole row is the click
13
+ * target. Inside a Field it's named by the Field's label and picks up its error, help, `required` and `id`.
14
+ */
15
+ export declare function RadioGroup({ options, value, defaultValue, onChange, label, name, orientation, disabled, required, id, className, ...rest }: RadioGroupProps): import("react").JSX.Element;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Field as BaseField } from '@base-ui/react/field';
3
+ import { Radio } from '@base-ui/react/radio';
4
+ import { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group';
5
+ import { cx } from '@tuuject/koi-react/styled-system/css';
6
+ import { radioGroup } from '@tuuject/koi-react/styled-system/recipes';
7
+ import { useContext, useId } from 'react';
8
+ import { KoiFieldContext } from '../field/context.js';
9
+ /**
10
+ * 2–5 mutually exclusive options, all visible. Arrow keys move the choice; Tab enters and leaves the group.
11
+ * Each radio is Base UI's `<span role="radio">` plus a hidden `<input type="radio">`; the whole row is the click
12
+ * target. Inside a Field it's named by the Field's label and picks up its error, help, `required` and `id`.
13
+ */
14
+ export function RadioGroup({ options, value, defaultValue, onChange, label, name, orientation = 'vertical', disabled, required, id, className, ...rest }) {
15
+ const f = useContext(KoiFieldContext);
16
+ const s = radioGroup({ orientation });
17
+ const uid = useId();
18
+ // Inside a Field each row needs its own Field.Item: otherwise every radio takes the Field's label as its name
19
+ // and the Field's control id as its input id.
20
+ const Row = f ? BaseField.Item : 'label';
21
+ return (_jsx(BaseRadioGroup, { ...rest, id: id ?? f?.id, "aria-label": label, value: value, defaultValue: defaultValue, onValueChange: onChange, name: name, disabled: disabled, required: required ?? f?.required, className: cx(s.root, className), children: options.map((o, i) => {
22
+ const labelId = `${uid}-${i}-label`;
23
+ const descId = o.description ? `${uid}-${i}-desc` : undefined;
24
+ return (_jsxs(Row, { className: s.item, ...(f ? { render: _jsx("label", {}) } : null), children: [_jsx(Radio.Root, { value: o.value, disabled: o.disabled, "aria-labelledby": labelId, "aria-describedby": descId, className: s.control, children: _jsx(Radio.Indicator, { keepMounted: true, className: s.indicator }) }), _jsxs("span", { className: s.text, children: [_jsx("span", { id: labelId, className: s.label, children: o.label }), descId ? (_jsx("span", { id: descId, className: s.description, children: o.description })) : null] })] }, o.value));
25
+ }) }));
26
+ }
@@ -0,0 +1,11 @@
1
+ import type { SelectOwnProps } from '@tuuject/koi-core';
2
+ import { type ComponentPropsWithRef } from 'react';
3
+ export type { SelectOption } from '@tuuject/koi-core';
4
+ export interface SelectProps extends SelectOwnProps, Omit<ComponentPropsWithRef<'select'>, keyof SelectOwnProps> {
5
+ }
6
+ /**
7
+ * A native dropdown styled like Input. It opens the platform picker (the system sheet on phones) and is fully
8
+ * keyboard accessible. For grouped, searchable or custom-drawn options, use SelectPanel.
9
+ * Base UI has no native select part, so this is Field.Control rendering a <select>.
10
+ */
11
+ export declare function Select({ options, placeholder, size, invalid, className, id, required, disabled, children, ...rest }: SelectProps): import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Field as BaseField } from '@base-ui/react/field';
3
+ import { cx } from '@tuuject/koi-react/styled-system/css';
4
+ import { select } from '@tuuject/koi-react/styled-system/recipes';
5
+ import { useContext } from 'react';
6
+ import { KoiFieldContext } from '../field/context.js';
7
+ import { ChevronDownIcon } from '../icons.js';
8
+ /**
9
+ * A native dropdown styled like Input. It opens the platform picker (the system sheet on phones) and is fully
10
+ * keyboard accessible. For grouped, searchable or custom-drawn options, use SelectPanel.
11
+ * Base UI has no native select part, so this is Field.Control rendering a <select>.
12
+ */
13
+ export function Select({ options, placeholder, size = 'md', invalid, className, id, required, disabled, children, ...rest }) {
14
+ const f = useContext(KoiFieldContext);
15
+ const s = select({ size });
16
+ // Uncontrolled with a placeholder: start on it, as legacy did.
17
+ const defaultValue = placeholder && rest.value === undefined && rest.defaultValue === undefined ? '' : rest.defaultValue;
18
+ const content = (_jsxs(_Fragment, { children: [placeholder ? (_jsx("option", { value: "", disabled: true, children: placeholder })) : null, children ??
19
+ options?.map((o) => (_jsx("option", { value: o.value, disabled: o.disabled, children: o.label }, o.value)))] }));
20
+ return (_jsxs("span", { className: cx(s.root, className), children: [_jsx(BaseField.Control, { ...rest, defaultValue: defaultValue, render: (props) => _jsx("select", { ...props, children: content }), id: id ?? f?.id, required: required ?? f?.required, disabled: disabled, "aria-invalid": invalid && !disabled ? true : rest['aria-invalid'], className: s.el }), _jsx("span", { className: s.chevron, "aria-hidden": "true", children: _jsx(ChevronDownIcon, {}) })] }));
21
+ }
@@ -0,0 +1,24 @@
1
+ import { type SelectEntry as Entry, type SelectGroupOwnProps, type SelectItemOwnProps, type SelectPanelOwnProps } from '@tuuject/koi-core';
2
+ import { type ComponentPropsWithRef, type HTMLAttributes } from 'react';
3
+ export type { SelectOptionGroup } from '@tuuject/koi-core';
4
+ export interface SelectPanelProps<I extends Entry = Entry> extends SelectPanelOwnProps<I, Event>, Omit<ComponentPropsWithRef<'button'>, keyof SelectPanelOwnProps | 'required' | 'name' | 'type'> {
5
+ /** Submitted with the form. */
6
+ name?: string;
7
+ required?: boolean;
8
+ }
9
+ /**
10
+ * A dropdown whose panel you draw: grouped options, rich rows, a search box. Base UI provides the behavior: a
11
+ * listbox (Base UI Select), or with `searchable` a combobox with its input inside the panel (Base UI Combobox).
12
+ * Both open below the trigger, flip near the viewport edge and are fully keyboard accessible.
13
+ */
14
+ export declare function SelectPanel<I extends Entry = Entry>({ items, value, defaultValue, onChange, placeholder, size, invalid, name, readOnly, required, disabled, searchable, searchPlaceholder, emptyText, renderValue, children, open, defaultOpen, onOpenChange, id, className, ...rest }: SelectPanelProps<I>): import("react").JSX.Element;
15
+ export interface SelectItemProps extends SelectItemOwnProps, Omit<HTMLAttributes<HTMLDivElement>, keyof SelectItemOwnProps> {
16
+ }
17
+ /** One choosable row in a SelectPanel. A check mark shows on the chosen one. */
18
+ export declare function SelectItem({ value, label, disabled, className, children, ...rest }: SelectItemProps): import("react").JSX.Element;
19
+ export interface SelectGroupProps extends SelectGroupOwnProps, Omit<HTMLAttributes<HTMLDivElement>, keyof SelectGroupOwnProps> {
20
+ }
21
+ /** A titled group of SelectItems. */
22
+ export declare function SelectGroup({ label, className, children, ...rest }: SelectGroupProps): import("react").JSX.Element;
23
+ /** A line between items or groups. */
24
+ export declare function SelectSeparator({ className, ...rest }: HTMLAttributes<HTMLDivElement>): import("react").JSX.Element;
@@ -0,0 +1,105 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Combobox } from '@base-ui/react/combobox';
3
+ import { Select as BaseSelect } from '@base-ui/react/select';
4
+ import { strings, } from '@tuuject/koi-core';
5
+ import { cx } from '@tuuject/koi-react/styled-system/css';
6
+ import { selectPanel } from '@tuuject/koi-react/styled-system/recipes';
7
+ import { components } from '@tuuject/koi-tokens/dimensions';
8
+ import { createContext, useContext, useMemo, } from 'react';
9
+ import { KoiFieldContext } from '../field/context.js';
10
+ import { CheckIcon, ChevronDownIcon, SearchIcon } from '../icons.js';
11
+ const isGroup = (e) => Array.isArray(e.items);
12
+ // SelectItem and friends render Base UI Select or Combobox parts, whichever the panel they're in uses.
13
+ const PARTS = {
14
+ select: {
15
+ Item: BaseSelect.Item,
16
+ ItemIndicator: BaseSelect.ItemIndicator,
17
+ Group: BaseSelect.Group,
18
+ GroupLabel: BaseSelect.GroupLabel,
19
+ Separator: BaseSelect.Separator,
20
+ },
21
+ combobox: {
22
+ Item: Combobox.Item,
23
+ ItemIndicator: Combobox.ItemIndicator,
24
+ Group: Combobox.Group,
25
+ GroupLabel: Combobox.GroupLabel,
26
+ Separator: Combobox.Separator,
27
+ },
28
+ };
29
+ const ModeContext = createContext(null);
30
+ function useParts(part) {
31
+ const mode = useContext(ModeContext);
32
+ if (!mode) {
33
+ throw new Error(`${part} must be rendered inside a SelectPanel.`);
34
+ }
35
+ // The two sets take the same props for everything used here; Select.Item's `label` is passed only to Select.
36
+ return { mode, P: PARTS[mode] };
37
+ }
38
+ function defaultRender(entries) {
39
+ const option = (o) => (_jsx(SelectItem, { value: o.value, label: o.label, disabled: o.disabled, children: o.label }, o.value));
40
+ return entries.map((e) => isGroup(e) ? (_jsx(SelectGroup, { label: e.label, children: e.items.map(option) }, e.label)) : (option(e)));
41
+ }
42
+ /**
43
+ * A dropdown whose panel you draw: grouped options, rich rows, a search box. Base UI provides the behavior: a
44
+ * listbox (Base UI Select), or with `searchable` a combobox with its input inside the panel (Base UI Combobox).
45
+ * Both open below the trigger, flip near the viewport edge and are fully keyboard accessible.
46
+ */
47
+ export function SelectPanel({ items, value, defaultValue, onChange, placeholder = strings.selectPlaceholder, size = 'md', invalid, name, readOnly, required, disabled, searchable, searchPlaceholder = strings.search, emptyText = strings.noMatches, renderValue, children, open, defaultOpen, onOpenChange, id, className, ...rest }) {
48
+ const f = useContext(KoiFieldContext);
49
+ const s = selectPanel({ size });
50
+ const flat = useMemo(() => items.flatMap((e) => (isGroup(e) ? e.items : [e])), [items]);
51
+ const render = children ?? defaultRender;
52
+ const showValue = (v) => {
53
+ const item = v == null ? undefined : flat.find((o) => o.value === v);
54
+ return item ? (renderValue ? renderValue(item) : item.label) : placeholder;
55
+ };
56
+ const root = {
57
+ value,
58
+ defaultValue,
59
+ open,
60
+ defaultOpen,
61
+ name,
62
+ readOnly,
63
+ disabled,
64
+ required: required ?? f?.required,
65
+ id: id ?? f?.id,
66
+ onValueChange: onChange,
67
+ onOpenChange,
68
+ };
69
+ const trigger = (P) => (_jsxs(P.Trigger, { ...rest, "aria-invalid": invalid && !disabled ? true : rest['aria-invalid'], className: cx(s.trigger, className), children: [_jsx("span", { className: s.value, children: _jsx(P.Value, { children: showValue }) }), _jsx(P.Icon, { className: s.icon, children: _jsx(ChevronDownIcon, {}) })] }));
70
+ // Base UI's Combobox filters on the labels; values stay the option's `value` strings.
71
+ const collection = useMemo(
72
+ // Typed as flat; createItems reads groups at runtime.
73
+ () => Combobox.createItems(items, {
74
+ getValue: (o) => o.value,
75
+ getLabel: (o) => o.label,
76
+ }), [items]);
77
+ if (searchable) {
78
+ return (_jsxs(Combobox.Root, { ...root, items: collection, children: [trigger(Combobox), _jsx(Combobox.Portal, { children: _jsx(Combobox.Positioner, { align: "start", sideOffset: components.popup.offset, className: s.positioner, children: _jsxs(Combobox.Popup, { className: s.popup, children: [_jsxs("div", { className: s.search, children: [_jsx(SearchIcon, {}), _jsx(Combobox.Input, { placeholder: searchPlaceholder, "aria-label": searchPlaceholder, className: s.searchInput })] }), _jsx(Combobox.Empty, { className: s.empty, children: emptyText }), _jsx(Combobox.List, { className: s.list, children: _jsx(ModeContext.Provider, { value: "combobox", children: _jsx(Filtered, { render: render }) }) })] }) }) })] }));
79
+ }
80
+ return (_jsxs(BaseSelect.Root, { ...root, items: flat, children: [trigger(BaseSelect), _jsx(BaseSelect.Portal, { children: _jsx(BaseSelect.Positioner, { alignItemWithTrigger: false, align: "start", sideOffset: components.popup.offset, className: s.positioner, children: _jsx(BaseSelect.Popup, { className: s.popup, children: _jsx(BaseSelect.List, { className: s.list, children: _jsx(ModeContext.Provider, { value: "select", children: render([...items]) }) }) }) }) })] }));
81
+ }
82
+ function Filtered({ render }) {
83
+ const filtered = Combobox.useFilteredItems();
84
+ return _jsx(_Fragment, { children: render(filtered.filter((e) => !isGroup(e) || e.items.length > 0)) });
85
+ }
86
+ /** One choosable row in a SelectPanel. A check mark shows on the chosen one. */
87
+ export function SelectItem({ value, label, disabled, className, children, ...rest }) {
88
+ const { mode, P } = useParts('SelectItem');
89
+ const s = selectPanel();
90
+ return (_jsxs(P.Item, { ...rest, value: value, disabled: disabled,
91
+ // A plain prop, not a conditional spread: Panda tries to evaluate useParts() for a spread and warns.
92
+ label: mode === 'select' ? label : undefined, className: cx(s.item, className), children: [_jsx("span", { className: s.itemText, children: children }), _jsx(P.ItemIndicator, { className: s.itemIndicator, children: _jsx(CheckIcon, {}) })] }));
93
+ }
94
+ /** A titled group of SelectItems. */
95
+ export function SelectGroup({ label, className, children, ...rest }) {
96
+ const { P } = useParts('SelectGroup');
97
+ const s = selectPanel();
98
+ return (_jsxs(P.Group, { ...rest, className: cx(s.group, className), children: [_jsx(P.GroupLabel, { className: s.groupLabel, children: label }), children] }));
99
+ }
100
+ /** A line between items or groups. */
101
+ export function SelectSeparator({ className, ...rest }) {
102
+ const { P } = useParts('SelectSeparator');
103
+ const s = selectPanel();
104
+ return _jsx(P.Separator, { ...rest, className: cx(s.separator, className) });
105
+ }
@@ -0,0 +1,16 @@
1
+ import type { ButtonShape } from '@tuuject/koi-core';
2
+ import { type ReactNode } from 'react';
3
+ export type { ButtonShape };
4
+ /**
5
+ * The default button shape for a surface. Buttons are rounded (8px) unless a container says
6
+ * otherwise; wrap a page in `shape="pill"` for the blog's pill voice. Dialog's footer and table
7
+ * cells still provide "rounded" themselves, so they stay rounded inside a pill page: the dialog's
8
+ * 32px radius = 8px button radius + 24px padding (concentric corners), and row actions need the
9
+ * dense shape. An explicit `shape` prop on a Button always wins.
10
+ */
11
+ export declare const ShapeContext: import("react").Context<ButtonShape | null>;
12
+ export declare function ShapeProvider({ shape, children }: {
13
+ shape: ButtonShape;
14
+ children: ReactNode;
15
+ }): import("react").JSX.Element;
16
+ export declare function useButtonShape(explicit?: ButtonShape): ButtonShape;
package/dist/shape.js ADDED
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from 'react';
3
+ /**
4
+ * The default button shape for a surface. Buttons are rounded (8px) unless a container says
5
+ * otherwise; wrap a page in `shape="pill"` for the blog's pill voice. Dialog's footer and table
6
+ * cells still provide "rounded" themselves, so they stay rounded inside a pill page: the dialog's
7
+ * 32px radius = 8px button radius + 24px padding (concentric corners), and row actions need the
8
+ * dense shape. An explicit `shape` prop on a Button always wins.
9
+ */
10
+ export const ShapeContext = createContext(null);
11
+ export function ShapeProvider({ shape, children }) {
12
+ return _jsx(ShapeContext.Provider, { value: shape, children: children });
13
+ }
14
+ export function useButtonShape(explicit) {
15
+ const fromContext = useContext(ShapeContext);
16
+ return explicit ?? fromContext ?? 'rounded';
17
+ }
@@ -0,0 +1,8 @@
1
+ import type { SkeletonOwnProps } from '@tuuject/koi-core';
2
+ import type { HTMLAttributes, JSX } from 'react';
3
+ export interface SkeletonProps extends SkeletonOwnProps, Omit<HTMLAttributes<HTMLElement>, keyof SkeletonOwnProps> {
4
+ /** Element to render. Default 'div'; use 'span' inside text. */
5
+ as?: keyof JSX.IntrinsicElements;
6
+ }
7
+ /** Only the fill and shimmer. Set width, height, border-radius and layout with your own className or style. Always aria-hidden. */
8
+ export declare function Skeleton({ as: Tag, className, ...rest }: SkeletonProps): JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from '@tuuject/koi-react/styled-system/css';
3
+ import { skeleton } from '@tuuject/koi-react/styled-system/recipes';
4
+ /** Only the fill and shimmer. Set width, height, border-radius and layout with your own className or style. Always aria-hidden. */
5
+ export function Skeleton({ as: Tag = 'div', className, ...rest }) {
6
+ const El = Tag;
7
+ return _jsx(El, { "aria-hidden": "true", ...rest, className: cx(skeleton(), className) });
8
+ }
@@ -0,0 +1,7 @@
1
+ import { type SpinnerOwnProps } from '@tuuject/koi-core';
2
+ export type { SpinnerSize } from '@tuuject/koi-core';
3
+ export interface SpinnerProps extends SpinnerOwnProps {
4
+ className?: string;
5
+ }
6
+ /** A 25%-opacity ring with an arc that turns every 800ms. Takes the surrounding text color. */
7
+ export declare function Spinner({ size, label, className }: SpinnerProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { strings } from '@tuuject/koi-core';
3
+ import { cx } from '@tuuject/koi-react/styled-system/css';
4
+ import { spinner } from '@tuuject/koi-react/styled-system/recipes';
5
+ import { components } from '@tuuject/koi-tokens/dimensions';
6
+ const { spinner: dim } = components;
7
+ const PX = { sm: dim.sizeSm, md: dim.sizeMd, lg: dim.sizeLg };
8
+ /** A 25%-opacity ring with an arc that turns every 800ms. Takes the surrounding text color. */
9
+ export function Spinner({ size = 'md', label = strings.loading, className }) {
10
+ const s = spinner();
11
+ const px = PX[size] ?? PX.md;
12
+ return (_jsx("span", { role: "status", "aria-label": label, className: cx(s.root, className), children: _jsxs("svg", { className: s.svg, width: px, height: px, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [_jsx("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeOpacity: 0.25, strokeWidth: dim.stroke }), _jsx("path", { d: "M21 12a9 9 0 00-9-9", stroke: "currentColor", strokeWidth: dim.stroke, strokeLinecap: "round" })] }) }));
13
+ }
@@ -0,0 +1,22 @@
1
+ import type { SwitchOwnProps } from '@tuuject/koi-core';
2
+ import { type ComponentPropsWithRef, type Ref } from 'react';
3
+ export interface SwitchProps extends SwitchOwnProps<Event>, Omit<ComponentPropsWithRef<'button'>, keyof SwitchOwnProps | 'required' | 'name' | 'value' | 'form' | 'type' | 'children' | 'onChange' | 'defaultValue'> {
4
+ required?: boolean;
5
+ /** The hidden input's name, for form submission. */
6
+ name?: string;
7
+ /** Submitted when on. */
8
+ value?: string;
9
+ /** Submitted when off (by default nothing is). */
10
+ uncheckedValue?: string;
11
+ /** The id of the form the hidden input belongs to. */
12
+ form?: string;
13
+ /** Reaches the hidden `<input>`. */
14
+ inputRef?: Ref<HTMLInputElement>;
15
+ }
16
+ /**
17
+ * A 36 × 20px pill toggle for a setting that applies at once; the whole row is the click target.
18
+ * Renders a `<button role="switch">` (which gets `id`, `ref` and the other button attributes) plus a hidden
19
+ * `<input>` outside the row that carries `name`, `value`, `required` and `form` (`inputRef` reaches it).
20
+ * Inside a Field it picks up the error and help (aria-describedby), `required`, `disabled` and `id`.
21
+ */
22
+ export declare function Switch({ label, className, style, id, required, onCheckedChange, 'aria-labelledby': labelledBy, ...rest }: SwitchProps): import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Switch as BaseSwitch } from '@base-ui/react/switch';
3
+ import { cx } from '@tuuject/koi-react/styled-system/css';
4
+ import { switchRecipe } from '@tuuject/koi-react/styled-system/recipes';
5
+ import { useContext, useId } from 'react';
6
+ import { KoiFieldContext } from '../field/context.js';
7
+ /**
8
+ * A 36 × 20px pill toggle for a setting that applies at once; the whole row is the click target.
9
+ * Renders a `<button role="switch">` (which gets `id`, `ref` and the other button attributes) plus a hidden
10
+ * `<input>` outside the row that carries `name`, `value`, `required` and `form` (`inputRef` reaches it).
11
+ * Inside a Field it picks up the error and help (aria-describedby), `required`, `disabled` and `id`.
12
+ */
13
+ export function Switch({ label, className, style, id, required, onCheckedChange, 'aria-labelledby': labelledBy, ...rest }) {
14
+ const f = useContext(KoiFieldContext);
15
+ const s = switchRecipe();
16
+ const uid = useId();
17
+ const hasLabel = label != null;
18
+ const labelId = hasLabel ? `${uid}-label` : undefined;
19
+ return (_jsx(BaseSwitch.Root, { ...rest, nativeButton: true, id: id ?? f?.id, required: required ?? f?.required, "aria-labelledby": labelledBy ?? labelId, onCheckedChange: onCheckedChange, className: hasLabel ? s.control : cx(s.control, className), style: hasLabel ? undefined : style,
20
+ // The row <label> goes inside the render so Base UI's hidden input lands beside it, not in it
21
+ // (a label may hold only one labelable element). Without a label there's no row.
22
+ render: (props) => hasLabel ? (_jsxs("label", { className: cx(s.root, className), style: style, children: [_jsx("button", { ...props, type: "button" }), _jsx("span", { id: labelId, className: s.label, children: label })] })) : (_jsx("button", { ...props, type: "button" })), children: _jsx(BaseSwitch.Thumb, { className: s.thumb }) }));
23
+ }
@@ -0,0 +1,12 @@
1
+ import type { DataTableOwnProps } from '@tuuject/koi-core';
2
+ import type { ComponentPropsWithRef } from 'react';
3
+ export type { DataTableColumn } from '@tuuject/koi-core';
4
+ export interface DataTableProps<T> extends DataTableOwnProps<T>, Omit<ComponentPropsWithRef<'table'>, keyof DataTableOwnProps<T> | 'children'> {
5
+ /** Class for the scrolling container around the table. */
6
+ containerClassName?: string;
7
+ }
8
+ /**
9
+ * A ready-made table from `columns` and `rows`, with skeleton loading and an empty state. Built from the Table
10
+ * parts; for sorting, selection or a table library such as TanStack Table, compose those parts directly.
11
+ */
12
+ export declare function DataTable<T>({ columns, rows, getRowKey, loading, skeletonRows, empty, caption, ...rest }: DataTableProps<T>): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { table } from '@tuuject/koi-react/styled-system/recipes';
3
+ import { Skeleton } from '../skeleton/Skeleton.js';
4
+ import { Table, TableBody, TableCaption, TableCell, TableEmpty, TableHeader, TableHeaderCell, TableRow, } from './Table.js';
5
+ /**
6
+ * A ready-made table from `columns` and `rows`, with skeleton loading and an empty state. Built from the Table
7
+ * parts; for sorting, selection or a table library such as TanStack Table, compose those parts directly.
8
+ */
9
+ export function DataTable({ columns, rows, getRowKey, loading, skeletonRows = 5, empty, caption, ...rest }) {
10
+ const s = table();
11
+ return (_jsxs(Table, { ...rest, "aria-busy": loading || undefined, children: [caption != null ? _jsx(TableCaption, { children: caption }) : null, columns.some((col) => col.width != null) ? (_jsx("colgroup", { children: columns.map((col) => (_jsx("col", { style: col.width != null ? { width: col.width } : undefined }, col.key))) })) : null, _jsx(TableHeader, { children: _jsx(TableRow, { children: columns.map((col) => (_jsx(TableHeaderCell, { align: col.align, children: col.header }, col.key))) }) }), _jsx(TableBody, { children: loading ? (Array.from({ length: skeletonRows }, (_, i) => (_jsx(TableRow, { children: columns.map((col) => (_jsx(TableCell, { align: col.align, children: _jsx(Skeleton, { as: "span", className: s.skeleton, children: col.skeleton ?? '──────' }) }, col.key))) }, i)))) : rows.length === 0 ? (_jsx(TableEmpty, { colSpan: columns.length, children: empty })) : (rows.map((row, i) => (_jsx(TableRow, { children: columns.map((col) => (_jsx(TableCell, { align: col.align, children: col.cell(row, i) }, col.key))) }, getRowKey(row, i))))) })] }));
12
+ }
@@ -0,0 +1,38 @@
1
+ import { type TableBodyOwnProps, type TableCaptionOwnProps, type TableCellOwnProps, type TableEmptyOwnProps, type TableFooterOwnProps, type TableHeaderCellOwnProps, type TableHeaderOwnProps, type TableOwnProps, type TableRowOwnProps } from '@tuuject/koi-core';
2
+ import type { ComponentPropsWithRef, MouseEvent } from 'react';
3
+ export type { TableAlign, TableLayout, TableSortDirection } from '@tuuject/koi-core';
4
+ export interface TableProps extends TableOwnProps, Omit<ComponentPropsWithRef<'table'>, keyof TableOwnProps> {
5
+ /** Class for the scrolling container around the table. */
6
+ containerClassName?: string;
7
+ }
8
+ /** A data table inside a container that scrolls sideways when the table is wider than it. */
9
+ export declare function Table({ layout, containerClassName, className, ...rest }: TableProps): import("react").JSX.Element;
10
+ export interface TableHeaderProps extends TableHeaderOwnProps, Omit<ComponentPropsWithRef<'thead'>, keyof TableHeaderOwnProps> {
11
+ }
12
+ export declare function TableHeader({ className, ...rest }: TableHeaderProps): import("react").JSX.Element;
13
+ export interface TableBodyProps extends TableBodyOwnProps, Omit<ComponentPropsWithRef<'tbody'>, keyof TableBodyOwnProps> {
14
+ }
15
+ export declare function TableBody({ className, ...rest }: TableBodyProps): import("react").JSX.Element;
16
+ export interface TableFooterProps extends TableFooterOwnProps, Omit<ComponentPropsWithRef<'tfoot'>, keyof TableFooterOwnProps> {
17
+ }
18
+ export declare function TableFooter({ className, ...rest }: TableFooterProps): import("react").JSX.Element;
19
+ export interface TableRowProps extends TableRowOwnProps, Omit<ComponentPropsWithRef<'tr'>, keyof TableRowOwnProps> {
20
+ }
21
+ /** A row. Body rows are at least 48px tall and highlight on hover. */
22
+ export declare function TableRow({ selected, className, ...rest }: TableRowProps): import("react").JSX.Element;
23
+ export interface TableHeaderCellProps extends TableHeaderCellOwnProps<MouseEvent<HTMLButtonElement>>, Omit<ComponentPropsWithRef<'th'>, keyof TableHeaderCellOwnProps | 'align'> {
24
+ }
25
+ /** A column header (`<th scope="col">`). With `onSort` its label becomes a sort button. */
26
+ export declare function TableHeaderCell({ align, paddingBlock, sort, onSort, className, children, ...rest }: TableHeaderCellProps): import("react").JSX.Element;
27
+ export interface TableCellProps extends TableCellOwnProps, Omit<ComponentPropsWithRef<'td'>, keyof TableCellOwnProps | 'align'> {
28
+ }
29
+ /** A body or footer cell. Buttons inside default to shape="rounded", the dense shape, as in Dialog footers. */
30
+ export declare function TableCell({ align, paddingBlock, className, children, ...rest }: TableCellProps): import("react").JSX.Element;
31
+ export interface TableEmptyProps extends TableEmptyOwnProps, Omit<ComponentPropsWithRef<'td'>, keyof TableEmptyOwnProps> {
32
+ }
33
+ /** A full-width "no data" row for an empty TableBody. */
34
+ export declare function TableEmpty({ colSpan, className, children, ...rest }: TableEmptyProps): import("react").JSX.Element;
35
+ export interface TableCaptionProps extends TableCaptionOwnProps, Omit<ComponentPropsWithRef<'caption'>, keyof TableCaptionOwnProps> {
36
+ }
37
+ /** The table's accessible name, for screen readers only. Put it first inside Table. */
38
+ export declare function TableCaption({ className, ...rest }: TableCaptionProps): import("react").JSX.Element;
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { strings, } from '@tuuject/koi-core';
3
+ import { cx } from '@tuuject/koi-react/styled-system/css';
4
+ import { table } from '@tuuject/koi-react/styled-system/recipes';
5
+ import { SortDownIcon, SortIcon, SortUpIcon } from '../icons.js';
6
+ import { ShapeProvider } from '../shape.js';
7
+ /** A data table inside a container that scrolls sideways when the table is wider than it. */
8
+ export function Table({ layout, containerClassName, className, ...rest }) {
9
+ const s = table({ layout });
10
+ return (_jsx("div", { className: cx(s.container, containerClassName), children: _jsx("table", { ...rest, className: cx(s.root, className) }) }));
11
+ }
12
+ export function TableHeader({ className, ...rest }) {
13
+ return _jsx("thead", { ...rest, className: cx(table().header, className) });
14
+ }
15
+ export function TableBody({ className, ...rest }) {
16
+ return _jsx("tbody", { ...rest, className: cx(table().body, className) });
17
+ }
18
+ export function TableFooter({ className, ...rest }) {
19
+ return _jsx("tfoot", { ...rest, className: cx(table().footer, className) });
20
+ }
21
+ /** A row. Body rows are at least 48px tall and highlight on hover. */
22
+ export function TableRow({ selected, className, ...rest }) {
23
+ // A Koi attribute rather than aria-selected, which is only valid on rows of a grid, not of a plain table.
24
+ return _jsx("tr", { "data-koi-selected": selected ? '' : undefined, ...rest, className: cx(table().row, className) });
25
+ }
26
+ const ARIA_SORT = { asc: 'ascending', desc: 'descending' };
27
+ /** A column header (`<th scope="col">`). With `onSort` its label becomes a sort button. */
28
+ export function TableHeaderCell({ align, paddingBlock, sort = false, onSort, className, children, ...rest }) {
29
+ const s = table({ align, paddingBlock });
30
+ const sortable = onSort != null;
31
+ const Indicator = sort === 'asc' ? SortUpIcon : sort === 'desc' ? SortDownIcon : SortIcon;
32
+ return (_jsx("th", { scope: "col", "aria-sort": sortable && sort ? ARIA_SORT[sort] : undefined, ...rest, className: cx(s.headerCell, className), children: sortable ? (_jsxs("button", { type: "button", className: s.sortButton, onClick: onSort, children: [children, _jsx(Indicator, {})] })) : (children) }));
33
+ }
34
+ /** A body or footer cell. Buttons inside default to shape="rounded", the dense shape, as in Dialog footers. */
35
+ export function TableCell({ align, paddingBlock, className, children, ...rest }) {
36
+ return (_jsx("td", { ...rest, className: cx(table({ align, paddingBlock }).cell, className), children: _jsx(ShapeProvider, { shape: "rounded", children: children }) }));
37
+ }
38
+ /** A full-width "no data" row for an empty TableBody. */
39
+ export function TableEmpty({ colSpan, className, children, ...rest }) {
40
+ return (_jsx("tr", { children: _jsx("td", { ...rest, colSpan: colSpan, className: cx(table().empty, className), children: _jsx(ShapeProvider, { shape: "rounded", children: children ?? strings.noData }) }) }));
41
+ }
42
+ /** The table's accessible name, for screen readers only. Put it first inside Table. */
43
+ export function TableCaption({ className, ...rest }) {
44
+ return _jsx("caption", { ...rest, className: cx(table().caption, className) });
45
+ }
@@ -0,0 +1,11 @@
1
+ import type { TabsOwnProps } from '@tuuject/koi-core';
2
+ import type { ComponentPropsWithRef } from 'react';
3
+ export type { TabItem } from '@tuuject/koi-core';
4
+ export interface TabsProps extends TabsOwnProps<Event>, Omit<ComponentPropsWithRef<'div'>, keyof TabsOwnProps | 'defaultValue' | 'children' | 'aria-label'> {
5
+ }
6
+ /**
7
+ * A segmented pill track for 2–6 peer views. The active tab is a white pill (Base UI's Tabs.Indicator) that
8
+ * springs to the newly chosen tab. ←/→ move and choose (skipping disabled tabs, wrapping), Home/End jump.
9
+ * Only the active item's `content` is mounted, in a focusable tabpanel.
10
+ */
11
+ export declare function Tabs({ items, value, defaultValue, onChange, label, fullWidth, className, ...rest }: TabsProps): import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Tabs as BaseTabs } from '@base-ui/react/tabs';
3
+ import { cx } from '@tuuject/koi-react/styled-system/css';
4
+ import { tabs } from '@tuuject/koi-react/styled-system/recipes';
5
+ /**
6
+ * A segmented pill track for 2–6 peer views. The active tab is a white pill (Base UI's Tabs.Indicator) that
7
+ * springs to the newly chosen tab. ←/→ move and choose (skipping disabled tabs, wrapping), Home/End jump.
8
+ * Only the active item's `content` is mounted, in a focusable tabpanel.
9
+ */
10
+ export function Tabs({ items, value, defaultValue, onChange, label, fullWidth = false, className, ...rest }) {
11
+ const s = tabs({ fullWidth });
12
+ // Legacy picked the first item; pick the first enabled one so Base UI never has to fall back by itself.
13
+ const initial = defaultValue ?? (items.find((i) => !i.disabled) ?? items[0])?.value;
14
+ return (_jsxs(BaseTabs.Root, { ...rest, value: value, defaultValue: value === undefined ? initial : undefined, onValueChange: (v, details) => {
15
+ // Only user choices; Base UI's own fallbacks ('initial', 'disabled', 'missing') weren't events in legacy.
16
+ if (details.reason === 'none') {
17
+ onChange?.(v, details);
18
+ }
19
+ }, className: cx(s.root, className), children: [_jsxs(BaseTabs.List, { activateOnFocus: true, "aria-label": label, className: s.list, children: [items.map((i) => (_jsx(BaseTabs.Tab, { value: i.value, disabled: i.disabled, className: s.tab, children: i.label }, i.value))), _jsx(BaseTabs.Indicator, { className: s.indicator })] }), items.map((i) => i.content != null ? (_jsx(BaseTabs.Panel, { value: i.value, className: s.panel, children: i.content }, i.value)) : null)] }));
20
+ }
@@ -0,0 +1,6 @@
1
+ import { type TagOwnProps } from '@tuuject/koi-core';
2
+ import { type ComponentPropsWithRef, type MouseEvent } from 'react';
3
+ export interface TagProps extends TagOwnProps<MouseEvent<HTMLButtonElement>>, Omit<ComponentPropsWithRef<'span'>, keyof TagOwnProps | 'children'> {
4
+ }
5
+ /** An outlined pill chip for keywords, filters and removable selections. For pure status, use Badge. */
6
+ export declare function Tag({ children, icon, selected, onSelectedChange, onRemove, className, ...rest }: TagProps): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { strings } from '@tuuject/koi-core';
3
+ import { cx } from '@tuuject/koi-react/styled-system/css';
4
+ import { tag } from '@tuuject/koi-react/styled-system/recipes';
5
+ import { useId } from 'react';
6
+ import { CloseIcon } from '../icons.js';
7
+ /** An outlined pill chip for keywords, filters and removable selections. For pure status, use Badge. */
8
+ export function Tag({ children, icon, selected = false, onSelectedChange, onRemove, className, ...rest }) {
9
+ const s = tag({ selected });
10
+ const id = useId();
11
+ const labelId = `${id}-label`;
12
+ const removeId = `${id}-remove`;
13
+ const content = (_jsxs(_Fragment, { children: [icon ? (_jsx("span", { className: s.icon, "aria-hidden": "true", children: icon })) : null, _jsx("span", { className: s.label, id: onRemove ? labelId : undefined, children: children })] }));
14
+ return (_jsxs("span", { ...rest, className: cx(s.root, className), children: [onSelectedChange ? (_jsx("button", { type: "button", className: s.toggle, "aria-pressed": selected, onClick: (e) => onSelectedChange(!selected, e), children: content })) : (content), onRemove ? (_jsx("button", { type: "button", id: removeId, className: s.remove, "aria-label": strings.remove, "aria-labelledby": `${removeId} ${labelId}`, onClick: onRemove, children: _jsx(CloseIcon, {}) })) : null] }));
15
+ }
@@ -0,0 +1,9 @@
1
+ import type { TextareaOwnProps } from '@tuuject/koi-core';
2
+ import { type ComponentPropsWithRef } from 'react';
3
+ export interface TextareaProps extends TextareaOwnProps, Omit<ComponentPropsWithRef<'textarea'>, keyof TextareaOwnProps> {
4
+ }
5
+ /**
6
+ * A multi-line text field that matches Input. Resizes vertically, at least 80px tall.
7
+ * Base UI has no textarea part, so this is Field.Control rendering a <textarea>.
8
+ */
9
+ export declare function Textarea({ invalid, className, id, required, disabled, rows, ...rest }: TextareaProps): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Field as BaseField } from '@base-ui/react/field';
3
+ import { cx } from '@tuuject/koi-react/styled-system/css';
4
+ import { textarea } from '@tuuject/koi-react/styled-system/recipes';
5
+ import { useContext } from 'react';
6
+ import { KoiFieldContext } from '../field/context.js';
7
+ /**
8
+ * A multi-line text field that matches Input. Resizes vertically, at least 80px tall.
9
+ * Base UI has no textarea part, so this is Field.Control rendering a <textarea>.
10
+ */
11
+ export function Textarea({ invalid, className, id, required, disabled, rows = 4, ...rest }) {
12
+ const f = useContext(KoiFieldContext);
13
+ return (_jsx(BaseField.Control, { ...rest, render: _jsx("textarea", { rows: rows }), id: id ?? f?.id, required: required ?? f?.required, disabled: disabled, "aria-invalid": invalid && !disabled ? true : rest['aria-invalid'], className: cx(textarea(), className) }));
14
+ }