@tea-ui/core 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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -0
  3. package/dist/card.d.ts +60 -0
  4. package/dist/card.js +116 -0
  5. package/dist/card.js.map +1 -0
  6. package/dist/feedback/alert.d.ts +57 -0
  7. package/dist/feedback/alert.js +104 -0
  8. package/dist/feedback/alert.js.map +1 -0
  9. package/dist/feedback/badge.d.ts +34 -0
  10. package/dist/feedback/badge.js +62 -0
  11. package/dist/feedback/badge.js.map +1 -0
  12. package/dist/feedback/index.d.ts +13 -0
  13. package/dist/feedback/index.js +62 -0
  14. package/dist/feedback/index.js.map +1 -0
  15. package/dist/feedback/progress.d.ts +25 -0
  16. package/dist/feedback/progress.js +49 -0
  17. package/dist/feedback/progress.js.map +1 -0
  18. package/dist/feedback/skeleton.d.ts +38 -0
  19. package/dist/feedback/skeleton.js +49 -0
  20. package/dist/feedback/skeleton.js.map +1 -0
  21. package/dist/feedback/spinner.d.ts +31 -0
  22. package/dist/feedback/spinner.js +55 -0
  23. package/dist/feedback/spinner.js.map +1 -0
  24. package/dist/feedback/status.d.ts +63 -0
  25. package/dist/feedback/status.js +156 -0
  26. package/dist/feedback/status.js.map +1 -0
  27. package/dist/feedback/toast.d.ts +72 -0
  28. package/dist/feedback/toast.js +211 -0
  29. package/dist/feedback/toast.js.map +1 -0
  30. package/dist/format/index.d.ts +63 -0
  31. package/dist/format/index.js +84 -0
  32. package/dist/format/index.js.map +1 -0
  33. package/dist/index.d.ts +43 -0
  34. package/dist/index.js +163 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/inputs/button-group.d.ts +70 -0
  37. package/dist/inputs/button-group.js +59 -0
  38. package/dist/inputs/button-group.js.map +1 -0
  39. package/dist/inputs/button.d.ts +52 -0
  40. package/dist/inputs/button.js +84 -0
  41. package/dist/inputs/button.js.map +1 -0
  42. package/dist/inputs/checkbox.d.ts +48 -0
  43. package/dist/inputs/checkbox.js +74 -0
  44. package/dist/inputs/checkbox.js.map +1 -0
  45. package/dist/inputs/combobox.d.ts +77 -0
  46. package/dist/inputs/combobox.js +273 -0
  47. package/dist/inputs/combobox.js.map +1 -0
  48. package/dist/inputs/field.d.ts +220 -0
  49. package/dist/inputs/field.js +183 -0
  50. package/dist/inputs/field.js.map +1 -0
  51. package/dist/inputs/icon-button.d.ts +44 -0
  52. package/dist/inputs/icon-button.js +30 -0
  53. package/dist/inputs/icon-button.js.map +1 -0
  54. package/dist/inputs/index.d.ts +22 -0
  55. package/dist/inputs/index.js +92 -0
  56. package/dist/inputs/index.js.map +1 -0
  57. package/dist/inputs/input-group.d.ts +77 -0
  58. package/dist/inputs/input-group.js +76 -0
  59. package/dist/inputs/input-group.js.map +1 -0
  60. package/dist/inputs/input.d.ts +58 -0
  61. package/dist/inputs/input.js +68 -0
  62. package/dist/inputs/input.js.map +1 -0
  63. package/dist/inputs/label.d.ts +47 -0
  64. package/dist/inputs/label.js +29 -0
  65. package/dist/inputs/label.js.map +1 -0
  66. package/dist/inputs/number-input.d.ts +62 -0
  67. package/dist/inputs/number-input.js +86 -0
  68. package/dist/inputs/number-input.js.map +1 -0
  69. package/dist/inputs/password-input.d.ts +66 -0
  70. package/dist/inputs/password-input.js +73 -0
  71. package/dist/inputs/password-input.js.map +1 -0
  72. package/dist/inputs/radio.d.ts +66 -0
  73. package/dist/inputs/radio.js +96 -0
  74. package/dist/inputs/radio.js.map +1 -0
  75. package/dist/inputs/search-input.d.ts +66 -0
  76. package/dist/inputs/search-input.js +76 -0
  77. package/dist/inputs/search-input.js.map +1 -0
  78. package/dist/inputs/select.d.ts +65 -0
  79. package/dist/inputs/select.js +139 -0
  80. package/dist/inputs/select.js.map +1 -0
  81. package/dist/inputs/slider.d.ts +67 -0
  82. package/dist/inputs/slider.js +107 -0
  83. package/dist/inputs/slider.js.map +1 -0
  84. package/dist/inputs/switch.d.ts +42 -0
  85. package/dist/inputs/switch.js +69 -0
  86. package/dist/inputs/switch.js.map +1 -0
  87. package/dist/inputs/textarea.d.ts +51 -0
  88. package/dist/inputs/textarea.js +82 -0
  89. package/dist/inputs/textarea.js.map +1 -0
  90. package/dist/inputs/toggle.d.ts +57 -0
  91. package/dist/inputs/toggle.js +91 -0
  92. package/dist/inputs/toggle.js.map +1 -0
  93. package/dist/internal/index.d.ts +5 -0
  94. package/dist/internal/index.js +19 -0
  95. package/dist/internal/index.js.map +1 -0
  96. package/dist/internal/refs.d.ts +31 -0
  97. package/dist/internal/refs.js +34 -0
  98. package/dist/internal/refs.js.map +1 -0
  99. package/dist/internal/slot.d.ts +76 -0
  100. package/dist/internal/slot.js +40 -0
  101. package/dist/internal/slot.js.map +1 -0
  102. package/dist/internal/use-controllable-state.d.ts +25 -0
  103. package/dist/internal/use-controllable-state.js +38 -0
  104. package/dist/internal/use-controllable-state.js.map +1 -0
  105. package/dist/layout/index.d.ts +131 -0
  106. package/dist/layout/index.js +212 -0
  107. package/dist/layout/index.js.map +1 -0
  108. package/dist/navigation/index.d.ts +114 -0
  109. package/dist/navigation/index.js +523 -0
  110. package/dist/navigation/index.js.map +1 -0
  111. package/dist/overlays/alert-dialog.d.ts +75 -0
  112. package/dist/overlays/alert-dialog.js +163 -0
  113. package/dist/overlays/alert-dialog.js.map +1 -0
  114. package/dist/overlays/dialog.d.ts +44 -0
  115. package/dist/overlays/dialog.js +121 -0
  116. package/dist/overlays/dialog.js.map +1 -0
  117. package/dist/overlays/drawer.d.ts +49 -0
  118. package/dist/overlays/drawer.js +125 -0
  119. package/dist/overlays/drawer.js.map +1 -0
  120. package/dist/overlays/dropdown-menu.d.ts +53 -0
  121. package/dist/overlays/dropdown-menu.js +192 -0
  122. package/dist/overlays/dropdown-menu.js.map +1 -0
  123. package/dist/overlays/index.d.ts +10 -0
  124. package/dist/overlays/index.js +169 -0
  125. package/dist/overlays/index.js.map +1 -0
  126. package/dist/overlays/popover.d.ts +58 -0
  127. package/dist/overlays/popover.js +105 -0
  128. package/dist/overlays/popover.js.map +1 -0
  129. package/dist/styles.css +2 -0
  130. package/dist/typography/index.d.ts +79 -0
  131. package/dist/typography/index.js +280 -0
  132. package/dist/typography/index.js.map +1 -0
  133. package/package.json +60 -0
@@ -0,0 +1,70 @@
1
+ import * as React from 'react';
2
+
3
+ /**
4
+ * TEA UI — ButtonGroup.
5
+ *
6
+ * A set of related actions, either spaced apart or welded edge to edge.
7
+ *
8
+ * It renders `role="group"`, which is a real structural relationship the audit
9
+ * got for free in one place and missed in the rest: five copy-pasted button
10
+ * rows with no grouping, no orientation and no name. A group says "these belong
11
+ * together" to a screen reader user navigating by structure, which is exactly
12
+ * the information a visual user gets from the shared border.
13
+ *
14
+ * `joined` is done in CSS on the group's own children rather than by asking
15
+ * each child to cooperate. That matters here: the group's most common member is
16
+ * the reference `Button`, which must not need to know about groups. A negative
17
+ * inline margin on every child after the first removes the double border, and
18
+ * because TEA geometry has a radius of exactly zero, nothing else is needed to
19
+ * make the seam disappear. (With a radius, every child but the first and last
20
+ * would need its corners flattened — which is why the radius is zero.)
21
+ */
22
+ interface ButtonGroupContextValue {
23
+ orientation: "horizontal" | "vertical";
24
+ joined: boolean;
25
+ }
26
+ /**
27
+ * Read by the TEA UI controls that can join a group. `null` when standalone.
28
+ *
29
+ * A control that does not read this still renders correctly inside a group —
30
+ * the group styles its children from the outside — so this is an opt-in for
31
+ * the parts that want to adapt, never a requirement.
32
+ */
33
+ declare function useButtonGroupContext(): ButtonGroupContextValue | null;
34
+ interface ButtonGroupProps extends Omit<React.ComponentProps<"div">, "children"> {
35
+ children?: React.ReactNode;
36
+ /**
37
+ * Name for the group. **Required** — a `role="group"` with no name is an
38
+ * entry in the structure list that says nothing, which costs a screen reader
39
+ * user a stop for no information.
40
+ */
41
+ label: string;
42
+ /** Direction the children run in. Decides the join axis and the arrow order. */
43
+ orientation?: "horizontal" | "vertical";
44
+ /**
45
+ * Weld the children into one control: one border, no gaps, one shared focus
46
+ * ring per child. Off by default, because a toolbar of unrelated actions
47
+ * should not look like a segmented control.
48
+ */
49
+ joined?: boolean;
50
+ }
51
+ /**
52
+ * @example
53
+ * ```tsx
54
+ * <ButtonGroup label="Ansicht" joined>
55
+ * <Button variant="outline">Tag</Button>
56
+ * <Button variant="outline">Woche</Button>
57
+ * <Button variant="outline">Monat</Button>
58
+ * </ButtonGroup>
59
+ * ```
60
+ */
61
+ declare const ButtonGroup: React.ForwardRefExoticComponent<Omit<ButtonGroupProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
62
+ type ButtonGroupSeparatorProps = React.ComponentProps<"span">;
63
+ /**
64
+ * A gap inside a `joined` group, for splitting one control into two halves —
65
+ * "undo" and "redo", "date from" and "date to". A separator is a `<span>` with
66
+ * `aria-hidden`, because it is a drawing, not content.
67
+ */
68
+ declare const ButtonGroupSeparator: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
69
+
70
+ export { ButtonGroup, type ButtonGroupProps, ButtonGroupSeparator, type ButtonGroupSeparatorProps, useButtonGroupContext };
@@ -0,0 +1,59 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "@tea-ui/utils";
4
+ import { dataSlot } from "../internal/index.js";
5
+ const ButtonGroupContext = React.createContext(null);
6
+ function useButtonGroupContext() {
7
+ return React.useContext(ButtonGroupContext);
8
+ }
9
+ const ButtonGroup = React.forwardRef(function ButtonGroup2({ children, className, label, orientation = "horizontal", joined = false, ...props }, ref) {
10
+ const value = React.useMemo(
11
+ () => ({ orientation, joined }),
12
+ [orientation, joined]
13
+ );
14
+ return /* @__PURE__ */ jsx(ButtonGroupContext.Provider, { value, children: /* @__PURE__ */ jsx(
15
+ "div",
16
+ {
17
+ ref,
18
+ role: "group",
19
+ "aria-label": label,
20
+ "data-orientation": orientation,
21
+ "data-joined": joined || void 0,
22
+ "data-tea-touch": true,
23
+ className: cn(
24
+ "inline-flex items-stretch",
25
+ orientation === "vertical" ? "flex-col" : "flex-row",
26
+ // The seam: pull each child back over the shared border. `-ms-px` and
27
+ // `-mt-px` are layout, not `!`-prefixed overrides, and they are
28
+ // scoped to the group's own children.
29
+ joined && orientation === "horizontal" && "[&>*+*]:-ms-px",
30
+ joined && orientation === "vertical" && "[&>*+*]:-mt-px",
31
+ !joined && "gap-2",
32
+ className
33
+ ),
34
+ ...dataSlot("button-group"),
35
+ ...props,
36
+ children
37
+ }
38
+ ) });
39
+ });
40
+ const ButtonGroupSeparator = React.forwardRef(
41
+ function ButtonGroupSeparator2({ className, ...props }, ref) {
42
+ return /* @__PURE__ */ jsx(
43
+ "span",
44
+ {
45
+ ref,
46
+ "aria-hidden": "true",
47
+ className: cn("shrink-0 self-stretch bg-line w-px", className),
48
+ ...dataSlot("button-group-separator"),
49
+ ...props
50
+ }
51
+ );
52
+ }
53
+ );
54
+ export {
55
+ ButtonGroup,
56
+ ButtonGroupSeparator,
57
+ useButtonGroupContext
58
+ };
59
+ //# sourceMappingURL=button-group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/inputs/button-group.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@tea-ui/utils\";\n\nimport { dataSlot } from \"../internal\";\n\n/**\n * TEA UI — ButtonGroup.\n *\n * A set of related actions, either spaced apart or welded edge to edge.\n *\n * It renders `role=\"group\"`, which is a real structural relationship the audit\n * got for free in one place and missed in the rest: five copy-pasted button\n * rows with no grouping, no orientation and no name. A group says \"these belong\n * together\" to a screen reader user navigating by structure, which is exactly\n * the information a visual user gets from the shared border.\n *\n * `joined` is done in CSS on the group's own children rather than by asking\n * each child to cooperate. That matters here: the group's most common member is\n * the reference `Button`, which must not need to know about groups. A negative\n * inline margin on every child after the first removes the double border, and\n * because TEA geometry has a radius of exactly zero, nothing else is needed to\n * make the seam disappear. (With a radius, every child but the first and last\n * would need its corners flattened — which is why the radius is zero.)\n */\n\ninterface ButtonGroupContextValue {\n orientation: \"horizontal\" | \"vertical\";\n joined: boolean;\n}\n\nconst ButtonGroupContext = React.createContext<ButtonGroupContextValue | null>(null);\n\n/**\n * Read by the TEA UI controls that can join a group. `null` when standalone.\n *\n * A control that does not read this still renders correctly inside a group —\n * the group styles its children from the outside — so this is an opt-in for\n * the parts that want to adapt, never a requirement.\n */\nexport function useButtonGroupContext(): ButtonGroupContextValue | null {\n return React.useContext(ButtonGroupContext);\n}\n\nexport interface ButtonGroupProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n children?: React.ReactNode;\n /**\n * Name for the group. **Required** — a `role=\"group\"` with no name is an\n * entry in the structure list that says nothing, which costs a screen reader\n * user a stop for no information.\n */\n label: string;\n /** Direction the children run in. Decides the join axis and the arrow order. */\n orientation?: \"horizontal\" | \"vertical\";\n /**\n * Weld the children into one control: one border, no gaps, one shared focus\n * ring per child. Off by default, because a toolbar of unrelated actions\n * should not look like a segmented control.\n */\n joined?: boolean;\n}\n\n/**\n * @example\n * ```tsx\n * <ButtonGroup label=\"Ansicht\" joined>\n * <Button variant=\"outline\">Tag</Button>\n * <Button variant=\"outline\">Woche</Button>\n * <Button variant=\"outline\">Monat</Button>\n * </ButtonGroup>\n * ```\n */\nexport const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(function ButtonGroup(\n { children, className, label, orientation = \"horizontal\", joined = false, ...props },\n ref,\n) {\n const value = React.useMemo<ButtonGroupContextValue>(\n () => ({ orientation, joined }),\n [orientation, joined],\n );\n\n return (\n <ButtonGroupContext.Provider value={value}>\n <div\n ref={ref}\n role=\"group\"\n aria-label={label}\n data-orientation={orientation}\n data-joined={joined || undefined}\n data-tea-touch\n className={cn(\n \"inline-flex items-stretch\",\n orientation === \"vertical\" ? \"flex-col\" : \"flex-row\",\n // The seam: pull each child back over the shared border. `-ms-px` and\n // `-mt-px` are layout, not `!`-prefixed overrides, and they are\n // scoped to the group's own children.\n joined && orientation === \"horizontal\" && \"[&>*+*]:-ms-px\",\n joined && orientation === \"vertical\" && \"[&>*+*]:-mt-px\",\n !joined && \"gap-2\",\n className,\n )}\n {...dataSlot(\"button-group\")}\n {...props}\n >\n {children}\n </div>\n </ButtonGroupContext.Provider>\n );\n});\n\nexport type ButtonGroupSeparatorProps = React.ComponentProps<\"span\">;\n\n/**\n * A gap inside a `joined` group, for splitting one control into two halves —\n * \"undo\" and \"redo\", \"date from\" and \"date to\". A separator is a `<span>` with\n * `aria-hidden`, because it is a drawing, not content.\n */\nexport const ButtonGroupSeparator = React.forwardRef<HTMLSpanElement, ButtonGroupSeparatorProps>(\n function ButtonGroupSeparator({ className, ...props }, ref) {\n return (\n <span\n ref={ref}\n aria-hidden=\"true\"\n className={cn(\"shrink-0 self-stretch bg-line w-px\", className)}\n {...dataSlot(\"button-group-separator\")}\n {...props}\n />\n );\n },\n);\n"],"mappings":"AAkFM;AAlFL,YAAY,WAAW;AACxB,SAAS,UAAU;AAEnB,SAAS,gBAAgB;AA2BzB,MAAM,qBAAqB,MAAM,cAA8C,IAAI;AAS5E,SAAS,wBAAwD;AACtE,SAAO,MAAM,WAAW,kBAAkB;AAC5C;AA8BO,MAAM,cAAc,MAAM,WAA6C,SAASA,aACrF,EAAE,UAAU,WAAW,OAAO,cAAc,cAAc,SAAS,OAAO,GAAG,MAAM,GACnF,KACA;AACA,QAAM,QAAQ,MAAM;AAAA,IAClB,OAAO,EAAE,aAAa,OAAO;AAAA,IAC7B,CAAC,aAAa,MAAM;AAAA,EACtB;AAEA,SACE,oBAAC,mBAAmB,UAAnB,EAA4B,OAC3B;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,oBAAkB;AAAA,MAClB,eAAa,UAAU;AAAA,MACvB,kBAAc;AAAA,MACd,WAAW;AAAA,QACT;AAAA,QACA,gBAAgB,aAAa,aAAa;AAAA;AAAA;AAAA;AAAA,QAI1C,UAAU,gBAAgB,gBAAgB;AAAA,QAC1C,UAAU,gBAAgB,cAAc;AAAA,QACxC,CAAC,UAAU;AAAA,QACX;AAAA,MACF;AAAA,MACC,GAAG,SAAS,cAAc;AAAA,MAC1B,GAAG;AAAA,MAEH;AAAA;AAAA,EACH,GACF;AAEJ,CAAC;AASM,MAAM,uBAAuB,MAAM;AAAA,EACxC,SAASC,sBAAqB,EAAE,WAAW,GAAG,MAAM,GAAG,KAAK;AAC1D,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAW,GAAG,sCAAsC,SAAS;AAAA,QAC5D,GAAG,SAAS,wBAAwB;AAAA,QACpC,GAAG;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;","names":["ButtonGroup","ButtonGroupSeparator"]}
@@ -0,0 +1,52 @@
1
+ import * as class_variance_authority_types from 'class-variance-authority/types';
2
+ import * as React from 'react';
3
+ import { VariantProps } from '@tea-ui/utils';
4
+
5
+ /**
6
+ * TEA UI — Button. The reference implementation for every component in TEA UI.
7
+ *
8
+ * Read this file before writing another component. It encodes the house style:
9
+ *
10
+ * 1. `cva` for the variant surface, always with `className` passed *into* the
11
+ * call so a consumer's class merges correctly. (HomeServerManager's Button
12
+ * did this and its Badge did not — which is how the two drifted.)
13
+ * 2. `variant` and `size` are the only styling axes. There is no `color` prop
14
+ * and no `className` escape hatch that replaces a variant.
15
+ * 3. State is exposed as data attributes, not only as styling.
16
+ * 4. `asChild` is available on every component that renders an element, so a
17
+ * consumer can keep native semantics (a real `<a>`) and still get the
18
+ * styles.
19
+ * 5. `loading` is a first-class state, not a `disabled` alias: it keeps the
20
+ * control's width, announces itself, and blocks repeat activation.
21
+ * 6. `type` defaults to `"button"`. A TEA UI button inside a form is not a
22
+ * submit button by accident.
23
+ * 7. The ref is forwarded.
24
+ */
25
+ declare const buttonVariants: (props?: ({
26
+ variant?: "link" | "primary" | "destructive" | "subtle" | "secondary" | "outline" | "ghost" | null | undefined;
27
+ size?: "sm" | "md" | "lg" | "icon-sm" | "icon-md" | "icon-lg" | null | undefined;
28
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
29
+ interface ButtonProps extends Omit<React.ComponentProps<"button">, "color">, VariantProps<typeof buttonVariants> {
30
+ /**
31
+ * Render the consumer's single child element instead of a `<button>`, keeping
32
+ * the styling. Use this whenever the control is really a link or a router
33
+ * link — never to work around a nested-interactive problem.
34
+ */
35
+ asChild?: boolean | undefined;
36
+ /**
37
+ * The element to render instead of `<button>`. Prefer `asChild`; this exists
38
+ * for cases where the consumer must supply an element TEA UI cannot wrap.
39
+ */
40
+ as?: React.ElementType | undefined;
41
+ /**
42
+ * The control is performing an action. Shows a spinner, keeps the width, sets
43
+ * `aria-busy`, and blocks repeat activation — it does *not* collapse to a
44
+ * disabled control, so the label does not shift and the user can still read it.
45
+ */
46
+ loading?: boolean | undefined;
47
+ /** Accessible name for the spinner announced while `loading`. */
48
+ loadingLabel?: string | undefined;
49
+ }
50
+ declare const Button: React.ForwardRefExoticComponent<Omit<ButtonProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
51
+
52
+ export { Button, type ButtonProps, buttonVariants };
@@ -0,0 +1,84 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { Slot } from "radix-ui";
4
+ import { cva, cn } from "@tea-ui/utils";
5
+ import { stateAttributes } from "../internal/index.js";
6
+ import { Spinner } from "../feedback/spinner.js";
7
+ const buttonVariants = cva(
8
+ [
9
+ "relative inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap",
10
+ "border border-transparent font-medium",
11
+ "transition-[background-color,border-color,color,box-shadow,transform] duration-fast ease-standard",
12
+ "disabled:pointer-events-none disabled:opacity-50",
13
+ "aria-disabled:pointer-events-none aria-disabled:opacity-50",
14
+ // A pressed control moves 1px. It is the cheapest possible tactile feedback
15
+ // and the only one available on a touch device.
16
+ "active:translate-y-px"
17
+ ],
18
+ {
19
+ variants: {
20
+ variant: {
21
+ primary: "bg-primary text-primary-fg border-primary hover:bg-primary-hover hover:border-primary-hover",
22
+ secondary: "bg-surface-3 text-fg border-line hover:bg-line hover:border-line-strong",
23
+ outline: "bg-transparent text-fg border-line hover:bg-surface-2 hover:border-line-strong",
24
+ ghost: "bg-transparent text-fg-muted border-transparent hover:bg-surface-3 hover:text-fg",
25
+ subtle: "bg-primary-subtle text-primary border-primary-border hover:bg-primary-subtle/80",
26
+ destructive: "bg-destructive text-destructive-fg border-destructive hover:brightness-110",
27
+ link: "bg-transparent text-primary border-transparent underline-offset-4 hover:underline p-0 h-auto"
28
+ },
29
+ size: {
30
+ sm: "h-[calc(var(--tea-control-h)-0.25rem)] px-[calc(var(--tea-control-px)-0.25rem)] text-micro",
31
+ md: "control-h px-[length:var(--tea-control-px)] text-ui",
32
+ lg: "h-[calc(var(--tea-control-h)+0.25rem)] px-[calc(var(--tea-control-px)+0.25rem)] text-ui",
33
+ "icon-sm": "size-[calc(var(--tea-control-h)-0.25rem)] p-0 [--tea-control-icon:0.875rem] has-[svg]:size-[length:var(--tea-control-icon)]",
34
+ "icon-md": "size-[length:var(--tea-control-h)] p-0 [--tea-control-icon:1rem] has-[svg]:size-[length:var(--tea-control-icon)]",
35
+ "icon-lg": "size-[calc(var(--tea-control-h)+0.5rem)] p-0 [--tea-control-icon:1.25rem] has-[svg]:size-[length:var(--tea-control-icon)]"
36
+ }
37
+ },
38
+ defaultVariants: {
39
+ variant: "primary",
40
+ size: "md"
41
+ }
42
+ }
43
+ );
44
+ const Button = React.forwardRef(function Button2({
45
+ className,
46
+ variant,
47
+ size,
48
+ asChild = false,
49
+ as,
50
+ loading = false,
51
+ loadingLabel,
52
+ disabled,
53
+ type,
54
+ children,
55
+ ...props
56
+ }, ref) {
57
+ const Component = asChild ? Slot.Root : as ?? "button";
58
+ const isDisabled = disabled === true || loading;
59
+ return /* @__PURE__ */ jsxs(
60
+ Component,
61
+ {
62
+ ref,
63
+ className: cn(buttonVariants({ variant, size, className })),
64
+ ...asChild ? {} : { type: type ?? "button", disabled: isDisabled, "aria-busy": loading || void 0 },
65
+ "aria-disabled": loading || void 0,
66
+ onClick: loading ? (event) => {
67
+ event.preventDefault();
68
+ event.stopPropagation();
69
+ } : props.onClick,
70
+ ...stateAttributes({ disabled: isDisabled, loading }),
71
+ "data-tea-touch": true,
72
+ ...props,
73
+ children: [
74
+ loading ? /* @__PURE__ */ jsx(Spinner, { size: "sm", label: loadingLabel ?? "", className: "-ms-1" }) : null,
75
+ children
76
+ ]
77
+ }
78
+ );
79
+ });
80
+ export {
81
+ Button,
82
+ buttonVariants
83
+ };
84
+ //# sourceMappingURL=button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/inputs/button.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Slot } from \"radix-ui\";\nimport { cva, cn, type VariantProps } from \"@tea-ui/utils\";\n\nimport { stateAttributes } from \"../internal\";\nimport { Spinner } from \"../feedback/spinner\";\n\n/**\n * TEA UI — Button. The reference implementation for every component in TEA UI.\n *\n * Read this file before writing another component. It encodes the house style:\n *\n * 1. `cva` for the variant surface, always with `className` passed *into* the\n * call so a consumer's class merges correctly. (HomeServerManager's Button\n * did this and its Badge did not — which is how the two drifted.)\n * 2. `variant` and `size` are the only styling axes. There is no `color` prop\n * and no `className` escape hatch that replaces a variant.\n * 3. State is exposed as data attributes, not only as styling.\n * 4. `asChild` is available on every component that renders an element, so a\n * consumer can keep native semantics (a real `<a>`) and still get the\n * styles.\n * 5. `loading` is a first-class state, not a `disabled` alias: it keeps the\n * control's width, announces itself, and blocks repeat activation.\n * 6. `type` defaults to `\"button\"`. A TEA UI button inside a form is not a\n * submit button by accident.\n * 7. The ref is forwarded.\n */\nexport const buttonVariants = cva(\n [\n \"relative inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap\",\n \"border border-transparent font-medium\",\n \"transition-[background-color,border-color,color,box-shadow,transform] duration-fast ease-standard\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n \"aria-disabled:pointer-events-none aria-disabled:opacity-50\",\n // A pressed control moves 1px. It is the cheapest possible tactile feedback\n // and the only one available on a touch device.\n \"active:translate-y-px\",\n ],\n {\n variants: {\n variant: {\n primary:\n \"bg-primary text-primary-fg border-primary hover:bg-primary-hover hover:border-primary-hover\",\n secondary: \"bg-surface-3 text-fg border-line hover:bg-line hover:border-line-strong\",\n outline: \"bg-transparent text-fg border-line hover:bg-surface-2 hover:border-line-strong\",\n ghost: \"bg-transparent text-fg-muted border-transparent hover:bg-surface-3 hover:text-fg\",\n subtle: \"bg-primary-subtle text-primary border-primary-border hover:bg-primary-subtle/80\",\n destructive: \"bg-destructive text-destructive-fg border-destructive hover:brightness-110\",\n link: \"bg-transparent text-primary border-transparent underline-offset-4 hover:underline p-0 h-auto\",\n },\n size: {\n sm: \"h-[calc(var(--tea-control-h)-0.25rem)] px-[calc(var(--tea-control-px)-0.25rem)] text-micro\",\n md: \"control-h px-[length:var(--tea-control-px)] text-ui\",\n lg: \"h-[calc(var(--tea-control-h)+0.25rem)] px-[calc(var(--tea-control-px)+0.25rem)] text-ui\",\n \"icon-sm\":\n \"size-[calc(var(--tea-control-h)-0.25rem)] p-0 [--tea-control-icon:0.875rem] has-[svg]:size-[length:var(--tea-control-icon)]\",\n \"icon-md\":\n \"size-[length:var(--tea-control-h)] p-0 [--tea-control-icon:1rem] has-[svg]:size-[length:var(--tea-control-icon)]\",\n \"icon-lg\":\n \"size-[calc(var(--tea-control-h)+0.5rem)] p-0 [--tea-control-icon:1.25rem] has-[svg]:size-[length:var(--tea-control-icon)]\",\n },\n },\n defaultVariants: {\n variant: \"primary\",\n size: \"md\",\n },\n },\n);\n\nexport interface ButtonProps\n extends Omit<React.ComponentProps<\"button\">, \"color\">,\n VariantProps<typeof buttonVariants> {\n /**\n * Render the consumer's single child element instead of a `<button>`, keeping\n * the styling. Use this whenever the control is really a link or a router\n * link — never to work around a nested-interactive problem.\n */\n asChild?: boolean | undefined;\n /**\n * The element to render instead of `<button>`. Prefer `asChild`; this exists\n * for cases where the consumer must supply an element TEA UI cannot wrap.\n */\n as?: React.ElementType | undefined;\n /**\n * The control is performing an action. Shows a spinner, keeps the width, sets\n * `aria-busy`, and blocks repeat activation — it does *not* collapse to a\n * disabled control, so the label does not shift and the user can still read it.\n */\n loading?: boolean | undefined;\n /** Accessible name for the spinner announced while `loading`. */\n loadingLabel?: string | undefined;\n}\n\nexport const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n className,\n variant,\n size,\n asChild = false,\n as,\n loading = false,\n loadingLabel,\n disabled,\n type,\n children,\n ...props\n },\n ref,\n) {\n const Component = asChild ? Slot.Root : (as ?? \"button\");\n const isDisabled = disabled === true || loading;\n\n return (\n <Component\n ref={ref as React.Ref<HTMLButtonElement>}\n className={cn(buttonVariants({ variant, size, className }))}\n {...(asChild\n ? {}\n : ({ type: type ?? \"button\", disabled: isDisabled, \"aria-busy\": loading || undefined } as const))}\n aria-disabled={loading || undefined}\n // A loading button is a real `disabled` element, not a polite one: repeat\n // activation must be impossible, or an impatient second click submits a\n // save twice. The label stays readable and the width does not change.\n onClick={\n loading\n ? (event: React.MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n }\n : props.onClick\n }\n {...stateAttributes({ disabled: isDisabled, loading })}\n data-tea-touch\n {...props}\n >\n {loading ? <Spinner size=\"sm\" label={loadingLabel ?? \"\"} className=\"-ms-1\" /> : null}\n {children}\n </Component>\n );\n});\n\n"],"mappings":"AAiHI,SAsBa,KAtBb;AAjHH,YAAY,WAAW;AACxB,SAAS,YAAY;AACrB,SAAS,KAAK,UAA6B;AAE3C,SAAS,uBAAuB;AAChC,SAAS,eAAe;AAsBjB,MAAM,iBAAiB;AAAA,EAC5B;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SACE;AAAA,QACF,WAAW;AAAA,QACX,SAAS;AAAA,QACT,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,MAAM;AAAA,MACR;AAAA,MACA,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,WACE;AAAA,QACF,WACE;AAAA,QACF,WACE;AAAA,MACJ;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AA0BO,MAAM,SAAS,MAAM,WAA2C,SAASA,QAC9E;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GACA,KACA;AACA,QAAM,YAAY,UAAU,KAAK,OAAQ,MAAM;AAC/C,QAAM,aAAa,aAAa,QAAQ;AAExC,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,UAAU,CAAC,CAAC;AAAA,MACzD,GAAI,UACD,CAAC,IACA,EAAE,MAAM,QAAQ,UAAU,UAAU,YAAY,aAAa,WAAW,OAAU;AAAA,MACvF,iBAAe,WAAW;AAAA,MAI1B,SACE,UACI,CAAC,UAA4B;AAC3B,cAAM,eAAe;AACrB,cAAM,gBAAgB;AAAA,MACxB,IACA,MAAM;AAAA,MAEX,GAAG,gBAAgB,EAAE,UAAU,YAAY,QAAQ,CAAC;AAAA,MACrD,kBAAc;AAAA,MACb,GAAG;AAAA,MAEH;AAAA,kBAAU,oBAAC,WAAQ,MAAK,MAAK,OAAO,gBAAgB,IAAI,WAAU,SAAQ,IAAK;AAAA,QAC/E;AAAA;AAAA;AAAA,EACH;AAEJ,CAAC;","names":["Button"]}
@@ -0,0 +1,48 @@
1
+ import * as React from 'react';
2
+ import { Checkbox as Checkbox$1 } from 'radix-ui';
3
+
4
+ /**
5
+ * TEA UI — Checkbox.
6
+ *
7
+ * A native `role="checkbox"` wrapped in Radix's primitive, which renders a
8
+ * `<button>` plus a hidden native `<input>` so the control still participates in
9
+ * form submission, in form reset, and in the browser's own validation UI.
10
+ *
11
+ * The naming question, which is where this component could have gone wrong:
12
+ *
13
+ * - **Inside a `Field`**: `FieldLabel` renders `<label htmlFor>` pointing at the
14
+ * control's id. A `<button>` is a *labelable element* in HTML, so the
15
+ * association is real rather than a convention that happens to work.
16
+ * - **Standalone**: `label` wraps the control and its text in a `<label>`, which
17
+ * gives the same implicit association.
18
+ *
19
+ * `indeterminate` exists because "some but not all" is a real state of a parent
20
+ * checkbox that no amount of colour can express. It is reported as
21
+ * `aria-checked="mixed"` and drawn as a dash. Clearing it is the *parent's* job:
22
+ * pass `indeterminate={someSelected}` and update it in `onCheckedChange`.
23
+ */
24
+ interface CheckboxProps extends Omit<React.ComponentProps<typeof Checkbox$1.Root>, "checked" | "defaultChecked" | "indeterminate" | "children"> {
25
+ /** Controlled checked state. */
26
+ checked?: boolean | undefined;
27
+ /** Uncontrolled initial checked state. */
28
+ defaultChecked?: boolean | undefined;
29
+ /** Called when the checked state changes, in both controlled and uncontrolled mode. */
30
+ onCheckedChange?: ((checked: boolean) => void) | undefined;
31
+ /** Some but not all children are checked. Draws a dash, reports `aria-checked="mixed"`. */
32
+ indeterminate?: boolean | undefined;
33
+ /** Text next to the box, for a control used without a `Field`. */
34
+ label?: React.ReactNode | undefined;
35
+ className?: string | undefined;
36
+ }
37
+ /**
38
+ * @example
39
+ * ```tsx
40
+ * <Field>
41
+ * <FieldLabel>Benachrichtigungen per E-Mail</FieldLabel>
42
+ * <Checkbox defaultChecked />
43
+ * </Field>
44
+ * ```
45
+ */
46
+ declare const Checkbox: React.ForwardRefExoticComponent<Omit<CheckboxProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
47
+
48
+ export { Checkbox, type CheckboxProps };
@@ -0,0 +1,74 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { Checkbox as CheckboxPrimitive } from "radix-ui";
4
+ import { Check, Minus } from "@tea-ui/icons";
5
+ import { cn } from "@tea-ui/utils";
6
+ import { dataSlot, stateAttributes, useControllableState } from "../internal/index.js";
7
+ import { useFieldControlProps } from "./field.js";
8
+ const Checkbox = React.forwardRef(function Checkbox2({
9
+ className,
10
+ label,
11
+ checked,
12
+ defaultChecked = false,
13
+ onCheckedChange,
14
+ indeterminate = false,
15
+ disabled,
16
+ required,
17
+ ...props
18
+ }, ref) {
19
+ const field = useFieldControlProps();
20
+ const isDisabled = field.disabled ?? disabled ?? false;
21
+ const isRequired = field["aria-required"] ?? required ?? false;
22
+ const [isChecked, setIsChecked] = useControllableState({
23
+ value: checked,
24
+ defaultValue: defaultChecked,
25
+ onChange: (next) => onCheckedChange?.(next),
26
+ name: "Checkbox"
27
+ });
28
+ const control = /* @__PURE__ */ jsx(
29
+ CheckboxPrimitive.Root,
30
+ {
31
+ ref,
32
+ id: field.id,
33
+ checked: indeterminate ? "indeterminate" : isChecked,
34
+ onCheckedChange: (next) => setIsChecked(next === true),
35
+ disabled: isDisabled,
36
+ required: isRequired,
37
+ "aria-invalid": field["aria-invalid"],
38
+ "aria-describedby": field["aria-describedby"],
39
+ "aria-required": isRequired || void 0,
40
+ "data-tea-touch": true,
41
+ className: cn(
42
+ // `rounded-pill` is one of the seven documented exceptions to TEA
43
+ // geometry: a checkbox is a control marker, not a surface.
44
+ "size-4 shrink-0 rounded-pill border border-line-strong bg-surface",
45
+ "inline-flex items-center justify-center p-0 text-primary-fg",
46
+ "transition-[background-color,border-color,box-shadow] duration-[120ms] ease-standard",
47
+ "outline-none",
48
+ "focus-visible:border-ring-strong focus-visible:ring-2 focus-visible:ring-ring",
49
+ "data-[state=checked]:border-primary data-[state=checked]:bg-primary",
50
+ "data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary",
51
+ "disabled:cursor-not-allowed disabled:opacity-50",
52
+ "aria-[invalid=true]:border-critical",
53
+ className
54
+ ),
55
+ ...stateAttributes({
56
+ disabled: isDisabled,
57
+ invalid: field["aria-invalid"] === true,
58
+ checked: isChecked
59
+ }),
60
+ ...dataSlot("checkbox"),
61
+ ...props,
62
+ children: /* @__PURE__ */ jsx(CheckboxPrimitive.Indicator, { className: "size-full", children: indeterminate ? /* @__PURE__ */ jsx(Minus, { className: "size-full" }) : /* @__PURE__ */ jsx(Check, { className: "size-full" }) })
63
+ }
64
+ );
65
+ if (label === void 0 || label === null || label === false) return control;
66
+ return /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-ui", ...dataSlot("checkbox-label"), children: [
67
+ control,
68
+ /* @__PURE__ */ jsx("span", { children: label })
69
+ ] });
70
+ });
71
+ export {
72
+ Checkbox
73
+ };
74
+ //# sourceMappingURL=checkbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/inputs/checkbox.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Checkbox as CheckboxPrimitive } from \"radix-ui\";\nimport { Check, Minus } from \"@tea-ui/icons\";\nimport { cn } from \"@tea-ui/utils\";\n\nimport { dataSlot, stateAttributes, useControllableState } from \"../internal\";\nimport { useFieldControlProps } from \"./field\";\n\n/**\n * TEA UI — Checkbox.\n *\n * A native `role=\"checkbox\"` wrapped in Radix's primitive, which renders a\n * `<button>` plus a hidden native `<input>` so the control still participates in\n * form submission, in form reset, and in the browser's own validation UI.\n *\n * The naming question, which is where this component could have gone wrong:\n *\n * - **Inside a `Field`**: `FieldLabel` renders `<label htmlFor>` pointing at the\n * control's id. A `<button>` is a *labelable element* in HTML, so the\n * association is real rather than a convention that happens to work.\n * - **Standalone**: `label` wraps the control and its text in a `<label>`, which\n * gives the same implicit association.\n *\n * `indeterminate` exists because \"some but not all\" is a real state of a parent\n * checkbox that no amount of colour can express. It is reported as\n * `aria-checked=\"mixed\"` and drawn as a dash. Clearing it is the *parent's* job:\n * pass `indeterminate={someSelected}` and update it in `onCheckedChange`.\n */\nexport interface CheckboxProps\n extends Omit<\n React.ComponentProps<typeof CheckboxPrimitive.Root>,\n \"checked\" | \"defaultChecked\" | \"indeterminate\" | \"children\"\n > {\n /** Controlled checked state. */\n checked?: boolean | undefined;\n /** Uncontrolled initial checked state. */\n defaultChecked?: boolean | undefined;\n /** Called when the checked state changes, in both controlled and uncontrolled mode. */\n onCheckedChange?: ((checked: boolean) => void) | undefined;\n /** Some but not all children are checked. Draws a dash, reports `aria-checked=\"mixed\"`. */\n indeterminate?: boolean | undefined;\n /** Text next to the box, for a control used without a `Field`. */\n label?: React.ReactNode | undefined;\n className?: string | undefined;\n}\n\n/**\n * @example\n * ```tsx\n * <Field>\n * <FieldLabel>Benachrichtigungen per E-Mail</FieldLabel>\n * <Checkbox defaultChecked />\n * </Field>\n * ```\n */\nexport const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(function Checkbox(\n {\n className,\n label,\n checked,\n defaultChecked = false,\n onCheckedChange,\n indeterminate = false,\n disabled,\n required,\n ...props\n },\n ref,\n) {\n const field = useFieldControlProps();\n const isDisabled = field.disabled ?? disabled ?? false;\n const isRequired = field[\"aria-required\"] ?? required ?? false;\n\n // The state is held here rather than inside Radix so that `data-checked` is\n // exact in *uncontrolled* use too. Passing a controlled value to Radix and\n // asking the DOM afterwards would be a second source of truth for the one\n // thing the test suite asserts on.\n const [isChecked, setIsChecked] = useControllableState({\n value: checked,\n defaultValue: defaultChecked,\n onChange: (next) => onCheckedChange?.(next),\n name: \"Checkbox\",\n });\n\n const control = (\n <CheckboxPrimitive.Root\n ref={ref}\n id={field.id}\n checked={indeterminate ? \"indeterminate\" : isChecked}\n onCheckedChange={(next) => setIsChecked(next === true)}\n disabled={isDisabled}\n required={isRequired}\n aria-invalid={field[\"aria-invalid\"]}\n aria-describedby={field[\"aria-describedby\"]}\n aria-required={isRequired || undefined}\n data-tea-touch\n className={cn(\n // `rounded-pill` is one of the seven documented exceptions to TEA\n // geometry: a checkbox is a control marker, not a surface.\n \"size-4 shrink-0 rounded-pill border border-line-strong bg-surface\",\n \"inline-flex items-center justify-center p-0 text-primary-fg\",\n \"transition-[background-color,border-color,box-shadow] duration-[120ms] ease-standard\",\n \"outline-none\",\n \"focus-visible:border-ring-strong focus-visible:ring-2 focus-visible:ring-ring\",\n \"data-[state=checked]:border-primary data-[state=checked]:bg-primary\",\n \"data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"aria-[invalid=true]:border-critical\",\n className,\n )}\n {...stateAttributes({\n disabled: isDisabled,\n invalid: field[\"aria-invalid\"] === true,\n checked: isChecked,\n })}\n {...dataSlot(\"checkbox\")}\n {...props}\n >\n {/* Radix mounts the indicator for the `checked` *and* the `indeterminate`\n state, so nothing here is forced into the DOM. */}\n <CheckboxPrimitive.Indicator className=\"size-full\">\n {indeterminate ? <Minus className=\"size-full\" /> : <Check className=\"size-full\" />}\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n );\n\n if (label === undefined || label === null || label === false) return control;\n\n return (\n <label className=\"inline-flex items-center gap-2 text-ui\" {...dataSlot(\"checkbox-label\")}>\n {control}\n <span>{label}</span>\n </label>\n );\n});\n"],"mappings":"AAyHyB,cAQrB,YARqB;AAzHxB,YAAY,WAAW;AACxB,SAAS,YAAY,yBAAyB;AAC9C,SAAS,OAAO,aAAa;AAC7B,SAAS,UAAU;AAEnB,SAAS,UAAU,iBAAiB,4BAA4B;AAChE,SAAS,4BAA4B;AAiD9B,MAAM,WAAW,MAAM,WAA6C,SAASA,UAClF;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GACA,KACA;AACA,QAAM,QAAQ,qBAAqB;AACnC,QAAM,aAAa,MAAM,YAAY,YAAY;AACjD,QAAM,aAAa,MAAM,eAAe,KAAK,YAAY;AAMzD,QAAM,CAAC,WAAW,YAAY,IAAI,qBAAqB;AAAA,IACrD,OAAO;AAAA,IACP,cAAc;AAAA,IACd,UAAU,CAAC,SAAS,kBAAkB,IAAI;AAAA,IAC1C,MAAM;AAAA,EACR,CAAC;AAED,QAAM,UACJ;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC;AAAA,MACA,IAAI,MAAM;AAAA,MACV,SAAS,gBAAgB,kBAAkB;AAAA,MAC3C,iBAAiB,CAAC,SAAS,aAAa,SAAS,IAAI;AAAA,MACrD,UAAU;AAAA,MACV,UAAU;AAAA,MACV,gBAAc,MAAM,cAAc;AAAA,MAClC,oBAAkB,MAAM,kBAAkB;AAAA,MAC1C,iBAAe,cAAc;AAAA,MAC7B,kBAAc;AAAA,MACd,WAAW;AAAA;AAAA;AAAA,QAGT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG,gBAAgB;AAAA,QAClB,UAAU;AAAA,QACV,SAAS,MAAM,cAAc,MAAM;AAAA,QACnC,SAAS;AAAA,MACX,CAAC;AAAA,MACA,GAAG,SAAS,UAAU;AAAA,MACtB,GAAG;AAAA,MAIJ,8BAAC,kBAAkB,WAAlB,EAA4B,WAAU,aACpC,0BAAgB,oBAAC,SAAM,WAAU,aAAY,IAAK,oBAAC,SAAM,WAAU,aAAY,GAClF;AAAA;AAAA,EACF;AAGF,MAAI,UAAU,UAAa,UAAU,QAAQ,UAAU,MAAO,QAAO;AAErE,SACE,qBAAC,WAAM,WAAU,0CAA0C,GAAG,SAAS,gBAAgB,GACpF;AAAA;AAAA,IACD,oBAAC,UAAM,iBAAM;AAAA,KACf;AAEJ,CAAC;","names":["Checkbox"]}
@@ -0,0 +1,77 @@
1
+ import * as class_variance_authority_types from 'class-variance-authority/types';
2
+ import * as React from 'react';
3
+ import { VariantProps } from '@tea-ui/utils';
4
+
5
+ /**
6
+ * TEA UI — Combobox.
7
+ *
8
+ * The ARIA 1.2 combobox/listbox pattern, implemented properly. This component
9
+ * exists because the audit's global search had *no* combobox semantics at all:
10
+ * no `role="combobox"`, no `aria-expanded`, no `aria-activedescendant`, no arrow
11
+ * keys, and results that were plain buttons. A screen-reader user and a
12
+ * keyboard-only user both had no way to know a result list existed, let alone
13
+ * move through it.
14
+ *
15
+ * The pattern, in full:
16
+ * - the input is the combobox: `role="combobox"`, `aria-expanded`,
17
+ * `aria-controls` pointing at the list, `aria-activedescendant` pointing at
18
+ * the active option;
19
+ * - the list is `role="listbox"`, the options are `role="option"` with
20
+ * `aria-selected`;
21
+ * - ArrowDown/ArrowUp move the active option and scroll it into view,
22
+ * Home/End jump, Enter selects, Escape reverts the query first and closes
23
+ * second;
24
+ * - the active option is tracked with `aria-activedescendant`, never by moving
25
+ * DOM focus, so the query text is not destroyed on every keystroke.
26
+ *
27
+ * Options are supplied, not fetched. The component takes `options` and an
28
+ * optional `filter`; async resolution belongs to the product, which is why
29
+ * `loading` and `emptyMessage` are props rather than a data source.
30
+ */
31
+ interface ComboboxOption {
32
+ /** The value emitted on selection. */
33
+ readonly value: string;
34
+ /** The visible label. */
35
+ readonly label: string;
36
+ /** Optional secondary text. Never the only carrier of meaning. */
37
+ readonly description?: string | undefined;
38
+ /** Group heading. Rendered as a `role="presentation"` separator row. */
39
+ readonly group?: string | undefined;
40
+ /** Disabled options stay visible and stay focusable, but cannot be chosen. */
41
+ readonly disabled?: boolean | undefined;
42
+ }
43
+ interface ComboboxProps extends Omit<React.ComponentProps<"input">, "value" | "defaultValue" | "onChange" | "onSelect" | "size">, Omit<VariantProps<typeof comboboxTriggerVariants>, "size"> {
44
+ /** The accessible name. Required — the search icon is not a label. */
45
+ label: string;
46
+ options: readonly ComboboxOption[];
47
+ /** The controlled value. */
48
+ value?: string | undefined;
49
+ /** The uncontrolled initial value. */
50
+ defaultValue?: string | undefined;
51
+ /** Called with the emitted `value` of the chosen option. */
52
+ onValueChange?: ((value: string) => undefined | void) | undefined;
53
+ /** Called on every keystroke, with the raw query. */
54
+ onQueryChange?: ((query: string) => void) | undefined;
55
+ /**
56
+ * Filtering. Defaults to a case- and diacritic-insensitive substring match
57
+ * over the label, so a product that needs fuzzy search opts in rather than
58
+ * getting it by accident.
59
+ */
60
+ filter?: ((option: ComboboxOption, query: string) => boolean) | undefined;
61
+ /** Shown when the query matches nothing. */
62
+ emptyMessage?: string | undefined;
63
+ /** A search is running. Shows a spinner and sets `aria-busy`. */
64
+ loading?: boolean | undefined;
65
+ /** Disable the whole control. */
66
+ disabled?: boolean | undefined;
67
+ /** Allow choosing more than one option. */
68
+ multiple?: boolean | undefined;
69
+ className?: string | undefined;
70
+ }
71
+ declare const comboboxTriggerVariants: (props?: ({
72
+ variant?: "outline" | "ghost" | null | undefined;
73
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
74
+ /** Stable, id-safe DOM ids for one listbox. */
75
+ declare const Combobox: React.ForwardRefExoticComponent<Omit<ComboboxProps, "ref"> & React.RefAttributes<HTMLInputElement>>;
76
+
77
+ export { Combobox, type ComboboxOption, type ComboboxProps, comboboxTriggerVariants };