@carbonbits/sixr 1.3.0 → 1.4.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 (42) hide show
  1. package/README.md +77 -18
  2. package/dist/chunk-NZHHNVXZ.js +118 -0
  3. package/dist/chunk-NZHHNVXZ.js.map +1 -0
  4. package/dist/email-input.cjs +390 -0
  5. package/dist/email-input.cjs.map +1 -0
  6. package/dist/email-input.d.cts +14 -0
  7. package/dist/email-input.d.ts +14 -0
  8. package/dist/email-input.js +367 -0
  9. package/dist/email-input.js.map +1 -0
  10. package/dist/index-DwjxC4l4.d.cts +62 -0
  11. package/dist/index-DwjxC4l4.d.ts +62 -0
  12. package/dist/index.cjs +112 -0
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +48 -9
  15. package/dist/index.d.ts +48 -9
  16. package/dist/index.js +1 -0
  17. package/dist/password-input.cjs +406 -0
  18. package/dist/password-input.cjs.map +1 -0
  19. package/dist/password-input.d.cts +20 -0
  20. package/dist/password-input.d.ts +20 -0
  21. package/dist/password-input.js +385 -0
  22. package/dist/password-input.js.map +1 -0
  23. package/dist/phone-input.cjs +386 -0
  24. package/dist/phone-input.cjs.map +1 -0
  25. package/dist/phone-input.d.cts +12 -0
  26. package/dist/phone-input.d.ts +12 -0
  27. package/dist/phone-input.js +363 -0
  28. package/dist/phone-input.js.map +1 -0
  29. package/dist/styles.css +1 -1
  30. package/dist/text-input.cjs +228 -59
  31. package/dist/text-input.cjs.map +1 -1
  32. package/dist/text-input.d.cts +10 -58
  33. package/dist/text-input.d.ts +10 -58
  34. package/dist/text-input.js +226 -60
  35. package/dist/text-input.js.map +1 -1
  36. package/dist/typography.cjs +120 -0
  37. package/dist/typography.cjs.map +1 -0
  38. package/dist/typography.d.cts +46 -0
  39. package/dist/typography.d.ts +46 -0
  40. package/dist/typography.js +3 -0
  41. package/dist/typography.js.map +1 -0
  42. package/package.json +45 -1
@@ -1,16 +1,15 @@
1
1
  "use client";
2
2
  "use client";
3
3
 
4
- // src/components/text-input/index.tsx
4
+ // src/components/field/index.tsx
5
+ import { Label } from "@radix-ui/react-label";
6
+ import { useId } from "react";
7
+
8
+ // src/components/field/use-field-behaviour.ts
5
9
  import {
6
10
  useCallback,
7
- useId,
8
11
  useState
9
12
  } from "react";
10
- import { jsx, jsxs } from "react/jsx-runtime";
11
- var FIELD_CLASSES = "h-10 w-full rounded-field border bg-surface px-3 font-[inherit] text-field text-fg outline-none placeholder:text-fg-subtle disabled:cursor-not-allowed disabled:opacity-45";
12
- var VALID_CLASSES = "border-line focus:border-accent focus:ring-3 focus:ring-accent-soft";
13
- var INVALID_CLASSES = "border-danger focus:border-danger focus:ring-3 focus:ring-danger-soft";
14
13
  var FALLBACK_MESSAGE = "Invalid value";
15
14
  function runSchema(schema, value) {
16
15
  if (!schema) {
@@ -25,50 +24,6 @@ function runSchema(schema, value) {
25
24
  error: result.error.issues[0]?.message ?? FALLBACK_MESSAGE
26
25
  };
27
26
  }
28
- function fieldClassName(invalid, mono, hasTrailing, className) {
29
- return [
30
- FIELD_CLASSES,
31
- invalid ? INVALID_CLASSES : VALID_CLASSES,
32
- mono ? "font-code" : "",
33
- hasTrailing ? "pr-16" : "",
34
- className ?? ""
35
- ].filter(Boolean).join(" ");
36
- }
37
- function FieldLabel({
38
- htmlFor,
39
- label,
40
- optional,
41
- action
42
- }) {
43
- if (!label) {
44
- return null;
45
- }
46
- return /* @__PURE__ */ jsxs(
47
- "label",
48
- {
49
- htmlFor,
50
- className: "mb-1.5 flex items-center text-label font-semibold text-fg-muted",
51
- children: [
52
- label,
53
- optional ? /* @__PURE__ */ jsx("span", { className: "ml-auto text-hint font-semibold text-fg-subtle", children: "Optional" }) : null,
54
- action ? /* @__PURE__ */ jsx("span", { className: "ml-auto", children: action }) : null
55
- ]
56
- }
57
- );
58
- }
59
- function FieldMessage({
60
- id,
61
- error,
62
- hint
63
- }) {
64
- if (error) {
65
- return /* @__PURE__ */ jsx("p", { id, role: "alert", className: "mt-1.5 text-hint text-danger", children: error });
66
- }
67
- if (hint) {
68
- return /* @__PURE__ */ jsx("p", { id, className: "mt-1.5 text-hint text-fg-subtle", children: hint });
69
- }
70
- return null;
71
- }
72
27
  function useFieldBehaviour({
73
28
  value,
74
29
  defaultValue,
@@ -122,15 +77,210 @@ function useFieldBehaviour({
122
77
  handleBlur
123
78
  };
124
79
  }
125
- function TextInput({
80
+
81
+ // src/components/typography/index.tsx
82
+ import { Slot } from "@radix-ui/react-slot";
83
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
84
+ var DEFAULT_ELEMENT = {
85
+ display: "h1",
86
+ heading: "h2",
87
+ subheading: "h3",
88
+ body: "p",
89
+ label: "span",
90
+ hint: "p",
91
+ code: "code"
92
+ };
93
+ var DEFAULT_WEIGHT = {
94
+ display: "semibold",
95
+ heading: "semibold",
96
+ subheading: "medium",
97
+ body: "regular",
98
+ label: "semibold",
99
+ hint: "regular",
100
+ code: "regular"
101
+ };
102
+ var VARIANT_CLASSES = {
103
+ display: "text-display leading-tight tracking-tight",
104
+ heading: "text-heading leading-tight tracking-tight",
105
+ subheading: "text-subheading leading-snug",
106
+ body: "text-field leading-normal",
107
+ label: "text-label leading-normal",
108
+ hint: "text-hint leading-normal",
109
+ code: "font-code text-field leading-normal"
110
+ };
111
+ var TONE_CLASSES = {
112
+ default: "text-fg",
113
+ muted: "text-fg-muted",
114
+ subtle: "text-fg-subtle",
115
+ accent: "text-accent",
116
+ "on-accent": "text-on-accent",
117
+ success: "text-success",
118
+ warning: "text-warning",
119
+ info: "text-info",
120
+ danger: "text-danger"
121
+ };
122
+ var WEIGHT_CLASSES = {
123
+ regular: "font-normal",
124
+ medium: "font-medium",
125
+ semibold: "font-semibold",
126
+ bold: "font-bold"
127
+ };
128
+ var ALIGN_CLASSES = {
129
+ start: "text-start",
130
+ center: "text-center",
131
+ end: "text-end"
132
+ };
133
+ var ICON_CLASSES = "inline-flex shrink-0 items-center [&>svg]:h-[1em] [&>svg]:w-[1em]";
134
+ function typographyClassName({
135
+ variant,
136
+ tone,
137
+ weight,
138
+ align,
139
+ truncate,
140
+ hasIcon,
141
+ className
142
+ }) {
143
+ return [
144
+ VARIANT_CLASSES[variant],
145
+ TONE_CLASSES[tone],
146
+ WEIGHT_CLASSES[weight],
147
+ align ? ALIGN_CLASSES[align] : "",
148
+ hasIcon ? "inline-flex items-center gap-1.5" : "",
149
+ // With an icon the root is a flex container, and text-overflow only
150
+ // clips block text, so the text span carries the truncation instead.
151
+ truncate && !hasIcon ? "truncate" : "",
152
+ className ?? ""
153
+ ].filter(Boolean).join(" ");
154
+ }
155
+ function Typography({
156
+ variant = "body",
157
+ tone = "default",
158
+ weight,
159
+ align,
160
+ truncate = false,
161
+ icon,
162
+ iconPosition = "leading",
163
+ as,
164
+ asChild = false,
165
+ className,
166
+ children,
167
+ ...rest
168
+ }) {
169
+ const hasIcon = icon !== void 0 && icon !== null;
170
+ const Component = asChild ? Slot : as ?? DEFAULT_ELEMENT[variant];
171
+ const iconNode = hasIcon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: ICON_CLASSES, children: icon }) : null;
172
+ const content = hasIcon ? /* @__PURE__ */ jsxs(Fragment, { children: [
173
+ iconPosition === "leading" ? iconNode : null,
174
+ /* @__PURE__ */ jsx("span", { className: truncate ? "min-w-0 truncate" : void 0, children }),
175
+ iconPosition === "trailing" ? iconNode : null
176
+ ] }) : children;
177
+ return /* @__PURE__ */ jsx(
178
+ Component,
179
+ {
180
+ ...rest,
181
+ className: typographyClassName({
182
+ variant,
183
+ tone,
184
+ weight: weight ?? DEFAULT_WEIGHT[variant],
185
+ align,
186
+ truncate,
187
+ hasIcon,
188
+ className
189
+ }),
190
+ children: content
191
+ }
192
+ );
193
+ }
194
+
195
+ // src/components/field/index.tsx
196
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
197
+ var FIELD_CLASSES = "h-10 w-full rounded-field border bg-surface px-3 font-[inherit] text-field text-fg outline-none placeholder:text-fg-subtle disabled:cursor-not-allowed disabled:opacity-45";
198
+ var VALID_CLASSES = "border-line focus:border-accent focus:ring-3 focus:ring-accent-soft";
199
+ var INVALID_CLASSES = "border-danger focus:border-danger focus:ring-3 focus:ring-danger-soft";
200
+ function fieldClassName(invalid, hasTrailing, className) {
201
+ return [
202
+ FIELD_CLASSES,
203
+ invalid ? INVALID_CLASSES : VALID_CLASSES,
204
+ hasTrailing ? "pr-16" : "",
205
+ className ?? ""
206
+ ].filter(Boolean).join(" ");
207
+ }
208
+ function FieldLabel({
209
+ htmlFor,
210
+ label,
211
+ optional,
212
+ action
213
+ }) {
214
+ if (!label) {
215
+ return null;
216
+ }
217
+ return /* @__PURE__ */ jsx2(
218
+ Typography,
219
+ {
220
+ variant: "label",
221
+ tone: "muted",
222
+ className: "mb-1.5 flex items-center",
223
+ asChild: true,
224
+ children: /* @__PURE__ */ jsxs2(Label, { htmlFor, children: [
225
+ label,
226
+ optional ? /* @__PURE__ */ jsx2(
227
+ Typography,
228
+ {
229
+ as: "span",
230
+ variant: "hint",
231
+ tone: "subtle",
232
+ weight: "semibold",
233
+ className: "ml-auto",
234
+ children: "Optional"
235
+ }
236
+ ) : null,
237
+ action ? /* @__PURE__ */ jsx2("span", { className: "ml-auto", children: action }) : null
238
+ ] })
239
+ }
240
+ );
241
+ }
242
+ function FieldMessage({
243
+ id,
244
+ error,
245
+ hint
246
+ }) {
247
+ if (error) {
248
+ return /* @__PURE__ */ jsx2(
249
+ Typography,
250
+ {
251
+ as: "p",
252
+ variant: "hint",
253
+ tone: "danger",
254
+ id,
255
+ role: "alert",
256
+ className: "mt-1.5",
257
+ children: error
258
+ }
259
+ );
260
+ }
261
+ if (hint) {
262
+ return /* @__PURE__ */ jsx2(
263
+ Typography,
264
+ {
265
+ as: "p",
266
+ variant: "hint",
267
+ tone: "subtle",
268
+ id,
269
+ className: "mt-1.5",
270
+ children: hint
271
+ }
272
+ );
273
+ }
274
+ return null;
275
+ }
276
+ function Field({
126
277
  label,
127
278
  hint,
128
279
  error,
129
280
  optional = false,
130
281
  action,
131
282
  trailing,
132
- mono = false,
133
- type = "text",
283
+ type,
134
284
  value,
135
285
  defaultValue,
136
286
  onChange,
@@ -155,8 +305,8 @@ function TextInput({
155
305
  validateOn,
156
306
  onValidationChange
157
307
  });
158
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
159
- /* @__PURE__ */ jsx(
308
+ return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col", children: [
309
+ /* @__PURE__ */ jsx2(
160
310
  FieldLabel,
161
311
  {
162
312
  htmlFor: inputId,
@@ -165,8 +315,8 @@ function TextInput({
165
315
  action
166
316
  }
167
317
  ),
168
- /* @__PURE__ */ jsxs("div", { className: trailing ? "relative" : void 0, children: [
169
- /* @__PURE__ */ jsx(
318
+ /* @__PURE__ */ jsxs2("div", { className: trailing ? "relative" : void 0, children: [
319
+ /* @__PURE__ */ jsx2(
170
320
  "input",
171
321
  {
172
322
  ...rest,
@@ -179,17 +329,33 @@ function TextInput({
179
329
  onBlur: handleBlur,
180
330
  className: fieldClassName(
181
331
  Boolean(shownError),
182
- mono,
183
332
  Boolean(trailing),
184
333
  className
185
334
  )
186
335
  }
187
336
  ),
188
- trailing ? /* @__PURE__ */ jsx("span", { className: "absolute top-1/2 right-[9px] -translate-y-1/2 text-hint font-semibold text-fg-subtle", children: trailing }) : null
337
+ trailing ? /* @__PURE__ */ jsx2("span", { className: "absolute top-1/2 right-[9px] flex -translate-y-1/2 items-center text-hint font-semibold text-fg-subtle", children: trailing }) : null
189
338
  ] }),
190
- /* @__PURE__ */ jsx(FieldMessage, { id: describedById, error: shownError, hint })
339
+ /* @__PURE__ */ jsx2(FieldMessage, { id: describedById, error: shownError, hint })
191
340
  ] });
192
341
  }
342
+
343
+ // src/components/text-input/index.tsx
344
+ import { jsx as jsx3 } from "react/jsx-runtime";
345
+ function TextInput({
346
+ mono = false,
347
+ className,
348
+ ...rest
349
+ }) {
350
+ return /* @__PURE__ */ jsx3(
351
+ Field,
352
+ {
353
+ ...rest,
354
+ type: "text",
355
+ className: [mono ? "font-code" : "", className ?? ""].filter(Boolean).join(" ")
356
+ }
357
+ );
358
+ }
193
359
  export {
194
360
  TextInput
195
361
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/text-input/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type ChangeEvent,\n type FocusEvent,\n type InputHTMLAttributes,\n type ReactNode,\n useCallback,\n useId,\n useState,\n} from \"react\";\n\n/**\n * Structural shape of a zod schema — every zod schema satisfies it, and any\n * version of zod does. Declaring it this way rather than importing `ZodType`\n * keeps zod out of this module's public types, so consumers who only want an\n * input do not have to install zod to typecheck it.\n */\nexport type TextInputSchema = {\n safeParse: (\n value: unknown,\n ) =>\n | { success: true }\n | { success: false; error: { issues: readonly { message: string }[] } };\n};\n\n/** Outcome of a single validation pass, handed to `onValidationChange`. */\nexport type TextInputValidation = {\n valid: boolean;\n /** First issue reported by the schema, or `undefined` when valid. */\n error?: string;\n};\n\nexport type TextInputProps = Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"onChange\" | \"value\" | \"defaultValue\" | \"type\"\n> & {\n label?: string;\n /** Guidance under the field. Replaced by the error message while invalid. */\n hint?: ReactNode;\n /**\n * Error supplied by the caller — a server-side or cross-field failure. Takes\n * precedence over the schema, so a form can hold a field invalid regardless\n * of what `schema` makes of the value on its own.\n */\n error?: string;\n /** Renders the design's muted \"Optional\" marker at the end of the label. */\n optional?: boolean;\n /** Trailing label slot, for affordances like the design's \"Forgot?\" link. */\n action?: ReactNode;\n /** Trailing in-field slot, for affordances like the design's password peek. */\n trailing?: ReactNode;\n /** Renders the value in the design's monospace face. */\n mono?: boolean;\n type?: \"text\" | \"email\" | \"password\" | \"tel\" | \"url\" | \"search\";\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n /**\n * Any zod schema that accepts a string. Only `safeParse` is used, so a\n * refined, transformed or preprocessed schema works as well as a plain one.\n */\n schema?: TextInputSchema;\n /**\n * When the schema runs. `blur` — the default — avoids scolding someone\n * mid-keystroke; once a field has gone invalid it revalidates on every\n * keystroke so the message clears as soon as it is fixed.\n */\n validateOn?: \"blur\" | \"change\" | \"never\";\n onValidationChange?: (state: TextInputValidation) => void;\n};\n\nconst FIELD_CLASSES =\n \"h-10 w-full rounded-field border bg-surface px-3 font-[inherit] text-field text-fg outline-none placeholder:text-fg-subtle disabled:cursor-not-allowed disabled:opacity-45\";\nconst VALID_CLASSES =\n \"border-line focus:border-accent focus:ring-3 focus:ring-accent-soft\";\nconst INVALID_CLASSES =\n \"border-danger focus:border-danger focus:ring-3 focus:ring-danger-soft\";\nconst FALLBACK_MESSAGE = \"Invalid value\";\n\nfunction runSchema(\n schema: TextInputSchema | undefined,\n value: string,\n): TextInputValidation {\n if (!schema) {\n return { valid: true };\n }\n\n const result = schema.safeParse(value);\n if (result.success) {\n return { valid: true };\n }\n\n return {\n valid: false,\n error: result.error.issues[0]?.message ?? FALLBACK_MESSAGE,\n };\n}\n\nfunction fieldClassName(\n invalid: boolean,\n mono: boolean,\n hasTrailing: boolean,\n className: string | undefined,\n): string {\n return [\n FIELD_CLASSES,\n invalid ? INVALID_CLASSES : VALID_CLASSES,\n mono ? \"font-code\" : \"\",\n hasTrailing ? \"pr-16\" : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n}\n\nfunction FieldLabel({\n htmlFor,\n label,\n optional,\n action,\n}: {\n htmlFor: string;\n label: string | undefined;\n optional: boolean;\n action: ReactNode;\n}) {\n if (!label) {\n return null;\n }\n\n return (\n <label\n htmlFor={htmlFor}\n className=\"mb-1.5 flex items-center text-label font-semibold text-fg-muted\"\n >\n {label}\n {optional ? (\n <span className=\"ml-auto text-hint font-semibold text-fg-subtle\">\n Optional\n </span>\n ) : null}\n {action ? <span className=\"ml-auto\">{action}</span> : null}\n </label>\n );\n}\n\nfunction FieldMessage({\n id,\n error,\n hint,\n}: {\n id: string;\n error: string | undefined;\n hint: ReactNode;\n}) {\n if (error) {\n return (\n <p id={id} role=\"alert\" className=\"mt-1.5 text-hint text-danger\">\n {error}\n </p>\n );\n }\n\n if (hint) {\n return (\n <p id={id} className=\"mt-1.5 text-hint text-fg-subtle\">\n {hint}\n </p>\n );\n }\n\n return null;\n}\n\ntype FieldBehaviour = Pick<\n TextInputProps,\n | \"value\"\n | \"defaultValue\"\n | \"error\"\n | \"onChange\"\n | \"onBlur\"\n | \"schema\"\n | \"validateOn\"\n | \"onValidationChange\"\n>;\n\n/**\n * Owns the value (controlled or not), the schema verdict, and when to re-run\n * it. Split out so the component itself stays markup.\n */\nfunction useFieldBehaviour({\n value,\n defaultValue,\n error,\n onChange,\n onBlur,\n schema,\n validateOn = \"blur\",\n onValidationChange,\n}: FieldBehaviour) {\n const [uncontrolledValue, setUncontrolledValue] = useState(\n defaultValue ?? \"\",\n );\n const [schemaError, setSchemaError] = useState<string | undefined>(undefined);\n\n const isControlled = value !== undefined;\n\n const validate = useCallback(\n (next: string) => {\n const state = runSchema(schema, next);\n setSchemaError(state.error);\n onValidationChange?.(state);\n },\n [schema, onValidationChange],\n );\n\n const handleChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => {\n const next = event.target.value;\n if (!isControlled) {\n setUncontrolledValue(next);\n }\n onChange?.(next);\n\n // Revalidate on every keystroke once the field is already invalid, so a\n // corrected value clears the message without waiting for another blur.\n if (validateOn === \"change\" || (validateOn === \"blur\" && schemaError)) {\n validate(next);\n }\n },\n [isControlled, onChange, validateOn, schemaError, validate],\n );\n\n const handleBlur = useCallback(\n (event: FocusEvent<HTMLInputElement>) => {\n if (validateOn === \"blur\") {\n validate(event.target.value);\n }\n onBlur?.(event);\n },\n [validateOn, validate, onBlur],\n );\n\n return {\n currentValue: isControlled ? value : uncontrolledValue,\n // A caller-supplied error outranks the schema's verdict.\n shownError: error ?? schemaError,\n handleChange,\n handleBlur,\n };\n}\n\nexport function TextInput({\n label,\n hint,\n error,\n optional = false,\n action,\n trailing,\n mono = false,\n type = \"text\",\n value,\n defaultValue,\n onChange,\n schema,\n validateOn = \"blur\",\n onValidationChange,\n id,\n className,\n onBlur,\n ...rest\n}: TextInputProps) {\n const generatedId = useId();\n const inputId = id ?? generatedId;\n const describedById = `${inputId}-description`;\n\n const { currentValue, shownError, handleChange, handleBlur } =\n useFieldBehaviour({\n value,\n defaultValue,\n error,\n onChange,\n onBlur,\n schema,\n validateOn,\n onValidationChange,\n });\n\n return (\n <div className=\"flex flex-col\">\n <FieldLabel\n htmlFor={inputId}\n label={label}\n optional={optional}\n action={action}\n />\n\n <div className={trailing ? \"relative\" : undefined}>\n <input\n {...rest}\n id={inputId}\n type={type}\n value={currentValue}\n aria-invalid={shownError ? true : undefined}\n aria-describedby={shownError || hint ? describedById : undefined}\n onChange={handleChange}\n onBlur={handleBlur}\n className={fieldClassName(\n Boolean(shownError),\n mono,\n Boolean(trailing),\n className,\n )}\n />\n {trailing ? (\n <span className=\"absolute top-1/2 right-[9px] -translate-y-1/2 text-hint font-semibold text-fg-subtle\">\n {trailing}\n </span>\n ) : null}\n </div>\n\n <FieldMessage id={describedById} error={shownError} hint={hint} />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/components/field/index.tsx","../src/components/field/use-field-behaviour.ts","../src/components/typography/index.tsx","../src/components/text-input/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { Label } from \"@radix-ui/react-label\";\nimport { type InputHTMLAttributes, type ReactNode, useId } from \"react\";\nimport {\n type FieldSchema,\n type FieldValidation,\n useFieldBehaviour,\n} from \"@/components/field/use-field-behaviour\";\nimport { Typography } from \"@/components/typography\";\n\nexport type {\n FieldSchema,\n FieldValidation,\n} from \"@/components/field/use-field-behaviour\";\n\n/**\n * The single-line input behind `TextInput`, `EmailInput`, `PasswordInput` and\n * `PhoneInput`. Each of those fixes the `type` (and the keyboard, autocomplete\n * and validation defaults that go with it) and hands everything else here.\n *\n * Internal: this module has no subpath of its own. Consumers reach it through\n * the specialised inputs, and its shared types through the barrel.\n */\n\nexport type FieldType = \"text\" | \"email\" | \"password\" | \"tel\";\n\n/** Props every specialised input shares. `type` is fixed by the wrapper. */\nexport type FieldProps = Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"onChange\" | \"value\" | \"defaultValue\" | \"type\"\n> & {\n label?: string;\n /** Guidance under the field. Replaced by the error message while invalid. */\n hint?: ReactNode;\n /**\n * Error supplied by the caller — a server-side or cross-field failure. Takes\n * precedence over the schema, so a form can hold a field invalid regardless\n * of what `schema` makes of the value on its own.\n */\n error?: string;\n /** Renders the design's muted \"Optional\" marker at the end of the label. */\n optional?: boolean;\n /** Trailing label slot, for affordances like the design's \"Forgot?\" link. */\n action?: ReactNode;\n /** Trailing in-field slot, for affordances like the design's password peek. */\n trailing?: ReactNode;\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n /**\n * Any zod schema that accepts a string. Only `safeParse` is used, so a\n * refined, transformed or preprocessed schema works as well as a plain one.\n */\n schema?: FieldSchema;\n /**\n * When the schema runs. `blur` — the default — avoids scolding someone\n * mid-keystroke; once a field has gone invalid it revalidates on every\n * keystroke so the message clears as soon as it is fixed.\n */\n validateOn?: \"blur\" | \"change\" | \"never\";\n onValidationChange?: (state: FieldValidation) => void;\n};\n\nconst FIELD_CLASSES =\n \"h-10 w-full rounded-field border bg-surface px-3 font-[inherit] text-field text-fg outline-none placeholder:text-fg-subtle disabled:cursor-not-allowed disabled:opacity-45\";\nconst VALID_CLASSES =\n \"border-line focus:border-accent focus:ring-3 focus:ring-accent-soft\";\nconst INVALID_CLASSES =\n \"border-danger focus:border-danger focus:ring-3 focus:ring-danger-soft\";\nfunction fieldClassName(\n invalid: boolean,\n hasTrailing: boolean,\n className: string | undefined,\n): string {\n return [\n FIELD_CLASSES,\n invalid ? INVALID_CLASSES : VALID_CLASSES,\n hasTrailing ? \"pr-16\" : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n}\n\nfunction FieldLabel({\n htmlFor,\n label,\n optional,\n action,\n}: {\n htmlFor: string;\n label: string | undefined;\n optional: boolean;\n action: ReactNode;\n}) {\n if (!label) {\n return null;\n }\n\n return (\n <Typography\n variant=\"label\"\n tone=\"muted\"\n className=\"mb-1.5 flex items-center\"\n asChild\n >\n <Label htmlFor={htmlFor}>\n {label}\n {optional ? (\n <Typography\n as=\"span\"\n variant=\"hint\"\n tone=\"subtle\"\n weight=\"semibold\"\n className=\"ml-auto\"\n >\n Optional\n </Typography>\n ) : null}\n {action ? <span className=\"ml-auto\">{action}</span> : null}\n </Label>\n </Typography>\n );\n}\n\nfunction FieldMessage({\n id,\n error,\n hint,\n}: {\n id: string;\n error: string | undefined;\n hint: ReactNode;\n}) {\n if (error) {\n return (\n <Typography\n as=\"p\"\n variant=\"hint\"\n tone=\"danger\"\n id={id}\n role=\"alert\"\n className=\"mt-1.5\"\n >\n {error}\n </Typography>\n );\n }\n\n if (hint) {\n return (\n <Typography\n as=\"p\"\n variant=\"hint\"\n tone=\"subtle\"\n id={id}\n className=\"mt-1.5\"\n >\n {hint}\n </Typography>\n );\n }\n\n return null;\n}\n\nexport type FieldComponentProps = FieldProps & { type: FieldType };\n\nexport function Field({\n label,\n hint,\n error,\n optional = false,\n action,\n trailing,\n type,\n value,\n defaultValue,\n onChange,\n schema,\n validateOn = \"blur\",\n onValidationChange,\n id,\n className,\n onBlur,\n ...rest\n}: FieldComponentProps) {\n const generatedId = useId();\n const inputId = id ?? generatedId;\n const describedById = `${inputId}-description`;\n\n const { currentValue, shownError, handleChange, handleBlur } =\n useFieldBehaviour({\n value,\n defaultValue,\n error,\n onChange,\n onBlur,\n schema,\n validateOn,\n onValidationChange,\n });\n\n return (\n <div className=\"flex flex-col\">\n <FieldLabel\n htmlFor={inputId}\n label={label}\n optional={optional}\n action={action}\n />\n\n <div className={trailing ? \"relative\" : undefined}>\n <input\n {...rest}\n id={inputId}\n type={type}\n value={currentValue}\n aria-invalid={shownError ? true : undefined}\n aria-describedby={shownError || hint ? describedById : undefined}\n onChange={handleChange}\n onBlur={handleBlur}\n className={fieldClassName(\n Boolean(shownError),\n Boolean(trailing),\n className,\n )}\n />\n {trailing ? (\n <span className=\"absolute top-1/2 right-[9px] flex -translate-y-1/2 items-center text-hint font-semibold text-fg-subtle\">\n {trailing}\n </span>\n ) : null}\n </div>\n\n <FieldMessage id={describedById} error={shownError} hint={hint} />\n </div>\n );\n}\n","import {\n type ChangeEvent,\n type FocusEvent,\n useCallback,\n useState,\n} from \"react\";\n\n/**\n * Structural shape of a zod schema — every zod schema satisfies it, and any\n * version of zod does. Declaring it this way rather than importing `ZodType`\n * keeps zod out of this module's public types, so consumers who only want an\n * input do not have to install zod to typecheck it.\n */\nexport type FieldSchema = {\n safeParse: (\n value: unknown,\n ) =>\n | { success: true }\n | { success: false; error: { issues: readonly { message: string }[] } };\n};\n\n/** Outcome of a single validation pass, handed to `onValidationChange`. */\nexport type FieldValidation = {\n valid: boolean;\n /** First issue reported by the schema, or `undefined` when valid. */\n error?: string;\n};\n\nexport type FieldBehaviour = {\n value?: string;\n defaultValue?: string;\n /** A caller-supplied error. Outranks the schema's verdict. */\n error?: string;\n onChange?: (value: string) => void;\n onBlur?: (event: FocusEvent<HTMLInputElement>) => void;\n schema?: FieldSchema;\n validateOn?: \"blur\" | \"change\" | \"never\";\n onValidationChange?: (state: FieldValidation) => void;\n};\n\nconst FALLBACK_MESSAGE = \"Invalid value\";\n\nfunction runSchema(\n schema: FieldSchema | undefined,\n value: string,\n): FieldValidation {\n if (!schema) {\n return { valid: true };\n }\n\n const result = schema.safeParse(value);\n if (result.success) {\n return { valid: true };\n }\n\n return {\n valid: false,\n error: result.error.issues[0]?.message ?? FALLBACK_MESSAGE,\n };\n}\n\n/**\n * Owns the value (controlled or not), the schema verdict, and when to re-run\n * it. Split out so the `Field` component itself stays markup.\n */\nexport function useFieldBehaviour({\n value,\n defaultValue,\n error,\n onChange,\n onBlur,\n schema,\n validateOn = \"blur\",\n onValidationChange,\n}: FieldBehaviour) {\n const [uncontrolledValue, setUncontrolledValue] = useState(\n defaultValue ?? \"\",\n );\n const [schemaError, setSchemaError] = useState<string | undefined>(undefined);\n\n const isControlled = value !== undefined;\n\n const validate = useCallback(\n (next: string) => {\n const state = runSchema(schema, next);\n setSchemaError(state.error);\n onValidationChange?.(state);\n },\n [schema, onValidationChange],\n );\n\n const handleChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => {\n const next = event.target.value;\n if (!isControlled) {\n setUncontrolledValue(next);\n }\n onChange?.(next);\n\n // Revalidate on every keystroke once the field is already invalid, so a\n // corrected value clears the message without waiting for another blur.\n if (validateOn === \"change\" || (validateOn === \"blur\" && schemaError)) {\n validate(next);\n }\n },\n [isControlled, onChange, validateOn, schemaError, validate],\n );\n\n const handleBlur = useCallback(\n (event: FocusEvent<HTMLInputElement>) => {\n if (validateOn === \"blur\") {\n validate(event.target.value);\n }\n onBlur?.(event);\n },\n [validateOn, validate, onBlur],\n );\n\n return {\n currentValue: isControlled ? value : uncontrolledValue,\n // A caller-supplied error outranks the schema's verdict.\n shownError: error ?? schemaError,\n handleChange,\n handleBlur,\n };\n}\n","import { Slot } from \"@radix-ui/react-slot\";\nimport type { ElementType, HTMLAttributes, ReactNode, Ref } from \"react\";\n\n/**\n * The library's type scale. Everything that renders text — field labels, hints,\n * form errors, headings — goes through here, so a size or colour lives in one\n * place and the tokens in `src/styles/index.css` stay the single source of\n * truth for what \"hint\" or \"muted\" means.\n *\n * Server-safe: no state, no handlers, no directive. It composes Radix `Slot`\n * for `asChild`, which is how a consumer puts these styles on their own\n * element (a `next/link`, a Radix `Label`) without an extra wrapper.\n */\n\nexport type TypographyVariant =\n | \"display\"\n | \"heading\"\n | \"subheading\"\n | \"body\"\n | \"label\"\n | \"hint\"\n | \"code\";\n\nexport type TypographyTone =\n | \"default\"\n | \"muted\"\n | \"subtle\"\n | \"accent\"\n | \"on-accent\"\n | \"success\"\n | \"warning\"\n | \"info\"\n | \"danger\";\n\nexport type TypographyWeight = \"regular\" | \"medium\" | \"semibold\" | \"bold\";\n\nexport type TypographyElement =\n | \"p\"\n | \"span\"\n | \"div\"\n | \"h1\"\n | \"h2\"\n | \"h3\"\n | \"h4\"\n | \"h5\"\n | \"h6\"\n | \"label\"\n | \"code\"\n | \"strong\"\n | \"em\"\n | \"small\";\n\nexport type TypographyProps = Omit<HTMLAttributes<HTMLElement>, \"color\"> & {\n /** Size and default element. `body` unless told otherwise. */\n variant?: TypographyVariant;\n /** Colour role, from the design tokens. */\n tone?: TypographyTone;\n /** Overrides the variant's default weight. */\n weight?: TypographyWeight;\n align?: \"start\" | \"center\" | \"end\";\n /** Clip to one line with an ellipsis. */\n truncate?: boolean;\n /**\n * An icon rendered beside the text, hidden from assistive tech. Sized to the\n * text with `1em`, so the same SVG fits every variant.\n */\n icon?: ReactNode;\n iconPosition?: \"leading\" | \"trailing\";\n /** The element to render. Defaults per variant (`h2` for a heading, and so on). */\n as?: TypographyElement;\n /**\n * Radix-style composition: render the child element itself, with these\n * styles merged onto it, instead of a wrapper. Takes precedence over `as`.\n */\n asChild?: boolean;\n ref?: Ref<HTMLElement>;\n children?: ReactNode;\n};\n\nconst DEFAULT_ELEMENT: Record<TypographyVariant, TypographyElement> = {\n display: \"h1\",\n heading: \"h2\",\n subheading: \"h3\",\n body: \"p\",\n label: \"span\",\n hint: \"p\",\n code: \"code\",\n};\n\nconst DEFAULT_WEIGHT: Record<TypographyVariant, TypographyWeight> = {\n display: \"semibold\",\n heading: \"semibold\",\n subheading: \"medium\",\n body: \"regular\",\n label: \"semibold\",\n hint: \"regular\",\n code: \"regular\",\n};\n\nconst VARIANT_CLASSES: Record<TypographyVariant, string> = {\n display: \"text-display leading-tight tracking-tight\",\n heading: \"text-heading leading-tight tracking-tight\",\n subheading: \"text-subheading leading-snug\",\n body: \"text-field leading-normal\",\n label: \"text-label leading-normal\",\n hint: \"text-hint leading-normal\",\n code: \"font-code text-field leading-normal\",\n};\n\nconst TONE_CLASSES: Record<TypographyTone, string> = {\n default: \"text-fg\",\n muted: \"text-fg-muted\",\n subtle: \"text-fg-subtle\",\n accent: \"text-accent\",\n \"on-accent\": \"text-on-accent\",\n success: \"text-success\",\n warning: \"text-warning\",\n info: \"text-info\",\n danger: \"text-danger\",\n};\n\nconst WEIGHT_CLASSES: Record<TypographyWeight, string> = {\n regular: \"font-normal\",\n medium: \"font-medium\",\n semibold: \"font-semibold\",\n bold: \"font-bold\",\n};\n\nconst ALIGN_CLASSES = {\n start: \"text-start\",\n center: \"text-center\",\n end: \"text-end\",\n} as const;\n\nconst ICON_CLASSES =\n \"inline-flex shrink-0 items-center [&>svg]:h-[1em] [&>svg]:w-[1em]\";\n\nfunction typographyClassName({\n variant,\n tone,\n weight,\n align,\n truncate,\n hasIcon,\n className,\n}: {\n variant: TypographyVariant;\n tone: TypographyTone;\n weight: TypographyWeight;\n align: \"start\" | \"center\" | \"end\" | undefined;\n truncate: boolean;\n hasIcon: boolean;\n className: string | undefined;\n}): string {\n return [\n VARIANT_CLASSES[variant],\n TONE_CLASSES[tone],\n WEIGHT_CLASSES[weight],\n align ? ALIGN_CLASSES[align] : \"\",\n hasIcon ? \"inline-flex items-center gap-1.5\" : \"\",\n // With an icon the root is a flex container, and text-overflow only\n // clips block text, so the text span carries the truncation instead.\n truncate && !hasIcon ? \"truncate\" : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n}\n\nexport function Typography({\n variant = \"body\",\n tone = \"default\",\n weight,\n align,\n truncate = false,\n icon,\n iconPosition = \"leading\",\n as,\n asChild = false,\n className,\n children,\n ...rest\n}: TypographyProps) {\n const hasIcon = icon !== undefined && icon !== null;\n // Widened to `ElementType`: the union of intrinsic tags would otherwise\n // demand a `ref` typed to one specific element.\n const Component: ElementType = asChild\n ? Slot\n : (as ?? DEFAULT_ELEMENT[variant]);\n\n const iconNode = hasIcon ? (\n <span aria-hidden=\"true\" className={ICON_CLASSES}>\n {icon}\n </span>\n ) : null;\n\n const content = hasIcon ? (\n <>\n {iconPosition === \"leading\" ? iconNode : null}\n <span className={truncate ? \"min-w-0 truncate\" : undefined}>\n {children}\n </span>\n {iconPosition === \"trailing\" ? iconNode : null}\n </>\n ) : (\n children\n );\n\n return (\n <Component\n {...rest}\n className={typographyClassName({\n variant,\n tone,\n weight: weight ?? DEFAULT_WEIGHT[variant],\n align,\n truncate,\n hasIcon,\n className,\n })}\n >\n {content}\n </Component>\n );\n}\n","\"use client\";\n\nimport { Field, type FieldProps } from \"@/components/field\";\n\nexport type {\n FieldSchema,\n FieldValidation,\n} from \"@/components/field\";\n\n/**\n * Plain free text: names, job titles, one-time codes. Anything with a format\n * of its own — an email address, a phone number, a password — has its own\n * component (`EmailInput`, `PhoneInput`, `PasswordInput`), which fixes the\n * keyboard, autocomplete and input type that format needs.\n */\nexport type TextInputProps = FieldProps & {\n /** Renders the value in the design's monospace face, as the code entry does. */\n mono?: boolean;\n};\n\nexport function TextInput({\n mono = false,\n className,\n ...rest\n}: TextInputProps) {\n return (\n <Field\n {...rest}\n type=\"text\"\n className={[mono ? \"font-code\" : \"\", className ?? \"\"]\n .filter(Boolean)\n .join(\" \")}\n />\n );\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx"]}
@@ -0,0 +1,120 @@
1
+ 'use strict';
2
+
3
+ var reactSlot = require('@radix-ui/react-slot');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/components/typography/index.tsx
7
+ var DEFAULT_ELEMENT = {
8
+ display: "h1",
9
+ heading: "h2",
10
+ subheading: "h3",
11
+ body: "p",
12
+ label: "span",
13
+ hint: "p",
14
+ code: "code"
15
+ };
16
+ var DEFAULT_WEIGHT = {
17
+ display: "semibold",
18
+ heading: "semibold",
19
+ subheading: "medium",
20
+ body: "regular",
21
+ label: "semibold",
22
+ hint: "regular",
23
+ code: "regular"
24
+ };
25
+ var VARIANT_CLASSES = {
26
+ display: "text-display leading-tight tracking-tight",
27
+ heading: "text-heading leading-tight tracking-tight",
28
+ subheading: "text-subheading leading-snug",
29
+ body: "text-field leading-normal",
30
+ label: "text-label leading-normal",
31
+ hint: "text-hint leading-normal",
32
+ code: "font-code text-field leading-normal"
33
+ };
34
+ var TONE_CLASSES = {
35
+ default: "text-fg",
36
+ muted: "text-fg-muted",
37
+ subtle: "text-fg-subtle",
38
+ accent: "text-accent",
39
+ "on-accent": "text-on-accent",
40
+ success: "text-success",
41
+ warning: "text-warning",
42
+ info: "text-info",
43
+ danger: "text-danger"
44
+ };
45
+ var WEIGHT_CLASSES = {
46
+ regular: "font-normal",
47
+ medium: "font-medium",
48
+ semibold: "font-semibold",
49
+ bold: "font-bold"
50
+ };
51
+ var ALIGN_CLASSES = {
52
+ start: "text-start",
53
+ center: "text-center",
54
+ end: "text-end"
55
+ };
56
+ var ICON_CLASSES = "inline-flex shrink-0 items-center [&>svg]:h-[1em] [&>svg]:w-[1em]";
57
+ function typographyClassName({
58
+ variant,
59
+ tone,
60
+ weight,
61
+ align,
62
+ truncate,
63
+ hasIcon,
64
+ className
65
+ }) {
66
+ return [
67
+ VARIANT_CLASSES[variant],
68
+ TONE_CLASSES[tone],
69
+ WEIGHT_CLASSES[weight],
70
+ align ? ALIGN_CLASSES[align] : "",
71
+ hasIcon ? "inline-flex items-center gap-1.5" : "",
72
+ // With an icon the root is a flex container, and text-overflow only
73
+ // clips block text, so the text span carries the truncation instead.
74
+ truncate && !hasIcon ? "truncate" : "",
75
+ className ?? ""
76
+ ].filter(Boolean).join(" ");
77
+ }
78
+ function Typography({
79
+ variant = "body",
80
+ tone = "default",
81
+ weight,
82
+ align,
83
+ truncate = false,
84
+ icon,
85
+ iconPosition = "leading",
86
+ as,
87
+ asChild = false,
88
+ className,
89
+ children,
90
+ ...rest
91
+ }) {
92
+ const hasIcon = icon !== void 0 && icon !== null;
93
+ const Component = asChild ? reactSlot.Slot : as ?? DEFAULT_ELEMENT[variant];
94
+ const iconNode = hasIcon ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: ICON_CLASSES, children: icon }) : null;
95
+ const content = hasIcon ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
96
+ iconPosition === "leading" ? iconNode : null,
97
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: truncate ? "min-w-0 truncate" : void 0, children }),
98
+ iconPosition === "trailing" ? iconNode : null
99
+ ] }) : children;
100
+ return /* @__PURE__ */ jsxRuntime.jsx(
101
+ Component,
102
+ {
103
+ ...rest,
104
+ className: typographyClassName({
105
+ variant,
106
+ tone,
107
+ weight: weight ?? DEFAULT_WEIGHT[variant],
108
+ align,
109
+ truncate,
110
+ hasIcon,
111
+ className
112
+ }),
113
+ children: content
114
+ }
115
+ );
116
+ }
117
+
118
+ exports.Typography = Typography;
119
+ //# sourceMappingURL=typography.cjs.map
120
+ //# sourceMappingURL=typography.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/typography/index.tsx"],"names":["Slot","jsx","jsxs","Fragment"],"mappings":";;;;;;AA+EA,IAAM,eAAA,GAAgE;AAAA,EACpE,OAAA,EAAS,IAAA;AAAA,EACT,OAAA,EAAS,IAAA;AAAA,EACT,UAAA,EAAY,IAAA;AAAA,EACZ,IAAA,EAAM,GAAA;AAAA,EACN,KAAA,EAAO,MAAA;AAAA,EACP,IAAA,EAAM,GAAA;AAAA,EACN,IAAA,EAAM;AACR,CAAA;AAEA,IAAM,cAAA,GAA8D;AAAA,EAClE,OAAA,EAAS,UAAA;AAAA,EACT,OAAA,EAAS,UAAA;AAAA,EACT,UAAA,EAAY,QAAA;AAAA,EACZ,IAAA,EAAM,SAAA;AAAA,EACN,KAAA,EAAO,UAAA;AAAA,EACP,IAAA,EAAM,SAAA;AAAA,EACN,IAAA,EAAM;AACR,CAAA;AAEA,IAAM,eAAA,GAAqD;AAAA,EACzD,OAAA,EAAS,2CAAA;AAAA,EACT,OAAA,EAAS,2CAAA;AAAA,EACT,UAAA,EAAY,8BAAA;AAAA,EACZ,IAAA,EAAM,2BAAA;AAAA,EACN,KAAA,EAAO,2BAAA;AAAA,EACP,IAAA,EAAM,0BAAA;AAAA,EACN,IAAA,EAAM;AACR,CAAA;AAEA,IAAM,YAAA,GAA+C;AAAA,EACnD,OAAA,EAAS,SAAA;AAAA,EACT,KAAA,EAAO,eAAA;AAAA,EACP,MAAA,EAAQ,gBAAA;AAAA,EACR,MAAA,EAAQ,aAAA;AAAA,EACR,WAAA,EAAa,gBAAA;AAAA,EACb,OAAA,EAAS,cAAA;AAAA,EACT,OAAA,EAAS,cAAA;AAAA,EACT,IAAA,EAAM,WAAA;AAAA,EACN,MAAA,EAAQ;AACV,CAAA;AAEA,IAAM,cAAA,GAAmD;AAAA,EACvD,OAAA,EAAS,aAAA;AAAA,EACT,MAAA,EAAQ,aAAA;AAAA,EACR,QAAA,EAAU,eAAA;AAAA,EACV,IAAA,EAAM;AACR,CAAA;AAEA,IAAM,aAAA,GAAgB;AAAA,EACpB,KAAA,EAAO,YAAA;AAAA,EACP,MAAA,EAAQ,aAAA;AAAA,EACR,GAAA,EAAK;AACP,CAAA;AAEA,IAAM,YAAA,GACJ,mEAAA;AAEF,SAAS,mBAAA,CAAoB;AAAA,EAC3B,OAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAQW;AACT,EAAA,OAAO;AAAA,IACL,gBAAgB,OAAO,CAAA;AAAA,IACvB,aAAa,IAAI,CAAA;AAAA,IACjB,eAAe,MAAM,CAAA;AAAA,IACrB,KAAA,GAAQ,aAAA,CAAc,KAAK,CAAA,GAAI,EAAA;AAAA,IAC/B,UAAU,kCAAA,GAAqC,EAAA;AAAA;AAAA;AAAA,IAG/C,QAAA,IAAY,CAAC,OAAA,GAAU,UAAA,GAAa,EAAA;AAAA,IACpC,SAAA,IAAa;AAAA,GACf,CACG,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AACb;AAEO,SAAS,UAAA,CAAW;AAAA,EACzB,OAAA,GAAU,MAAA;AAAA,EACV,IAAA,GAAO,SAAA;AAAA,EACP,MAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX,IAAA;AAAA,EACA,YAAA,GAAe,SAAA;AAAA,EACf,EAAA;AAAA,EACA,OAAA,GAAU,KAAA;AAAA,EACV,SAAA;AAAA,EACA,QAAA;AAAA,EACA,GAAG;AACL,CAAA,EAAoB;AAClB,EAAA,MAAM,OAAA,GAAU,IAAA,KAAS,MAAA,IAAa,IAAA,KAAS,IAAA;AAG/C,EAAA,MAAM,SAAA,GAAyB,OAAA,GAC3BA,cAAA,GACC,EAAA,IAAM,gBAAgB,OAAO,CAAA;AAElC,EAAA,MAAM,QAAA,GAAW,0BACfC,cAAA,CAAC,MAAA,EAAA,EAAK,eAAY,MAAA,EAAO,SAAA,EAAW,YAAA,EACjC,QAAA,EAAA,IAAA,EACH,CAAA,GACE,IAAA;AAEJ,EAAA,MAAM,OAAA,GAAU,0BACdC,eAAA,CAAAC,mBAAA,EAAA,EACG,QAAA,EAAA;AAAA,IAAA,YAAA,KAAiB,YAAY,QAAA,GAAW,IAAA;AAAA,mCACxC,MAAA,EAAA,EAAK,SAAA,EAAW,QAAA,GAAW,kBAAA,GAAqB,QAC9C,QAAA,EACH,CAAA;AAAA,IACC,YAAA,KAAiB,aAAa,QAAA,GAAW;AAAA,GAAA,EAC5C,CAAA,GAEA,QAAA;AAGF,EAAA,uBACEF,cAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACE,GAAG,IAAA;AAAA,MACJ,WAAW,mBAAA,CAAoB;AAAA,QAC7B,OAAA;AAAA,QACA,IAAA;AAAA,QACA,MAAA,EAAQ,MAAA,IAAU,cAAA,CAAe,OAAO,CAAA;AAAA,QACxC,KAAA;AAAA,QACA,QAAA;AAAA,QACA,OAAA;AAAA,QACA;AAAA,OACD,CAAA;AAAA,MAEA,QAAA,EAAA;AAAA;AAAA,GACH;AAEJ","file":"typography.cjs","sourcesContent":["import { Slot } from \"@radix-ui/react-slot\";\nimport type { ElementType, HTMLAttributes, ReactNode, Ref } from \"react\";\n\n/**\n * The library's type scale. Everything that renders text — field labels, hints,\n * form errors, headings — goes through here, so a size or colour lives in one\n * place and the tokens in `src/styles/index.css` stay the single source of\n * truth for what \"hint\" or \"muted\" means.\n *\n * Server-safe: no state, no handlers, no directive. It composes Radix `Slot`\n * for `asChild`, which is how a consumer puts these styles on their own\n * element (a `next/link`, a Radix `Label`) without an extra wrapper.\n */\n\nexport type TypographyVariant =\n | \"display\"\n | \"heading\"\n | \"subheading\"\n | \"body\"\n | \"label\"\n | \"hint\"\n | \"code\";\n\nexport type TypographyTone =\n | \"default\"\n | \"muted\"\n | \"subtle\"\n | \"accent\"\n | \"on-accent\"\n | \"success\"\n | \"warning\"\n | \"info\"\n | \"danger\";\n\nexport type TypographyWeight = \"regular\" | \"medium\" | \"semibold\" | \"bold\";\n\nexport type TypographyElement =\n | \"p\"\n | \"span\"\n | \"div\"\n | \"h1\"\n | \"h2\"\n | \"h3\"\n | \"h4\"\n | \"h5\"\n | \"h6\"\n | \"label\"\n | \"code\"\n | \"strong\"\n | \"em\"\n | \"small\";\n\nexport type TypographyProps = Omit<HTMLAttributes<HTMLElement>, \"color\"> & {\n /** Size and default element. `body` unless told otherwise. */\n variant?: TypographyVariant;\n /** Colour role, from the design tokens. */\n tone?: TypographyTone;\n /** Overrides the variant's default weight. */\n weight?: TypographyWeight;\n align?: \"start\" | \"center\" | \"end\";\n /** Clip to one line with an ellipsis. */\n truncate?: boolean;\n /**\n * An icon rendered beside the text, hidden from assistive tech. Sized to the\n * text with `1em`, so the same SVG fits every variant.\n */\n icon?: ReactNode;\n iconPosition?: \"leading\" | \"trailing\";\n /** The element to render. Defaults per variant (`h2` for a heading, and so on). */\n as?: TypographyElement;\n /**\n * Radix-style composition: render the child element itself, with these\n * styles merged onto it, instead of a wrapper. Takes precedence over `as`.\n */\n asChild?: boolean;\n ref?: Ref<HTMLElement>;\n children?: ReactNode;\n};\n\nconst DEFAULT_ELEMENT: Record<TypographyVariant, TypographyElement> = {\n display: \"h1\",\n heading: \"h2\",\n subheading: \"h3\",\n body: \"p\",\n label: \"span\",\n hint: \"p\",\n code: \"code\",\n};\n\nconst DEFAULT_WEIGHT: Record<TypographyVariant, TypographyWeight> = {\n display: \"semibold\",\n heading: \"semibold\",\n subheading: \"medium\",\n body: \"regular\",\n label: \"semibold\",\n hint: \"regular\",\n code: \"regular\",\n};\n\nconst VARIANT_CLASSES: Record<TypographyVariant, string> = {\n display: \"text-display leading-tight tracking-tight\",\n heading: \"text-heading leading-tight tracking-tight\",\n subheading: \"text-subheading leading-snug\",\n body: \"text-field leading-normal\",\n label: \"text-label leading-normal\",\n hint: \"text-hint leading-normal\",\n code: \"font-code text-field leading-normal\",\n};\n\nconst TONE_CLASSES: Record<TypographyTone, string> = {\n default: \"text-fg\",\n muted: \"text-fg-muted\",\n subtle: \"text-fg-subtle\",\n accent: \"text-accent\",\n \"on-accent\": \"text-on-accent\",\n success: \"text-success\",\n warning: \"text-warning\",\n info: \"text-info\",\n danger: \"text-danger\",\n};\n\nconst WEIGHT_CLASSES: Record<TypographyWeight, string> = {\n regular: \"font-normal\",\n medium: \"font-medium\",\n semibold: \"font-semibold\",\n bold: \"font-bold\",\n};\n\nconst ALIGN_CLASSES = {\n start: \"text-start\",\n center: \"text-center\",\n end: \"text-end\",\n} as const;\n\nconst ICON_CLASSES =\n \"inline-flex shrink-0 items-center [&>svg]:h-[1em] [&>svg]:w-[1em]\";\n\nfunction typographyClassName({\n variant,\n tone,\n weight,\n align,\n truncate,\n hasIcon,\n className,\n}: {\n variant: TypographyVariant;\n tone: TypographyTone;\n weight: TypographyWeight;\n align: \"start\" | \"center\" | \"end\" | undefined;\n truncate: boolean;\n hasIcon: boolean;\n className: string | undefined;\n}): string {\n return [\n VARIANT_CLASSES[variant],\n TONE_CLASSES[tone],\n WEIGHT_CLASSES[weight],\n align ? ALIGN_CLASSES[align] : \"\",\n hasIcon ? \"inline-flex items-center gap-1.5\" : \"\",\n // With an icon the root is a flex container, and text-overflow only\n // clips block text, so the text span carries the truncation instead.\n truncate && !hasIcon ? \"truncate\" : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n}\n\nexport function Typography({\n variant = \"body\",\n tone = \"default\",\n weight,\n align,\n truncate = false,\n icon,\n iconPosition = \"leading\",\n as,\n asChild = false,\n className,\n children,\n ...rest\n}: TypographyProps) {\n const hasIcon = icon !== undefined && icon !== null;\n // Widened to `ElementType`: the union of intrinsic tags would otherwise\n // demand a `ref` typed to one specific element.\n const Component: ElementType = asChild\n ? Slot\n : (as ?? DEFAULT_ELEMENT[variant]);\n\n const iconNode = hasIcon ? (\n <span aria-hidden=\"true\" className={ICON_CLASSES}>\n {icon}\n </span>\n ) : null;\n\n const content = hasIcon ? (\n <>\n {iconPosition === \"leading\" ? iconNode : null}\n <span className={truncate ? \"min-w-0 truncate\" : undefined}>\n {children}\n </span>\n {iconPosition === \"trailing\" ? iconNode : null}\n </>\n ) : (\n children\n );\n\n return (\n <Component\n {...rest}\n className={typographyClassName({\n variant,\n tone,\n weight: weight ?? DEFAULT_WEIGHT[variant],\n align,\n truncate,\n hasIcon,\n className,\n })}\n >\n {content}\n </Component>\n );\n}\n"]}
@@ -0,0 +1,46 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes, ReactNode, Ref } from 'react';
3
+
4
+ /**
5
+ * The library's type scale. Everything that renders text — field labels, hints,
6
+ * form errors, headings — goes through here, so a size or colour lives in one
7
+ * place and the tokens in `src/styles/index.css` stay the single source of
8
+ * truth for what "hint" or "muted" means.
9
+ *
10
+ * Server-safe: no state, no handlers, no directive. It composes Radix `Slot`
11
+ * for `asChild`, which is how a consumer puts these styles on their own
12
+ * element (a `next/link`, a Radix `Label`) without an extra wrapper.
13
+ */
14
+ type TypographyVariant = "display" | "heading" | "subheading" | "body" | "label" | "hint" | "code";
15
+ type TypographyTone = "default" | "muted" | "subtle" | "accent" | "on-accent" | "success" | "warning" | "info" | "danger";
16
+ type TypographyWeight = "regular" | "medium" | "semibold" | "bold";
17
+ type TypographyElement = "p" | "span" | "div" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "label" | "code" | "strong" | "em" | "small";
18
+ type TypographyProps = Omit<HTMLAttributes<HTMLElement>, "color"> & {
19
+ /** Size and default element. `body` unless told otherwise. */
20
+ variant?: TypographyVariant;
21
+ /** Colour role, from the design tokens. */
22
+ tone?: TypographyTone;
23
+ /** Overrides the variant's default weight. */
24
+ weight?: TypographyWeight;
25
+ align?: "start" | "center" | "end";
26
+ /** Clip to one line with an ellipsis. */
27
+ truncate?: boolean;
28
+ /**
29
+ * An icon rendered beside the text, hidden from assistive tech. Sized to the
30
+ * text with `1em`, so the same SVG fits every variant.
31
+ */
32
+ icon?: ReactNode;
33
+ iconPosition?: "leading" | "trailing";
34
+ /** The element to render. Defaults per variant (`h2` for a heading, and so on). */
35
+ as?: TypographyElement;
36
+ /**
37
+ * Radix-style composition: render the child element itself, with these
38
+ * styles merged onto it, instead of a wrapper. Takes precedence over `as`.
39
+ */
40
+ asChild?: boolean;
41
+ ref?: Ref<HTMLElement>;
42
+ children?: ReactNode;
43
+ };
44
+ declare function Typography({ variant, tone, weight, align, truncate, icon, iconPosition, as, asChild, className, children, ...rest }: TypographyProps): react.JSX.Element;
45
+
46
+ export { Typography, type TypographyElement, type TypographyProps, type TypographyTone, type TypographyVariant, type TypographyWeight };
@@ -0,0 +1,46 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes, ReactNode, Ref } from 'react';
3
+
4
+ /**
5
+ * The library's type scale. Everything that renders text — field labels, hints,
6
+ * form errors, headings — goes through here, so a size or colour lives in one
7
+ * place and the tokens in `src/styles/index.css` stay the single source of
8
+ * truth for what "hint" or "muted" means.
9
+ *
10
+ * Server-safe: no state, no handlers, no directive. It composes Radix `Slot`
11
+ * for `asChild`, which is how a consumer puts these styles on their own
12
+ * element (a `next/link`, a Radix `Label`) without an extra wrapper.
13
+ */
14
+ type TypographyVariant = "display" | "heading" | "subheading" | "body" | "label" | "hint" | "code";
15
+ type TypographyTone = "default" | "muted" | "subtle" | "accent" | "on-accent" | "success" | "warning" | "info" | "danger";
16
+ type TypographyWeight = "regular" | "medium" | "semibold" | "bold";
17
+ type TypographyElement = "p" | "span" | "div" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "label" | "code" | "strong" | "em" | "small";
18
+ type TypographyProps = Omit<HTMLAttributes<HTMLElement>, "color"> & {
19
+ /** Size and default element. `body` unless told otherwise. */
20
+ variant?: TypographyVariant;
21
+ /** Colour role, from the design tokens. */
22
+ tone?: TypographyTone;
23
+ /** Overrides the variant's default weight. */
24
+ weight?: TypographyWeight;
25
+ align?: "start" | "center" | "end";
26
+ /** Clip to one line with an ellipsis. */
27
+ truncate?: boolean;
28
+ /**
29
+ * An icon rendered beside the text, hidden from assistive tech. Sized to the
30
+ * text with `1em`, so the same SVG fits every variant.
31
+ */
32
+ icon?: ReactNode;
33
+ iconPosition?: "leading" | "trailing";
34
+ /** The element to render. Defaults per variant (`h2` for a heading, and so on). */
35
+ as?: TypographyElement;
36
+ /**
37
+ * Radix-style composition: render the child element itself, with these
38
+ * styles merged onto it, instead of a wrapper. Takes precedence over `as`.
39
+ */
40
+ asChild?: boolean;
41
+ ref?: Ref<HTMLElement>;
42
+ children?: ReactNode;
43
+ };
44
+ declare function Typography({ variant, tone, weight, align, truncate, icon, iconPosition, as, asChild, className, children, ...rest }: TypographyProps): react.JSX.Element;
45
+
46
+ export { Typography, type TypographyElement, type TypographyProps, type TypographyTone, type TypographyVariant, type TypographyWeight };
@@ -0,0 +1,3 @@
1
+ export { Typography } from './chunk-NZHHNVXZ.js';
2
+ //# sourceMappingURL=typography.js.map
3
+ //# sourceMappingURL=typography.js.map