@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,183 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { Label as LabelPrimitive } from "radix-ui";
4
+ import { cn } from "@tea-ui/utils";
5
+ import { COPY } from "@tea-ui/ux-standards";
6
+ import { dataSlot, renderElement, stateAttributes } from "../internal/index.js";
7
+ const FieldContext = React.createContext(null);
8
+ const FieldPartsContext = React.createContext(null);
9
+ function useField() {
10
+ return React.useContext(FieldContext);
11
+ }
12
+ function useFieldControlProps() {
13
+ const field = React.useContext(FieldContext);
14
+ const parts = React.useContext(FieldPartsContext);
15
+ if (!field) {
16
+ return {
17
+ id: void 0,
18
+ "aria-describedby": void 0,
19
+ "aria-invalid": void 0,
20
+ "aria-required": void 0,
21
+ disabled: void 0,
22
+ readOnly: void 0
23
+ };
24
+ }
25
+ const describedBy = [
26
+ parts?.hasDescription ? field.descriptionId : null,
27
+ parts?.hasError ? field.errorId : null
28
+ ].filter(Boolean).join(" ");
29
+ return {
30
+ id: field.id,
31
+ "aria-describedby": describedBy || void 0,
32
+ "aria-invalid": field.isInvalid || void 0,
33
+ "aria-required": field.isRequired || void 0,
34
+ disabled: field.isDisabled || void 0,
35
+ readOnly: field.isReadOnly || void 0
36
+ };
37
+ }
38
+ const Field = React.forwardRef(function Field2({
39
+ children,
40
+ className,
41
+ invalid = false,
42
+ required = false,
43
+ disabled = false,
44
+ readOnly = false,
45
+ asChild = false,
46
+ as,
47
+ idBase,
48
+ ...props
49
+ }, ref) {
50
+ const reactId = React.useId();
51
+ const stem = idBase ?? reactId.replace(/:/g, "");
52
+ const [parts, setParts] = React.useState({ description: false, error: false });
53
+ const register = React.useCallback((part, present) => {
54
+ setParts((prev) => prev[part] === present ? prev : { ...prev, [part]: present });
55
+ }, []);
56
+ const value = React.useMemo(
57
+ () => ({
58
+ name: stem,
59
+ id: `${stem}-control`,
60
+ descriptionId: `${stem}-description`,
61
+ errorId: `${stem}-error`,
62
+ isInvalid: invalid,
63
+ isRequired: required,
64
+ isDisabled: disabled,
65
+ isReadOnly: readOnly
66
+ }),
67
+ [stem, invalid, required, disabled, readOnly]
68
+ );
69
+ const partsValue = React.useMemo(
70
+ () => ({ hasDescription: parts.description, hasError: parts.error, register }),
71
+ [parts, register]
72
+ );
73
+ return /* @__PURE__ */ jsx(FieldContext.Provider, { value, children: /* @__PURE__ */ jsx(FieldPartsContext.Provider, { value: partsValue, children: renderElement(
74
+ {
75
+ asChild,
76
+ as,
77
+ className: cn("flex min-w-0 flex-col gap-2", className),
78
+ children,
79
+ ref,
80
+ ...stateAttributes({ disabled, invalid, readOnly }),
81
+ ...dataSlot("field"),
82
+ ...props
83
+ },
84
+ "div"
85
+ ) }) });
86
+ });
87
+ const FieldLabel = React.forwardRef(function FieldLabel2({ children, className, required, htmlFor, ...props }, ref) {
88
+ const field = useField();
89
+ const isRequired = required ?? field?.isRequired ?? false;
90
+ return /* @__PURE__ */ jsxs(
91
+ LabelPrimitive.Root,
92
+ {
93
+ ref,
94
+ htmlFor: htmlFor ?? field?.id,
95
+ className: cn("inline-flex items-center gap-1 text-ui font-medium text-fg", className),
96
+ ...dataSlot("field-label"),
97
+ ...props,
98
+ children: [
99
+ children,
100
+ isRequired ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-critical", title: COPY.states.required, children: "*" }) : null
101
+ ]
102
+ }
103
+ );
104
+ });
105
+ function useFieldPart(part, present) {
106
+ const parts = React.useContext(FieldPartsContext);
107
+ const register = parts?.register;
108
+ React.useEffect(() => {
109
+ register?.(part, present);
110
+ return () => register?.(part, false);
111
+ }, [register, part, present]);
112
+ }
113
+ const FieldDescription = React.forwardRef(
114
+ function FieldDescription2({ children, className, ...props }, ref) {
115
+ const field = useField();
116
+ const present = children !== null && children !== void 0 && children !== false;
117
+ useFieldPart("description", present);
118
+ if (!present || !field) return null;
119
+ return /* @__PURE__ */ jsx(
120
+ "p",
121
+ {
122
+ ref,
123
+ id: field.descriptionId,
124
+ className: cn("text-micro text-fg-muted", className),
125
+ ...dataSlot("field-description"),
126
+ ...props,
127
+ children
128
+ }
129
+ );
130
+ }
131
+ );
132
+ const FieldError = React.forwardRef(function FieldError2({ children, className, ...props }, ref) {
133
+ const field = useField();
134
+ const present = children !== null && children !== void 0 && children !== false && children !== "";
135
+ useFieldPart("error", present);
136
+ if (!present || !field) return null;
137
+ return /* @__PURE__ */ jsx(
138
+ "p",
139
+ {
140
+ ref,
141
+ id: field.errorId,
142
+ role: "alert",
143
+ className: cn("text-micro font-medium text-critical", className),
144
+ ...dataSlot("field-error"),
145
+ ...props,
146
+ children
147
+ }
148
+ );
149
+ });
150
+ const FieldGroup = React.forwardRef(function FieldGroup2({ children, className, legend, description, disabled, ...props }, ref) {
151
+ const descriptionId = React.useId().replace(/:/g, "");
152
+ return /* @__PURE__ */ jsxs(
153
+ "fieldset",
154
+ {
155
+ ref,
156
+ disabled,
157
+ "aria-describedby": description ? descriptionId : void 0,
158
+ className: cn(
159
+ "flex min-w-0 flex-col field-gap",
160
+ "disabled:opacity-50",
161
+ className
162
+ ),
163
+ ...stateAttributes({ disabled }),
164
+ ...dataSlot("field-group"),
165
+ ...props,
166
+ children: [
167
+ legend ? /* @__PURE__ */ jsx("legend", { className: "mb-1 text-ui font-semibold text-fg", ...dataSlot("field-legend"), children: legend }) : null,
168
+ description ? /* @__PURE__ */ jsx("p", { id: descriptionId, className: "-mt-1 text-micro text-fg-muted", children: description }) : null,
169
+ children
170
+ ]
171
+ }
172
+ );
173
+ });
174
+ export {
175
+ Field,
176
+ FieldDescription,
177
+ FieldError,
178
+ FieldGroup,
179
+ FieldLabel,
180
+ useField,
181
+ useFieldControlProps
182
+ };
183
+ //# sourceMappingURL=field.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/inputs/field.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Label as LabelPrimitive } from \"radix-ui\";\nimport { cn } from \"@tea-ui/utils\";\nimport { COPY } from \"@tea-ui/ux-standards\";\n\nimport { dataSlot, renderElement, stateAttributes } from \"../internal\";\n\n/**\n * TEA UI — the Field system.\n *\n * This file exists because of a defect the audit found in **four of five**\n * field implementations across the two source projects: a `<label>` that was\n * never associated with its control. The consequence is not subtle — a screen\n * reader user reaches a text field and is told nothing about what belongs in\n * it, and a voice-control user simply cannot fill the form, because there is no\n * name to say. It is also invisible in review, because `<label>` next to\n * `<input>` *looks* right.\n *\n * The fix is structural rather than a review rule. `Field` owns the id space;\n * `FieldLabel`, `FieldDescription` and `FieldError` write into it; and every\n * control calls {@link useFieldControlProps}. A control cannot be wired wrong,\n * because it never writes the wiring by hand:\n *\n * ```tsx\n * <Field invalid={!!error} required>\n * <FieldLabel>E-Mail</FieldLabel>\n * <Input type=\"email\" />\n * <FieldDescription>Wir senden keine Bestätigung.</FieldDescription>\n * <FieldError>{error}</FieldError>\n * </Field>\n * ```\n *\n * The four parts degrade safely. `FieldError` with no message renders nothing\n * at all, and the error id disappears from `aria-describedby` with it, so there\n * is never a description pointing at an element that does not exist.\n */\n\n/** The state a `Field` passes down to its parts. */\nexport interface FieldState {\n /** The control holds a value that failed validation. */\n invalid?: boolean | undefined;\n /** The control may not be left empty. */\n required?: boolean | undefined;\n /** The control and everything around it is unavailable. */\n disabled?: boolean | undefined;\n /** The value is visible but not editable. */\n readOnly?: boolean | undefined;\n}\n\nexport interface FieldContextValue {\n /** Base name of the field, used to build the three ids. */\n name: string;\n /** The control's id. This is what `FieldLabel` points `htmlFor` at. */\n id: string;\n /** Id of the description element, whether or not one is rendered. */\n descriptionId: string;\n /** Id of the error element, whether or not one is rendered. */\n errorId: string;\n isInvalid: boolean;\n isRequired: boolean;\n isDisabled: boolean;\n isReadOnly: boolean;\n}\n\n/**\n * Which optional parts are actually on screen.\n *\n * Kept out of {@link FieldContextValue} on purpose: it is a rendering detail,\n * not part of the contract a consumer reads, and a consumer that could write to\n * it could desynchronise the ids from the DOM.\n */\ninterface FieldPartsContextValue {\n hasDescription: boolean;\n hasError: boolean;\n register: (part: \"description\" | \"error\", present: boolean) => void;\n}\n\nconst FieldContext = React.createContext<FieldContextValue | null>(null);\nconst FieldPartsContext = React.createContext<FieldPartsContextValue | null>(null);\n\n/**\n * Read the enclosing `Field`, or `null` when a control is used standalone.\n *\n * A control outside a `Field` is legal — a search field in a toolbar has\n * nowhere sensible to put a `FieldDescription` — so every control must work\n * without one. That is why this returns `null` rather than throwing.\n */\nexport function useField(): FieldContextValue | null {\n return React.useContext(FieldContext);\n}\n\n/** The subset of a field's context a control needs to wire itself. */\nexport interface FieldControlProps {\n id: string | undefined;\n \"aria-describedby\": string | undefined;\n \"aria-invalid\": true | undefined;\n \"aria-required\": true | undefined;\n disabled: true | undefined;\n readOnly: true | undefined;\n}\n\n/**\n * Wire a control to the enclosing `Field`. Call this in **every** control.\n *\n * The returned object is exactly the six attributes a control needs, so a\n * control spreads it and is done:\n *\n * ```tsx\n * const field = useFieldControlProps();\n * return <input {...field} className={…} />;\n * ```\n *\n * `aria-describedby` lists the description *and* the error, in that order. The\n * ids are added only once the corresponding element is really on screen, so the\n * attribute never points at nothing — an `aria-describedby` reference to a\n * missing id is a validation error in axe and is silently dropped by some\n * screen readers, which leaves the error text unannounced.\n *\n * Outside a `Field` every key is `undefined`, so `{...field}` is a no-op and\n * the consumer's own `id` and handlers survive.\n */\nexport function useFieldControlProps(): FieldControlProps {\n const field = React.useContext(FieldContext);\n const parts = React.useContext(FieldPartsContext);\n\n if (!field) {\n return {\n id: undefined,\n \"aria-describedby\": undefined,\n \"aria-invalid\": undefined,\n \"aria-required\": undefined,\n disabled: undefined,\n readOnly: undefined,\n };\n }\n\n const describedBy = [\n parts?.hasDescription ? field.descriptionId : null,\n parts?.hasError ? field.errorId : null,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return {\n id: field.id,\n \"aria-describedby\": describedBy || undefined,\n \"aria-invalid\": field.isInvalid || undefined,\n \"aria-required\": field.isRequired || undefined,\n disabled: field.isDisabled || undefined,\n readOnly: field.isReadOnly || undefined,\n };\n}\n\nexport interface FieldProps extends Omit<React.ComponentProps<\"div\">, \"children\">, FieldState {\n children?: React.ReactNode;\n /** Render the consumer's single child element instead of a `<div>`. */\n asChild?: boolean | undefined;\n /** The element to render instead of `<div>`. */\n as?: React.ElementType | undefined;\n /**\n * Override the generated id stem. Only needed when the ids must be stable\n * across server and client renders; `React.useId` already is.\n */\n idBase?: string | undefined;\n}\n\n/**\n * The container that owns a field's id space and state.\n *\n * Renders a `<div>` by default and nothing else — no role, no landmark. The\n * field is a layout relationship, and inventing a role for it would put a\n * meaningless entry in the screen reader's structure list.\n *\n * @example\n * ```tsx\n * <Field required>\n * <FieldLabel>Name</FieldLabel>\n * <Input />\n * </Field>\n * ```\n */\nexport const Field = React.forwardRef<HTMLDivElement, FieldProps>(function Field(\n {\n children,\n className,\n invalid = false,\n required = false,\n disabled = false,\n readOnly = false,\n asChild = false,\n as,\n idBase,\n ...props\n },\n ref,\n) {\n // `useId` emits `:r0:`, which is a legal HTML id but an illegal CSS\n // identifier. A consumer must be able to reach the control with a selector.\n const reactId = React.useId();\n const stem = idBase ?? reactId.replace(/:/g, \"\");\n\n const [parts, setParts] = React.useState({ description: false, error: false });\n\n const register = React.useCallback((part: \"description\" | \"error\", present: boolean) => {\n setParts((prev) => (prev[part] === present ? prev : { ...prev, [part]: present }));\n }, []);\n\n const value = React.useMemo<FieldContextValue>(\n () => ({\n name: stem,\n id: `${stem}-control`,\n descriptionId: `${stem}-description`,\n errorId: `${stem}-error`,\n isInvalid: invalid,\n isRequired: required,\n isDisabled: disabled,\n isReadOnly: readOnly,\n }),\n [stem, invalid, required, disabled, readOnly],\n );\n\n const partsValue = React.useMemo<FieldPartsContextValue>(\n () => ({ hasDescription: parts.description, hasError: parts.error, register }),\n [parts, register],\n );\n\n return (\n <FieldContext.Provider value={value}>\n <FieldPartsContext.Provider value={partsValue}>\n {renderElement(\n {\n asChild,\n as,\n className: cn(\"flex min-w-0 flex-col gap-2\", className),\n children,\n ref,\n ...stateAttributes({ disabled, invalid, readOnly }),\n ...dataSlot(\"field\"),\n ...props,\n },\n \"div\",\n )}\n </FieldPartsContext.Provider>\n </FieldContext.Provider>\n );\n});\n\n\nexport interface FieldLabelProps extends React.ComponentProps<typeof LabelPrimitive.Root> {\n /**\n * Render the required marker. Defaults to the enclosing `Field`'s `required`;\n * set it explicitly to label a field as required outside one.\n */\n required?: boolean | undefined;\n}\n\n/**\n * The field's label. A real `<label>` with `htmlFor` pointing at the control.\n *\n * Uses `Label.Root` from `radix-ui` rather than a bare `<label>` so that\n * clicking the label activates the control even when the control is a Radix\n * primitive rendering a `<button>` rather than an `<input>`.\n *\n * The required marker is a visible, `aria-hidden` `*` with a `title`, and the\n * requirement itself is carried by the control's `required` / `aria-required`.\n * It is deliberately **not** a visually hidden \"Pflichtfeld\" word inside the\n * label: that would append to the control's accessible name, so the control\n * would be announced as \"E-Mail Pflichtfeld\" instead of \"E-Mail\", and a\n * consumer matching on the visible label would find nothing. The attribute\n * already says it, and it says it correctly.\n *\n * @example\n * ```tsx\n * <FieldLabel required>E-Mail</FieldLabel>\n * ```\n */\nexport const FieldLabel = React.forwardRef<HTMLLabelElement, FieldLabelProps>(function FieldLabel(\n { children, className, required, htmlFor, ...props },\n ref,\n) {\n const field = useField();\n const isRequired = required ?? field?.isRequired ?? false;\n\n return (\n <LabelPrimitive.Root\n ref={ref}\n htmlFor={htmlFor ?? field?.id}\n className={cn(\"inline-flex items-center gap-1 text-ui font-medium text-fg\", className)}\n {...dataSlot(\"field-label\")}\n {...props}\n >\n {children}\n {isRequired ? (\n <span aria-hidden=\"true\" className=\"text-critical\" title={COPY.states.required}>\n *\n </span>\n ) : null}\n </LabelPrimitive.Root>\n );\n});\n\n/**\n * Record which optional parts are mounted, so `aria-describedby` never points\n * at an element that is not there.\n *\n * Registration happens in an effect, not during render, because it changes the\n * field's state and rendering must stay side-effect free.\n */\nfunction useFieldPart(part: \"description\" | \"error\", present: boolean): void {\n const parts = React.useContext(FieldPartsContext);\n const register = parts?.register;\n React.useEffect(() => {\n register?.(part, present);\n return () => register?.(part, false);\n }, [register, part, present]);\n}\n\nexport type FieldDescriptionProps = React.ComponentProps<\"p\">;\n\n/**\n * Help text for the field, associated through `aria-describedby`.\n *\n * @example\n * ```tsx\n * <FieldDescription>Nur Großbuchstaben, Zahlen und Bindestriche.</FieldDescription>\n * ```\n */\nexport const FieldDescription = React.forwardRef<HTMLParagraphElement, FieldDescriptionProps>(\n function FieldDescription({ children, className, ...props }, ref) {\n const field = useField();\n const present = children !== null && children !== undefined && children !== false;\n useFieldPart(\"description\", present);\n\n if (!present || !field) return null;\n\n return (\n <p\n ref={ref}\n id={field.descriptionId}\n className={cn(\"text-micro text-fg-muted\", className)}\n {...dataSlot(\"field-description\")}\n {...props}\n >\n {children}\n </p>\n );\n },\n);\n\nexport interface FieldErrorProps extends Omit<React.ComponentProps<\"p\">, \"children\"> {\n /**\n * The error text. `null`, `undefined` and `false` render nothing, which is\n * what lets a caller write `<FieldError>{error}</FieldError>` and leave it in\n * the tree permanently.\n */\n children?: React.ReactNode;\n}\n\n/**\n * The field's error message: `role=\"alert\"`, so it is announced when it appears.\n *\n * A colour change is not an announcement. Without `role=\"alert\"` a validation\n * error is invisible to a screen reader user, who is left with a red box and no\n * idea what is wrong with what they just typed.\n *\n * Renders nothing when there is no message — including when it is inside a\n * `Field` and therefore participates in `aria-describedby`.\n *\n * @example\n * ```tsx\n * <FieldError>{errors.email}</FieldError>\n * ```\n */\nexport const FieldError = React.forwardRef<HTMLParagraphElement, FieldErrorProps>(function FieldError(\n { children, className, ...props },\n ref,\n) {\n const field = useField();\n const present =\n children !== null && children !== undefined && children !== false && children !== \"\";\n\n useFieldPart(\"error\", present);\n\n if (!present || !field) return null;\n\n return (\n <p\n ref={ref}\n id={field.errorId}\n role=\"alert\"\n className={cn(\"text-micro font-medium text-critical\", className)}\n {...dataSlot(\"field-error\")}\n {...props}\n >\n {children}\n </p>\n );\n});\n\nexport interface FieldGroupProps extends Omit<React.ComponentProps<\"fieldset\">, \"children\"> {\n children?: React.ReactNode;\n /**\n * The group's label. Rendered as the `<legend>`, which is what associates the\n * set of controls with a name — `aria-label` on a `<fieldset>` does not\n * become the group's accessible name in every browser.\n */\n legend?: React.ReactNode;\n /** Help text for the whole set, associated with the `<fieldset>`. */\n description?: React.ReactNode;\n}\n\n/**\n * A named set of related controls: `<fieldset>` + `<legend>`.\n *\n * A native `<fieldset disabled>` also disables every form control inside it,\n * which is the cheapest possible \"these five settings are not editable\" — a\n * `<fieldset>` in a `<form>` participates in form semantics that a `div` cannot.\n *\n * One component rather than `FieldSet` + `FieldLegend`: a legend is required\n * for the fieldset to be named, so separating them produces an unnamed group\n * whenever the legend is forgotten. The audit found plenty of those.\n *\n * @example\n * ```tsx\n * <FieldGroup legend=\"Benachrichtigungen\" disabled={saving}>\n * <Field>\n * <FieldLabel>Fehler per E-Mail</FieldLabel>\n * <Switch />\n * </Field>\n * </FieldGroup>\n * ```\n */\nexport const FieldGroup = React.forwardRef<HTMLFieldSetElement, FieldGroupProps>(function FieldGroup(\n { children, className, legend, description, disabled, ...props },\n ref,\n) {\n const descriptionId = React.useId().replace(/:/g, \"\");\n\n return (\n <fieldset\n ref={ref}\n disabled={disabled}\n aria-describedby={description ? descriptionId : undefined}\n className={cn(\n \"flex min-w-0 flex-col field-gap\",\n \"disabled:opacity-50\",\n className,\n )}\n {...stateAttributes({ disabled })}\n {...dataSlot(\"field-group\")}\n {...props}\n >\n {/* The legend must be the fieldset's first child: HTML names the set from\n the first legend, and a legend that is not first names nothing. */}\n {legend ? (\n <legend className=\"mb-1 text-ui font-semibold text-fg\" {...dataSlot(\"field-legend\")}>\n {legend}\n </legend>\n ) : null}\n {description ? (\n <p id={descriptionId} className=\"-mt-1 text-micro text-fg-muted\">\n {description}\n </p>\n ) : null}\n {children}\n </fieldset>\n );\n});\n"],"mappings":"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","names":["Field","FieldLabel","FieldDescription","FieldError","FieldGroup"]}
@@ -0,0 +1,44 @@
1
+ import * as React from 'react';
2
+ import { ButtonProps } from './button.js';
3
+ import 'class-variance-authority/types';
4
+ import '@tea-ui/utils';
5
+
6
+ /**
7
+ * TEA UI — IconButton.
8
+ *
9
+ * A button whose entire content is an icon, which is the one control shape
10
+ * where the accessible name cannot come from the content. The audit found five
11
+ * such buttons on every table row, every one of them unnamed: a screen reader
12
+ * announced "button" five times, and a voice-control user could not operate the
13
+ * table at all, because there was no word to say.
14
+ *
15
+ * The fix is not a lint rule — it is a required prop. `label` has no default
16
+ * and no `undefined` escape, so the compiler refuses the unnamed button. That
17
+ * is worth more than a whole paragraph in a style guide.
18
+ *
19
+ * The icon itself is wrapped in an `aria-hidden` span. `aria-label` on the
20
+ * button is already the name, and leaving the icon in the tree risks a second,
21
+ * conflicting name from an icon that happens to carry a `<title>`.
22
+ */
23
+ interface IconButtonProps extends Omit<ButtonProps, "size" | "children" | "asChild" | "as"> {
24
+ /**
25
+ * The accessible name. **Required** — an icon-only button without one is the
26
+ * defect this component exists to make impossible. Write the action, not the
27
+ * icon: "Server neu starten", not "Pfeil".
28
+ */
29
+ label: string;
30
+ /** Button size, mapped onto Button's `icon-*` sizes. */
31
+ size?: "sm" | "md" | "lg";
32
+ children?: React.ReactNode;
33
+ }
34
+ /**
35
+ * @example
36
+ * ```tsx
37
+ * <IconButton label="Server neu starten" variant="ghost" onClick={restart}>
38
+ * <RefreshCw />
39
+ * </IconButton>
40
+ * ```
41
+ */
42
+ declare const IconButton: React.ForwardRefExoticComponent<Omit<IconButtonProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
43
+
44
+ export { IconButton, type IconButtonProps };
@@ -0,0 +1,30 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { dataSlot } from "../internal/index.js";
4
+ import { Button } from "./button.js";
5
+ import { useButtonGroupContext } from "./button-group.js";
6
+ const ICON_SIZE = {
7
+ sm: "icon-sm",
8
+ md: "icon-md",
9
+ lg: "icon-lg"
10
+ };
11
+ const IconButton = React.forwardRef(function IconButton2({ label, size = "md", children, variant = "ghost", ...props }, ref) {
12
+ const group = useButtonGroupContext();
13
+ return /* @__PURE__ */ jsx(
14
+ Button,
15
+ {
16
+ ref,
17
+ size: ICON_SIZE[size],
18
+ variant,
19
+ "aria-label": label,
20
+ "data-tea-grouped": group ? group.orientation : void 0,
21
+ ...dataSlot("icon-button"),
22
+ ...props,
23
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "inline-flex items-center justify-center", children })
24
+ }
25
+ );
26
+ });
27
+ export {
28
+ IconButton
29
+ };
30
+ //# sourceMappingURL=icon-button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/inputs/icon-button.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { dataSlot } from \"../internal\";\nimport { Button, type ButtonProps } from \"./button\";\nimport { useButtonGroupContext } from \"./button-group\";\n\n/**\n * TEA UI — IconButton.\n *\n * A button whose entire content is an icon, which is the one control shape\n * where the accessible name cannot come from the content. The audit found five\n * such buttons on every table row, every one of them unnamed: a screen reader\n * announced \"button\" five times, and a voice-control user could not operate the\n * table at all, because there was no word to say.\n *\n * The fix is not a lint rule — it is a required prop. `label` has no default\n * and no `undefined` escape, so the compiler refuses the unnamed button. That\n * is worth more than a whole paragraph in a style guide.\n *\n * The icon itself is wrapped in an `aria-hidden` span. `aria-label` on the\n * button is already the name, and leaving the icon in the tree risks a second,\n * conflicting name from an icon that happens to carry a `<title>`.\n */\nexport interface IconButtonProps\n extends Omit<ButtonProps, \"size\" | \"children\" | \"asChild\" | \"as\"> {\n /**\n * The accessible name. **Required** — an icon-only button without one is the\n * defect this component exists to make impossible. Write the action, not the\n * icon: \"Server neu starten\", not \"Pfeil\".\n */\n label: string;\n /** Button size, mapped onto Button's `icon-*` sizes. */\n size?: \"sm\" | \"md\" | \"lg\";\n children?: React.ReactNode;\n}\n\n/** TEA UI's `icon-sm|md|lg` axes are Button's; the mapping is the whole file. */\nconst ICON_SIZE = {\n sm: \"icon-sm\",\n md: \"icon-md\",\n lg: \"icon-lg\",\n} as const;\n\n/**\n * @example\n * ```tsx\n * <IconButton label=\"Server neu starten\" variant=\"ghost\" onClick={restart}>\n * <RefreshCw />\n * </IconButton>\n * ```\n */\nexport const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(function IconButton(\n { label, size = \"md\", children, variant = \"ghost\", ...props },\n ref,\n) {\n const group = useButtonGroupContext();\n\n return (\n <Button\n ref={ref}\n size={ICON_SIZE[size]}\n variant={variant}\n aria-label={label}\n data-tea-grouped={group ? group.orientation : undefined}\n {...dataSlot(\"icon-button\")}\n {...props}\n >\n <span aria-hidden=\"true\" className=\"inline-flex items-center justify-center\">\n {children}\n </span>\n </Button>\n );\n});\n"],"mappings":"AAmEM;AAnEL,YAAY,WAAW;AAExB,SAAS,gBAAgB;AACzB,SAAS,cAAgC;AACzC,SAAS,6BAA6B;AAiCtC,MAAM,YAAY;AAAA,EAChB,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAUO,MAAM,aAAa,MAAM,WAA+C,SAASA,YACtF,EAAE,OAAO,OAAO,MAAM,UAAU,UAAU,SAAS,GAAG,MAAM,GAC5D,KACA;AACA,QAAM,QAAQ,sBAAsB;AAEpC,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAM,UAAU,IAAI;AAAA,MACpB;AAAA,MACA,cAAY;AAAA,MACZ,oBAAkB,QAAQ,MAAM,cAAc;AAAA,MAC7C,GAAG,SAAS,aAAa;AAAA,MACzB,GAAG;AAAA,MAEJ,8BAAC,UAAK,eAAY,QAAO,WAAU,2CAChC,UACH;AAAA;AAAA,EACF;AAEJ,CAAC;","names":["IconButton"]}
@@ -0,0 +1,22 @@
1
+ export { Button, ButtonProps, buttonVariants } from './button.js';
2
+ export { ButtonGroup, ButtonGroupProps, ButtonGroupSeparator, ButtonGroupSeparatorProps } from './button-group.js';
3
+ export { IconButton, IconButtonProps } from './icon-button.js';
4
+ export { Input, InputProps, inputVariants } from './input.js';
5
+ export { Textarea, TextareaProps, textareaVariants } from './textarea.js';
6
+ export { InputGroup, InputGroupEnd, InputGroupEndProps, InputGroupProps, InputGroupStart, InputGroupStartProps, useInputGroupContext } from './input-group.js';
7
+ export { SearchInput, SearchInputProps } from './search-input.js';
8
+ export { PasswordInput, PasswordInputProps } from './password-input.js';
9
+ export { NumberInput, NumberInputProps } from './number-input.js';
10
+ export { Checkbox, CheckboxProps } from './checkbox.js';
11
+ export { Radio, RadioGroup, RadioGroupProps, RadioProps } from './radio.js';
12
+ export { Switch, SwitchProps } from './switch.js';
13
+ export { Slider, SliderProps } from './slider.js';
14
+ export { Toggle, ToggleGroup, ToggleGroupProps, ToggleProps, toggleVariants } from './toggle.js';
15
+ export { Label, LabelProps } from './label.js';
16
+ export { Combobox, ComboboxOption, ComboboxProps, comboboxTriggerVariants } from './combobox.js';
17
+ export { Select, SelectContent, SelectContentProps, SelectGroup, SelectItem, SelectItemProps, SelectProps, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectTriggerProps, SelectValue, selectTriggerVariants } from './select.js';
18
+ export { Field, FieldContextValue, FieldControlProps, FieldDescription, FieldDescriptionProps, FieldError, FieldErrorProps, FieldGroup, FieldGroupProps, FieldLabel, FieldLabelProps, FieldProps, FieldState, useField, useFieldControlProps } from './field.js';
19
+ import 'class-variance-authority/types';
20
+ import 'react';
21
+ import '@tea-ui/utils';
22
+ import 'radix-ui';
@@ -0,0 +1,92 @@
1
+ import { Button, buttonVariants } from "./button.js";
2
+ import { ButtonGroup, ButtonGroupSeparator } from "./button-group.js";
3
+ import { IconButton } from "./icon-button.js";
4
+ import { Input, inputVariants } from "./input.js";
5
+ import { Textarea, textareaVariants } from "./textarea.js";
6
+ import {
7
+ InputGroup,
8
+ InputGroupEnd,
9
+ InputGroupStart,
10
+ useInputGroupContext
11
+ } from "./input-group.js";
12
+ import { SearchInput } from "./search-input.js";
13
+ import { PasswordInput } from "./password-input.js";
14
+ import { NumberInput } from "./number-input.js";
15
+ import { Checkbox } from "./checkbox.js";
16
+ import { Radio, RadioGroup } from "./radio.js";
17
+ import { Switch } from "./switch.js";
18
+ import { Slider } from "./slider.js";
19
+ import { Toggle, ToggleGroup, toggleVariants } from "./toggle.js";
20
+ import { Label } from "./label.js";
21
+ import {
22
+ Combobox,
23
+ comboboxTriggerVariants
24
+ } from "./combobox.js";
25
+ import {
26
+ Select,
27
+ SelectContent,
28
+ SelectGroup,
29
+ SelectItem,
30
+ SelectScrollDownButton,
31
+ SelectScrollUpButton,
32
+ SelectSeparator,
33
+ SelectTrigger,
34
+ SelectValue,
35
+ selectTriggerVariants
36
+ } from "./select.js";
37
+ import {
38
+ Field,
39
+ FieldDescription,
40
+ FieldError,
41
+ FieldGroup,
42
+ FieldLabel,
43
+ useField,
44
+ useFieldControlProps
45
+ } from "./field.js";
46
+ export {
47
+ Button,
48
+ ButtonGroup,
49
+ ButtonGroupSeparator,
50
+ Checkbox,
51
+ Combobox,
52
+ Field,
53
+ FieldDescription,
54
+ FieldError,
55
+ FieldGroup,
56
+ FieldLabel,
57
+ IconButton,
58
+ Input,
59
+ InputGroup,
60
+ InputGroupEnd,
61
+ InputGroupStart,
62
+ Label,
63
+ NumberInput,
64
+ PasswordInput,
65
+ Radio,
66
+ RadioGroup,
67
+ SearchInput,
68
+ Select,
69
+ SelectContent,
70
+ SelectGroup,
71
+ SelectItem,
72
+ SelectScrollDownButton,
73
+ SelectScrollUpButton,
74
+ SelectSeparator,
75
+ SelectTrigger,
76
+ SelectValue,
77
+ Slider,
78
+ Switch,
79
+ Textarea,
80
+ Toggle,
81
+ ToggleGroup,
82
+ buttonVariants,
83
+ comboboxTriggerVariants,
84
+ inputVariants,
85
+ selectTriggerVariants,
86
+ textareaVariants,
87
+ toggleVariants,
88
+ useField,
89
+ useFieldControlProps,
90
+ useInputGroupContext
91
+ };
92
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/inputs/index.ts"],"sourcesContent":["/**\n * TEA UI — inputs.\n *\n * Every control here is keyboard operable, forward-ref, and supports both\n * controlled and uncontrolled use through the shared `useControllableState`.\n * Handlers follow React convention: `onValueChange` for a controlled value,\n * `onChange` for a native one.\n */\nexport { Button, buttonVariants, type ButtonProps } from \"./button\";\nexport { ButtonGroup, ButtonGroupSeparator, type ButtonGroupProps, type ButtonGroupSeparatorProps } from \"./button-group\";\nexport { IconButton, type IconButtonProps } from \"./icon-button\";\nexport { Input, inputVariants, type InputProps } from \"./input\";\nexport { Textarea, textareaVariants, type TextareaProps } from \"./textarea\";\nexport {\n InputGroup,\n InputGroupEnd,\n InputGroupStart,\n useInputGroupContext,\n type InputGroupEndProps,\n type InputGroupProps,\n type InputGroupStartProps,\n} from \"./input-group\";\nexport { SearchInput, type SearchInputProps } from \"./search-input\";\nexport { PasswordInput, type PasswordInputProps } from \"./password-input\";\nexport { NumberInput, type NumberInputProps } from \"./number-input\";\nexport { Checkbox, type CheckboxProps } from \"./checkbox\";\nexport { Radio, RadioGroup, type RadioProps, type RadioGroupProps } from \"./radio\";\nexport { Switch, type SwitchProps } from \"./switch\";\nexport { Slider, type SliderProps } from \"./slider\";\nexport { Toggle, ToggleGroup, toggleVariants, type ToggleProps, type ToggleGroupProps } from \"./toggle\";\nexport { Label, type LabelProps } from \"./label\";\nexport {\n Combobox,\n comboboxTriggerVariants,\n type ComboboxOption,\n type ComboboxProps,\n} from \"./combobox\";\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n selectTriggerVariants,\n type SelectContentProps,\n type SelectItemProps,\n type SelectProps,\n type SelectTriggerProps,\n} from \"./select\";\nexport {\n Field,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n useField,\n useFieldControlProps,\n type FieldContextValue,\n type FieldControlProps,\n type FieldDescriptionProps,\n type FieldErrorProps,\n type FieldGroupProps,\n type FieldLabelProps,\n type FieldProps,\n type FieldState,\n} from \"./field\";\n"],"mappings":"AAQA,SAAS,QAAQ,sBAAwC;AACzD,SAAS,aAAa,4BAAmF;AACzG,SAAS,kBAAwC;AACjD,SAAS,OAAO,qBAAsC;AACtD,SAAS,UAAU,wBAA4C;AAC/D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,mBAA0C;AACnD,SAAS,qBAA8C;AACvD,SAAS,mBAA0C;AACnD,SAAS,gBAAoC;AAC7C,SAAS,OAAO,kBAAyD;AACzE,SAAS,cAAgC;AACzC,SAAS,cAAgC;AACzC,SAAS,QAAQ,aAAa,sBAA+D;AAC7F,SAAS,aAA8B;AACvC;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OASK;","names":[]}
@@ -0,0 +1,77 @@
1
+ import * as React from 'react';
2
+
3
+ /**
4
+ * TEA UI — InputGroup.
5
+ *
6
+ * A control with addons welded to it: a search icon, a unit, a clear button, a
7
+ * currency prefix. The audit found three hand-typed search inputs, each with its
8
+ * own idea of how the icon sat in the box and at what height — and the second
9
+ * one had already drifted from the first.
10
+ *
11
+ * The mechanism is a context, not a `Slot`. `Slot` would let the *group* style a
12
+ * child it knows nothing about, which is the wrong direction: what has to change
13
+ * is the control's own chrome, and only the control knows what it is. So the
14
+ * group publishes the classes an inner control must drop — border, background,
15
+ * horizontal padding and its own focus ring — and `Input` applies them. The
16
+ * group can then wrap `Input`, `Textarea` or a `Select.Trigger`, and the border
17
+ * appears exactly once, on the outside, with the ring on the outside too.
18
+ *
19
+ * Focus is the reason the ring moves out. One ring around the whole group is
20
+ * both prettier and more legible than a ring around a borderless input inside a
21
+ * bordered box: the user can see at a glance that the *group*, not some internal
22
+ * element, has focus.
23
+ */
24
+ interface InputGroupContextValue {
25
+ /** What a control inside the group must drop to become the group's surface. */
26
+ controlClassName: string | null;
27
+ }
28
+ /** Read by `Input` and `Textarea`. `null` when the control is standalone. */
29
+ declare function useInputGroupContext(): InputGroupContextValue | null;
30
+ interface InputGroupProps extends Omit<React.ComponentProps<"div">, "children"> {
31
+ children?: React.ReactNode;
32
+ /**
33
+ * Name for the group. Give it one whenever the group is more than decoration
34
+ * — a `role="group"` with no name appears in the structure list and says
35
+ * nothing, which is worse than not being a group at all.
36
+ */
37
+ label?: string | undefined;
38
+ /** Dim the group. Individual controls keep their own `disabled` state. */
39
+ disabled?: boolean | undefined;
40
+ }
41
+ /**
42
+ * @example
43
+ * ```tsx
44
+ * <InputGroup label="Server durchsuchen">
45
+ * <InputGroupStart>
46
+ * <Search aria-hidden className="size-4" />
47
+ * </InputGroupStart>
48
+ * <Input type="search" />
49
+ * <InputGroupEnd>
50
+ * <IconButton label="Suche zurücksetzen" size="sm">
51
+ * <X aria-hidden />
52
+ * </IconButton>
53
+ * </InputGroupEnd>
54
+ * </InputGroup>
55
+ * ```
56
+ */
57
+ declare const InputGroup: React.ForwardRefExoticComponent<Omit<InputGroupProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
58
+ type InputGroupStartProps = React.ComponentProps<"span">;
59
+ /**
60
+ * An addon at the start edge — a decorative icon, a static prefix, a unit.
61
+ *
62
+ * A `title` attribute is not a label and an `svg` is not one either. If the icon
63
+ * is the only content, either give the *control* a name or mark the icon
64
+ * `aria-hidden` and label the group; never rely on the icon being read.
65
+ */
66
+ declare const InputGroupStart: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
67
+ type InputGroupEndProps = React.ComponentProps<"span">;
68
+ /**
69
+ * An addon at the end edge — a clear button, a suffix, a unit.
70
+ *
71
+ * An interactive child needs an accessible name of its own. The audit's five
72
+ * unnamed icon buttons per table row were all of this shape; `IconButton` makes
73
+ * the name a required prop so that cannot recur.
74
+ */
75
+ declare const InputGroupEnd: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
76
+
77
+ export { InputGroup, InputGroupEnd, type InputGroupEndProps, type InputGroupProps, InputGroupStart, type InputGroupStartProps, useInputGroupContext };
@@ -0,0 +1,76 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "@tea-ui/utils";
4
+ import { dataSlot, stateAttributes } from "../internal/index.js";
5
+ const InputGroupContext = React.createContext(null);
6
+ function useInputGroupContext() {
7
+ return React.useContext(InputGroupContext);
8
+ }
9
+ const CONTROL_CLASS_NAME = cn(
10
+ // The group owns the border and the background.
11
+ "h-full flex-1 rounded-none border-0 bg-transparent",
12
+ // The addons own the horizontal space, so the control needs none. Without
13
+ // this the text collides with the icon at every density.
14
+ "px-0",
15
+ // The group's `focus-within:` ring replaces the control's own ring, so the
16
+ // control gives it up.
17
+ "focus-visible:border-transparent focus-visible:ring-0",
18
+ // Same for the invalid ring: one critical ring, on the group.
19
+ "aria-[invalid=true]:ring-0"
20
+ );
21
+ const InputGroup = React.forwardRef(function InputGroup2({ children, className, label, disabled = false, ...props }, ref) {
22
+ const value = React.useMemo(
23
+ () => ({ controlClassName: CONTROL_CLASS_NAME }),
24
+ []
25
+ );
26
+ return /* @__PURE__ */ jsx(InputGroupContext.Provider, { value, children: /* @__PURE__ */ jsx(
27
+ "div",
28
+ {
29
+ ref,
30
+ role: label ? "group" : void 0,
31
+ "aria-label": label,
32
+ "data-tea-touch": true,
33
+ className: cn(
34
+ "flex min-w-0 items-stretch rounded-none border border-line bg-surface",
35
+ "focus-within:border-ring-strong focus-within:ring-2 focus-within:ring-ring",
36
+ "disabled:opacity-50",
37
+ className
38
+ ),
39
+ ...stateAttributes({ disabled }),
40
+ ...dataSlot("input-group"),
41
+ ...props,
42
+ children
43
+ }
44
+ ) });
45
+ });
46
+ const InputGroupAddon = React.forwardRef(function InputGroupAddon2({ children, className, edge, ...props }, ref) {
47
+ return /* @__PURE__ */ jsx(
48
+ "span",
49
+ {
50
+ ref,
51
+ className: cn(
52
+ "flex shrink-0 items-center gap-1 text-fg-muted",
53
+ edge === "start" ? "ps-3 pe-2" : "ps-2 pe-1",
54
+ className
55
+ ),
56
+ ...dataSlot("input-group-addon"),
57
+ ...props,
58
+ children
59
+ }
60
+ );
61
+ });
62
+ const InputGroupStart = React.forwardRef(
63
+ function InputGroupStart2(props, ref) {
64
+ return /* @__PURE__ */ jsx(InputGroupAddon, { ref, edge: "start", ...dataSlot("input-group-start"), ...props });
65
+ }
66
+ );
67
+ const InputGroupEnd = React.forwardRef(function InputGroupEnd2(props, ref) {
68
+ return /* @__PURE__ */ jsx(InputGroupAddon, { ref, edge: "end", ...dataSlot("input-group-end"), ...props });
69
+ });
70
+ export {
71
+ InputGroup,
72
+ InputGroupEnd,
73
+ InputGroupStart,
74
+ useInputGroupContext
75
+ };
76
+ //# sourceMappingURL=input-group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/inputs/input-group.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@tea-ui/utils\";\n\nimport { dataSlot, stateAttributes } from \"../internal\";\n\n/**\n * TEA UI — InputGroup.\n *\n * A control with addons welded to it: a search icon, a unit, a clear button, a\n * currency prefix. The audit found three hand-typed search inputs, each with its\n * own idea of how the icon sat in the box and at what height — and the second\n * one had already drifted from the first.\n *\n * The mechanism is a context, not a `Slot`. `Slot` would let the *group* style a\n * child it knows nothing about, which is the wrong direction: what has to change\n * is the control's own chrome, and only the control knows what it is. So the\n * group publishes the classes an inner control must drop — border, background,\n * horizontal padding and its own focus ring — and `Input` applies them. The\n * group can then wrap `Input`, `Textarea` or a `Select.Trigger`, and the border\n * appears exactly once, on the outside, with the ring on the outside too.\n *\n * Focus is the reason the ring moves out. One ring around the whole group is\n * both prettier and more legible than a ring around a borderless input inside a\n * bordered box: the user can see at a glance that the *group*, not some internal\n * element, has focus.\n */\n\ninterface InputGroupContextValue {\n /** What a control inside the group must drop to become the group's surface. */\n controlClassName: string | null;\n}\n\nconst InputGroupContext = React.createContext<InputGroupContextValue | null>(null);\n\n/** Read by `Input` and `Textarea`. `null` when the control is standalone. */\nexport function useInputGroupContext(): InputGroupContextValue | null {\n return React.useContext(InputGroupContext);\n}\n\nconst CONTROL_CLASS_NAME = cn(\n // The group owns the border and the background.\n \"h-full flex-1 rounded-none border-0 bg-transparent\",\n // The addons own the horizontal space, so the control needs none. Without\n // this the text collides with the icon at every density.\n \"px-0\",\n // The group's `focus-within:` ring replaces the control's own ring, so the\n // control gives it up.\n \"focus-visible:border-transparent focus-visible:ring-0\",\n // Same for the invalid ring: one critical ring, on the group.\n \"aria-[invalid=true]:ring-0\",\n);\n\nexport interface InputGroupProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n children?: React.ReactNode;\n /**\n * Name for the group. Give it one whenever the group is more than decoration\n * — a `role=\"group\"` with no name appears in the structure list and says\n * nothing, which is worse than not being a group at all.\n */\n label?: string | undefined;\n /** Dim the group. Individual controls keep their own `disabled` state. */\n disabled?: boolean | undefined;\n}\n\n/**\n * @example\n * ```tsx\n * <InputGroup label=\"Server durchsuchen\">\n * <InputGroupStart>\n * <Search aria-hidden className=\"size-4\" />\n * </InputGroupStart>\n * <Input type=\"search\" />\n * <InputGroupEnd>\n * <IconButton label=\"Suche zurücksetzen\" size=\"sm\">\n * <X aria-hidden />\n * </IconButton>\n * </InputGroupEnd>\n * </InputGroup>\n * ```\n */\nexport const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(function InputGroup(\n { children, className, label, disabled = false, ...props },\n ref,\n) {\n const value = React.useMemo<InputGroupContextValue>(\n () => ({ controlClassName: CONTROL_CLASS_NAME }),\n [],\n );\n\n return (\n <InputGroupContext.Provider value={value}>\n <div\n ref={ref}\n role={label ? \"group\" : undefined}\n aria-label={label}\n data-tea-touch\n className={cn(\n \"flex min-w-0 items-stretch rounded-none border border-line bg-surface\",\n \"focus-within:border-ring-strong focus-within:ring-2 focus-within:ring-ring\",\n \"disabled:opacity-50\",\n className,\n )}\n {...stateAttributes({ disabled })}\n {...dataSlot(\"input-group\")}\n {...props}\n >\n {children}\n </div>\n </InputGroupContext.Provider>\n );\n});\n\ninterface InputGroupAddonProps extends React.ComponentProps<\"span\"> {\n /** Which edge of the group the addon sits on. Sets the horizontal padding. */\n edge: \"start\" | \"end\";\n}\n\n/**\n * The shared body of both addons.\n *\n * The addon is a `<span>`, never a wrapper element of its own: it must not\n * introduce a block box between the group's flex line and the control, or the\n * control stops filling the height.\n */\nconst InputGroupAddon = React.forwardRef<HTMLSpanElement, InputGroupAddonProps>(function InputGroupAddon(\n { children, className, edge, ...props },\n ref,\n) {\n return (\n <span\n ref={ref}\n className={cn(\n \"flex shrink-0 items-center gap-1 text-fg-muted\",\n edge === \"start\" ? \"ps-3 pe-2\" : \"ps-2 pe-1\",\n className,\n )}\n {...dataSlot(\"input-group-addon\")}\n {...props}\n >\n {children}\n </span>\n );\n});\n\nexport type InputGroupStartProps = React.ComponentProps<\"span\">;\n\n/**\n * An addon at the start edge — a decorative icon, a static prefix, a unit.\n *\n * A `title` attribute is not a label and an `svg` is not one either. If the icon\n * is the only content, either give the *control* a name or mark the icon\n * `aria-hidden` and label the group; never rely on the icon being read.\n */\nexport const InputGroupStart = React.forwardRef<HTMLSpanElement, InputGroupStartProps>(\n function InputGroupStart(props, ref) {\n return <InputGroupAddon ref={ref} edge=\"start\" {...dataSlot(\"input-group-start\")} {...props} />;\n },\n);\n\nexport type InputGroupEndProps = React.ComponentProps<\"span\">;\n\n/**\n * An addon at the end edge — a clear button, a suffix, a unit.\n *\n * An interactive child needs an accessible name of its own. The audit's five\n * unnamed icon buttons per table row were all of this shape; `IconButton` makes\n * the name a required prop so that cannot recur.\n */\nexport const InputGroupEnd = React.forwardRef<HTMLSpanElement, InputGroupEndProps>(function InputGroupEnd(\n props,\n ref,\n) {\n return <InputGroupAddon ref={ref} edge=\"end\" {...dataSlot(\"input-group-end\")} {...props} />;\n});\n"],"mappings":"AA2FM;AA3FL,YAAY,WAAW;AACxB,SAAS,UAAU;AAEnB,SAAS,UAAU,uBAAuB;AA6B1C,MAAM,oBAAoB,MAAM,cAA6C,IAAI;AAG1E,SAAS,uBAAsD;AACpE,SAAO,MAAM,WAAW,iBAAiB;AAC3C;AAEA,MAAM,qBAAqB;AAAA;AAAA,EAEzB;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EAEA;AACF;AA8BO,MAAM,aAAa,MAAM,WAA4C,SAASA,YACnF,EAAE,UAAU,WAAW,OAAO,WAAW,OAAO,GAAG,MAAM,GACzD,KACA;AACA,QAAM,QAAQ,MAAM;AAAA,IAClB,OAAO,EAAE,kBAAkB,mBAAmB;AAAA,IAC9C,CAAC;AAAA,EACH;AAEA,SACE,oBAAC,kBAAkB,UAAlB,EAA2B,OAC1B;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAM,QAAQ,UAAU;AAAA,MACxB,cAAY;AAAA,MACZ,kBAAc;AAAA,MACd,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG,gBAAgB,EAAE,SAAS,CAAC;AAAA,MAC/B,GAAG,SAAS,aAAa;AAAA,MACzB,GAAG;AAAA,MAEH;AAAA;AAAA,EACH,GACF;AAEJ,CAAC;AAcD,MAAM,kBAAkB,MAAM,WAAkD,SAASC,iBACvF,EAAE,UAAU,WAAW,MAAM,GAAG,MAAM,GACtC,KACA;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA,SAAS,UAAU,cAAc;AAAA,QACjC;AAAA,MACF;AAAA,MACC,GAAG,SAAS,mBAAmB;AAAA,MAC/B,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ,CAAC;AAWM,MAAM,kBAAkB,MAAM;AAAA,EACnC,SAASC,iBAAgB,OAAO,KAAK;AACnC,WAAO,oBAAC,mBAAgB,KAAU,MAAK,SAAS,GAAG,SAAS,mBAAmB,GAAI,GAAG,OAAO;AAAA,EAC/F;AACF;AAWO,MAAM,gBAAgB,MAAM,WAAgD,SAASC,eAC1F,OACA,KACA;AACA,SAAO,oBAAC,mBAAgB,KAAU,MAAK,OAAO,GAAG,SAAS,iBAAiB,GAAI,GAAG,OAAO;AAC3F,CAAC;","names":["InputGroup","InputGroupAddon","InputGroupStart","InputGroupEnd"]}