@ps1ui/core 0.1.0 → 0.2.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.
package/dist/base.css CHANGED
@@ -19,6 +19,17 @@
19
19
  --ps1ui-color-primary-active: #3fb950;
20
20
  --ps1ui-color-primary-fg: #0b0f14;
21
21
  --ps1ui-color-accent: #ffa657;
22
+ /* Destructive / error signalling. Hue sits in the rose-coral band the code
23
+ palette already inhabits (--ps1ui-code-keyword / -tag / -deleted) so the
24
+ surface reads as one system; luminance is tuned so the base color hits
25
+ ≥ 4.5:1 vs BOTH --ps1ui-color-bg and --ps1ui-color-surface (verified by
26
+ Text.contrast.test.tsx) and danger-fg on each danger-* fill hits the
27
+ same threshold (verified by Button.contrast.test.tsx). Hover/active
28
+ progressively darken while staying above contrast. */
29
+ --ps1ui-color-danger: #ff7d8d;
30
+ --ps1ui-color-danger-hover: #f0637a;
31
+ --ps1ui-color-danger-active: #e04a60;
32
+ --ps1ui-color-danger-fg: #0b0f14;
22
33
  --ps1ui-color-focus-ring-soft: color-mix(in srgb, var(--ps1ui-color-primary) 14%, transparent);
23
34
  --ps1ui-shadow-focus-ring: 0 0 0 3px var(--ps1ui-color-focus-ring-soft);
24
35
 
@@ -88,6 +99,7 @@
88
99
 
89
100
  --ps1ui-transition-fast: 120ms ease;
90
101
  }
102
+
91
103
  /* ==========================================================================
92
104
  * Reset
93
105
  * --------------------------------------------------------------------------
@@ -107,6 +119,7 @@
107
119
  * layouts unless you set display:inline explicitly), <ul>/<ol> lose their
108
120
  * bullets. Locked in by src/styles/reset.test.tsx.
109
121
  * ========================================================================== */
122
+
110
123
  /* Box model + border baseline. Applied to `*` only — NOT to ::before/::after
111
124
  /::backdrop/::file-selector-button. Pseudo-elements with border-drawn
112
125
  glyphs (the classic rotated-L checkmark, tooltip carets, radio-dot rings)
@@ -120,6 +133,7 @@
120
133
  box-sizing: border-box;
121
134
  border: 0 solid;
122
135
  }
136
+
123
137
  body,
124
138
  blockquote,
125
139
  dl,
@@ -140,6 +154,7 @@ pre,
140
154
  ul {
141
155
  margin: 0;
142
156
  }
157
+
143
158
  fieldset,
144
159
  legend,
145
160
  menu,
@@ -147,6 +162,7 @@ ol,
147
162
  ul {
148
163
  padding: 0;
149
164
  }
165
+
150
166
  html {
151
167
  color-scheme: dark;
152
168
  line-height: 1.5;
@@ -157,11 +173,13 @@ html {
157
173
  tab-size: 4;
158
174
  text-rendering: optimizeLegibility;
159
175
  }
176
+
160
177
  body {
161
178
  min-height: 100dvh;
162
179
  -webkit-font-smoothing: antialiased;
163
180
  -moz-osx-font-smoothing: grayscale;
164
181
  }
182
+
165
183
  /* Heading responsibility belongs to <Heading>; raw h1..h6 inherit so a bare
166
184
  <h1> outside <Heading> doesn't blow up to the UA default. */
167
185
  h1,
@@ -175,15 +193,18 @@ h6 {
175
193
  text-wrap: balance;
176
194
  overflow-wrap: break-word;
177
195
  }
196
+
178
197
  p {
179
198
  text-wrap: pretty;
180
199
  overflow-wrap: break-word;
181
200
  }
201
+
182
202
  ol,
183
203
  ul,
184
204
  menu {
185
205
  list-style: none;
186
206
  }
207
+
187
208
  img,
188
209
  svg,
189
210
  video,
@@ -195,17 +216,20 @@ object {
195
216
  display: block;
196
217
  vertical-align: middle;
197
218
  }
219
+
198
220
  img,
199
221
  video {
200
222
  max-width: 100%;
201
223
  height: auto;
202
224
  }
225
+
203
226
  /* Anchor responsibility belongs to <Anchor>; raw <a> inherits color/decoration
204
227
  so it doesn't paint as UA blue underline against the dark canvas. */
205
228
  a {
206
229
  color: inherit;
207
230
  text-decoration: inherit;
208
231
  }
232
+
209
233
  /* UA still refuses to inherit font/color into form controls in 2026.
210
234
  Inherit font-feature-settings / font-variation-settings too so JetBrains
211
235
  Mono Variable's axes stay live inside <input> / <textarea>. */
@@ -224,13 +248,16 @@ textarea,
224
248
  border-radius: 0;
225
249
  opacity: 1;
226
250
  }
251
+
227
252
  :where(select:is([multiple], [size])) optgroup {
228
253
  font-weight: bolder;
229
254
  }
255
+
230
256
  button,
231
257
  select {
232
258
  text-transform: none;
233
259
  }
260
+
234
261
  button,
235
262
  input:where([type="button"], [type="reset"], [type="submit"]),
236
263
  ::file-selector-button {
@@ -238,12 +265,15 @@ input:where([type="button"], [type="reset"], [type="submit"]),
238
265
  appearance: button;
239
266
  -webkit-appearance: button;
240
267
  }
268
+
241
269
  button {
242
270
  cursor: pointer;
243
271
  }
272
+
244
273
  textarea {
245
274
  resize: vertical;
246
275
  }
276
+
247
277
  /* `in srgb` for consistency with the library's other color-mix calls. The
248
278
  space is irrelevant when mixing with `transparent` (premultiplied-alpha
249
279
  interpolation, CSS Color 4), so diverging from Tailwind Preflight's oklab
@@ -256,23 +286,28 @@ textarea {
256
286
  opacity: 1;
257
287
  color: color-mix(in srgb, currentcolor 55%, transparent);
258
288
  }
289
+
259
290
  ::-webkit-date-and-time-value {
260
291
  min-height: 1lh;
261
292
  text-align: inherit;
262
293
  }
294
+
263
295
  ::-webkit-datetime-edit {
264
296
  display: inline-flex;
265
297
  padding: 0;
266
298
  }
299
+
267
300
  ::-webkit-datetime-edit-fields-wrapper {
268
301
  padding: 0;
269
302
  }
303
+
270
304
  ::-webkit-inner-spin-button,
271
305
  ::-webkit-outer-spin-button {
272
306
  -webkit-appearance: none;
273
307
  margin: 0;
274
308
  height: auto;
275
309
  }
310
+
276
311
  /* number spinner: Chromium is already handled by ::-webkit-inner/outer-spin-button
277
312
  { appearance: none } above. Firefox needs its own vendor prefix — the unprefixed
278
313
  `appearance: textfield` is deprecated per CSS UI Level 4 (moved to compatibility
@@ -280,27 +315,34 @@ textarea {
280
315
  input[type="number"] {
281
316
  -moz-appearance: textfield;
282
317
  }
318
+
283
319
  [type="search"] {
284
320
  -webkit-appearance: textfield;
285
321
  outline-offset: -2px;
286
322
  }
323
+
287
324
  ::-webkit-search-decoration,
288
325
  ::-webkit-search-cancel-button {
289
326
  -webkit-appearance: none;
290
327
  }
328
+
291
329
  :-moz-ui-invalid {
292
330
  box-shadow: none;
293
331
  }
332
+
294
333
  :-moz-focusring:where(:not(iframe)) {
295
334
  outline: auto;
296
335
  }
336
+
297
337
  b,
298
338
  strong {
299
339
  font-weight: bolder;
300
340
  }
341
+
301
342
  small {
302
343
  font-size: 80%;
303
344
  }
345
+
304
346
  code,
305
347
  kbd,
306
348
  samp,
@@ -308,14 +350,17 @@ pre {
308
350
  font-family: var(--ps1ui-font-mono);
309
351
  font-size: 1em;
310
352
  }
353
+
311
354
  abbr:where([title]) {
312
355
  -webkit-text-decoration: underline dotted;
313
356
  text-decoration: underline dotted;
314
357
  }
358
+
315
359
  q::before,
316
360
  q::after {
317
361
  content: "";
318
362
  }
363
+
319
364
  /* Keeps sub/sup from expanding the parent line-height. */
320
365
  sub,
321
366
  sup {
@@ -324,34 +369,42 @@ sup {
324
369
  position: relative;
325
370
  vertical-align: baseline;
326
371
  }
372
+
327
373
  sub {
328
374
  bottom: -0.25em;
329
375
  }
376
+
330
377
  sup {
331
378
  top: -0.5em;
332
379
  }
380
+
333
381
  table {
334
382
  text-indent: 0;
335
383
  border-color: inherit;
336
384
  border-collapse: collapse;
337
385
  }
386
+
338
387
  hr {
339
388
  height: 0;
340
389
  color: inherit;
341
390
  border-top: 1px solid;
342
391
  }
392
+
343
393
  summary {
344
394
  display: list-item;
345
395
  }
396
+
346
397
  /* [hidden] would lose to `display: flex` etc. without this. */
347
398
  [hidden]:where(:not([hidden="until-found"])) {
348
399
  display: none !important;
349
400
  }
401
+
350
402
  @media (prefers-reduced-motion: no-preference) {
351
403
  html {
352
404
  interpolate-size: allow-keywords;
353
405
  }
354
406
  }
407
+
355
408
  /* ==========================================================================
356
409
  * Ambient environment
357
410
  * --------------------------------------------------------------------------
@@ -363,15 +416,18 @@ summary {
363
416
  * area would reveal the light UA default and flash white on scroll. `color`
364
417
  * and `font-family` cascade down to `body` and descendants naturally.
365
418
  * ========================================================================== */
419
+
366
420
  html {
367
421
  background: var(--ps1ui-color-bg);
368
422
  color: var(--ps1ui-color-fg);
369
423
  font-family: var(--ps1ui-font-mono);
370
424
  }
425
+
371
426
  ::-moz-selection {
372
427
  background: var(--ps1ui-color-primary);
373
428
  color: var(--ps1ui-color-primary-fg);
374
429
  }
430
+
375
431
  ::selection {
376
432
  background: var(--ps1ui-color-primary);
377
433
  color: var(--ps1ui-color-primary-fg);
@@ -1,10 +1,10 @@
1
1
  import { ComponentPropsWithoutRef, ElementType } from "react";
2
2
  //#region src/components/Button/Button.d.ts
3
- type ButtonVariant = "primary" | "secondary";
3
+ type ButtonVariant = "primary" | "secondary" | "danger";
4
4
  type ButtonOwnProps<E extends ElementType> = {
5
5
  /** Element or component to render instead of the native <button> — e.g. "a" or a router Link. */
6
6
  as?: E;
7
- /** Visual style: "primary" for the main action, "secondary" for supporting actions. */
7
+ /** Visual style: "primary" for the main action, "secondary" for supporting actions, "danger" for destructive actions. */
8
8
  variant?: ButtonVariant;
9
9
  };
10
10
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"Button.mjs","names":[],"sources":["../../../src/components/Button/Button.tsx"],"sourcesContent":["import { createElement } from \"react\";\nimport type { ComponentPropsWithoutRef, ElementType } from \"react\";\nimport { cx } from \"../../utils/cx\";\n\nexport type ButtonVariant = \"primary\" | \"secondary\";\n\ntype ButtonOwnProps<E extends ElementType> = {\n /** Element or component to render instead of the native <button> — e.g. \"a\" or a router Link. */\n as?: E;\n /** Visual style: \"primary\" for the main action, \"secondary\" for supporting actions. */\n variant?: ButtonVariant;\n};\n\n// Deliberate exception to the general \"use ComponentProps<'tag'>\" rule other components follow —\n// same reasoning as Anchor / Text: with a polymorphic `as` prop TypeScript cannot correctly\n// narrow the `ref` prop's type against the resolved element E. Button accepts both string tags\n// (`as=\"a\"`) and React component types (`as={NextLink}`) so consumers can render a link that\n// looks like a button; a loose ref type would silently accept mismatched refs, so dropping ref\n// from the prop type is safer than a misleading loose type.\n/**\n * Props for {@link Button}. Derived from the props of the rendered element `E`,\n * so what's accepted follows the `as` target.\n *\n * `disabled` and polymorphism: `disabled` only exists on form elements, so\n * `<Button as=\"a\" href=\"…\" disabled>` is a type error (`ComponentPropsWithoutRef<\"a\">`\n * has no `disabled` — locked in by `Button.test-d.tsx`). This is intentional and\n * matches the ARIA Authoring Practices: links are not disabled. When the action is\n * unavailable, render a native `<Button disabled>` (or no link at all) instead of a\n * link. Untyped JS callers and `as={Component}` escapes that pass `disabled` anyway\n * get a leaked no-op attribute — no visual or behavioral disabling happens.\n *\n * `type` on the native tag: when rendering a native <button>, `type` defaults to\n * \"button\" (not the browser's implicit \"submit\"), so a Button inside a <form>\n * never submits implicitly — pass type=\"submit\" explicitly to submit.\n */\nexport type ButtonProps<E extends ElementType = \"button\"> = ButtonOwnProps<E> &\n Omit<ComponentPropsWithoutRef<E>, keyof ButtonOwnProps<E>>;\n\nexport function Button<E extends ElementType = \"button\">({\n as,\n variant = \"primary\",\n className,\n ...rest\n}: ButtonProps<E>) {\n const tag: ElementType = as ?? \"button\";\n const classes = cx(\"ps1ui-button\", `ps1ui-button--${variant}`, className);\n const props: Record<string, unknown> = { ...rest, className: classes };\n // `type=\"button\"` is a defensive default so a native <button> inside a <form> never\n // submits implicitly. It only makes sense on the native tag — <a>, <NextLink>, and\n // other host/component targets have no such attribute, and stamping one on them\n // would leak an invalid attribute through to the DOM. Explicit callers still win.\n if (tag === \"button\" && props.type === undefined) {\n props.type = \"button\";\n }\n return createElement(tag, props);\n}\n"],"mappings":";;;AAsCA,SAAgB,OAAyC,EACvD,IACA,UAAU,WACV,WACA,GAAG,QACc;CACjB,MAAM,MAAmB,MAAM;CAC/B,MAAM,UAAU,GAAG,gBAAgB,iBAAiB,WAAW,SAAS;CACxE,MAAM,QAAiC;EAAE,GAAG;EAAM,WAAW;CAAQ;CAKrE,IAAI,QAAQ,YAAY,MAAM,SAAS,KAAA,GACrC,MAAM,OAAO;CAEf,OAAO,cAAc,KAAK,KAAK;AACjC"}
1
+ {"version":3,"file":"Button.mjs","names":[],"sources":["../../../src/components/Button/Button.tsx"],"sourcesContent":["import { createElement } from \"react\";\nimport type { ComponentPropsWithoutRef, ElementType } from \"react\";\nimport { cx } from \"../../utils/cx\";\n\nexport type ButtonVariant = \"primary\" | \"secondary\" | \"danger\";\n\ntype ButtonOwnProps<E extends ElementType> = {\n /** Element or component to render instead of the native <button> — e.g. \"a\" or a router Link. */\n as?: E;\n /** Visual style: \"primary\" for the main action, \"secondary\" for supporting actions, \"danger\" for destructive actions. */\n variant?: ButtonVariant;\n};\n\n// Deliberate exception to the general \"use ComponentProps<'tag'>\" rule other components follow —\n// same reasoning as Anchor / Text: with a polymorphic `as` prop TypeScript cannot correctly\n// narrow the `ref` prop's type against the resolved element E. Button accepts both string tags\n// (`as=\"a\"`) and React component types (`as={NextLink}`) so consumers can render a link that\n// looks like a button; a loose ref type would silently accept mismatched refs, so dropping ref\n// from the prop type is safer than a misleading loose type.\n/**\n * Props for {@link Button}. Derived from the props of the rendered element `E`,\n * so what's accepted follows the `as` target.\n *\n * `disabled` and polymorphism: `disabled` only exists on form elements, so\n * `<Button as=\"a\" href=\"…\" disabled>` is a type error (`ComponentPropsWithoutRef<\"a\">`\n * has no `disabled` — locked in by `Button.test-d.tsx`). This is intentional and\n * matches the ARIA Authoring Practices: links are not disabled. When the action is\n * unavailable, render a native `<Button disabled>` (or no link at all) instead of a\n * link. Untyped JS callers and `as={Component}` escapes that pass `disabled` anyway\n * get a leaked no-op attribute — no visual or behavioral disabling happens.\n *\n * `type` on the native tag: when rendering a native <button>, `type` defaults to\n * \"button\" (not the browser's implicit \"submit\"), so a Button inside a <form>\n * never submits implicitly — pass type=\"submit\" explicitly to submit.\n */\nexport type ButtonProps<E extends ElementType = \"button\"> = ButtonOwnProps<E> &\n Omit<ComponentPropsWithoutRef<E>, keyof ButtonOwnProps<E>>;\n\nexport function Button<E extends ElementType = \"button\">({\n as,\n variant = \"primary\",\n className,\n ...rest\n}: ButtonProps<E>) {\n const tag: ElementType = as ?? \"button\";\n const classes = cx(\"ps1ui-button\", `ps1ui-button--${variant}`, className);\n const props: Record<string, unknown> = { ...rest, className: classes };\n // `type=\"button\"` is a defensive default so a native <button> inside a <form> never\n // submits implicitly. It only makes sense on the native tag — <a>, <NextLink>, and\n // other host/component targets have no such attribute, and stamping one on them\n // would leak an invalid attribute through to the DOM. Explicit callers still win.\n if (tag === \"button\" && props.type === undefined) {\n props.type = \"button\";\n }\n return createElement(tag, props);\n}\n"],"mappings":";;;AAsCA,SAAgB,OAAyC,EACvD,IACA,UAAU,WACV,WACA,GAAG,QACc;CACjB,MAAM,MAAmB,MAAM;CAC/B,MAAM,UAAU,GAAG,gBAAgB,iBAAiB,WAAW,SAAS;CACxE,MAAM,QAAiC;EAAE,GAAG;EAAM,WAAW;CAAQ;CAKrE,IAAI,QAAQ,YAAY,MAAM,SAAS,KAAA,GACrC,MAAM,OAAO;CAEf,OAAO,cAAc,KAAK,KAAK;AACjC"}
@@ -3,7 +3,7 @@ import { FontWeight } from "../../utils/typography.mjs";
3
3
  import { CSSProperties, ComponentPropsWithoutRef } from "react";
4
4
  //#region src/components/Text/Text.d.ts
5
5
  type TextElement = "p" | "span" | "div" | "label" | "strong" | "em" | "small";
6
- type TextVariant = "body" | "muted" | "subtle" | "primary" | "accent";
6
+ type TextVariant = "body" | "muted" | "subtle" | "primary" | "accent" | "danger";
7
7
  type TextSize = "xs" | "sm" | "md" | "lg" | "xl";
8
8
  type TextWeight = FontWeight;
9
9
  type TextOwnProps<E extends TextElement> = {
@@ -1 +1 @@
1
- {"version":3,"file":"Text.mjs","names":[],"sources":["../../../src/components/Text/Text.tsx"],"sourcesContent":["import { createElement } from \"react\";\nimport type { ComponentPropsWithoutRef, CSSProperties } from \"react\";\nimport { cx } from \"../../utils/cx\";\nimport { resolveResponsive, type Responsive } from \"../../utils/responsive\";\nimport { fontSizeToVar, weightToValue, type FontWeight } from \"../../utils/typography\";\n\nexport type TextElement = \"p\" | \"span\" | \"div\" | \"label\" | \"strong\" | \"em\" | \"small\";\n\nexport type TextVariant = \"body\" | \"muted\" | \"subtle\" | \"primary\" | \"accent\";\nexport type TextSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n// TextWeight is a re-export of the shared FontWeight scale. Keeping a\n// component-local alias preserves the public type name (backwards compat)\n// while the underlying scale stays single-sourced in utils/typography.ts.\nexport type TextWeight = FontWeight;\n\ntype TextOwnProps<E extends TextElement> = {\n /** Element to render. */\n as?: E;\n /** Color variant. */\n variant?: TextVariant;\n /**\n * Font size on the type scale.\n * @default \"sm\"\n */\n size?: Responsive<TextSize>;\n /** Font weight. */\n weight?: Responsive<TextWeight>;\n /** Truncate overflowing text with an ellipsis instead of wrapping. */\n truncate?: boolean;\n};\n\n// Deliberate exception to the general \"use ComponentProps<'tag'>\" rule other components follow.\n// With the polymorphic `as` prop, TypeScript cannot narrow the `ref` prop's type against the\n// resolved element E — allowing `ref` here would silently accept mismatched ref types\n// (e.g. a ref typed for HTMLParagraphElement on `<Text as=\"span\" ref={…} />`). Keeping `ref`\n// out of the prop type entirely is safer than a misleading loose type; don't switch this\n// to ComponentProps for consistency with the other components.\nexport type TextProps<E extends TextElement = \"p\"> = TextOwnProps<E> &\n Omit<ComponentPropsWithoutRef<E>, keyof TextOwnProps<E>>;\n\n// Exhaustive over TextElement so adding a tag without classifying it fails to typecheck,\n// instead of silently leaving truncate's inline-block fix un-applied for that tag.\nconst INLINE_TEXT_ELEMENTS: Record<TextElement, boolean> = {\n p: false,\n div: false,\n span: true,\n label: true,\n strong: true,\n em: true,\n small: true,\n};\n\nexport function Text<E extends TextElement = \"p\">({\n as,\n variant = \"body\",\n size,\n weight,\n truncate = false,\n className,\n style,\n ...rest\n}: TextProps<E>) {\n const tag = as ?? \"p\";\n const sizeVars = resolveResponsive(size, \"--_text-size\", fontSizeToVar);\n const weightVars = resolveResponsive(weight, \"--_text-weight\", weightToValue);\n\n const classes = cx(\n \"ps1ui-text\",\n `ps1ui-text--${variant}`,\n truncate && \"ps1ui-text--truncate\",\n truncate && INLINE_TEXT_ELEMENTS[tag] && \"ps1ui-text--truncate-inline\",\n className,\n );\n\n // Caller style first, resolved vars last: `--_*` is the reserved internal\n // prefix and wins on purpose — responsive values are prop-controlled. Cast\n // because csstype has no index signature for `--*` keys (React 19 included).\n const mergedStyle: CSSProperties = {\n ...style,\n ...sizeVars,\n ...weightVars,\n } as CSSProperties;\n\n return createElement(tag, { ...rest, className: classes, style: mergedStyle });\n}\n"],"mappings":";;;;;AA0CA,MAAM,uBAAqD;CACzD,GAAG;CACH,KAAK;CACL,MAAM;CACN,OAAO;CACP,QAAQ;CACR,IAAI;CACJ,OAAO;AACT;AAEA,SAAgB,KAAkC,EAChD,IACA,UAAU,QACV,MACA,QACA,WAAW,OACX,WACA,OACA,GAAG,QACY;CACf,MAAM,MAAM,MAAM;CAClB,MAAM,WAAW,kBAAkB,MAAM,gBAAgB,aAAa;CACtE,MAAM,aAAa,kBAAkB,QAAQ,kBAAkB,aAAa;CAE5E,MAAM,UAAU,GACd,cACA,eAAe,WACf,YAAY,wBACZ,YAAY,qBAAqB,QAAQ,+BACzC,SACF;CAKA,MAAM,cAA6B;EACjC,GAAG;EACH,GAAG;EACH,GAAG;CACL;CAEA,OAAO,cAAc,KAAK;EAAE,GAAG;EAAM,WAAW;EAAS,OAAO;CAAY,CAAC;AAC/E"}
1
+ {"version":3,"file":"Text.mjs","names":[],"sources":["../../../src/components/Text/Text.tsx"],"sourcesContent":["import { createElement } from \"react\";\nimport type { ComponentPropsWithoutRef, CSSProperties } from \"react\";\nimport { cx } from \"../../utils/cx\";\nimport { resolveResponsive, type Responsive } from \"../../utils/responsive\";\nimport { fontSizeToVar, weightToValue, type FontWeight } from \"../../utils/typography\";\n\nexport type TextElement = \"p\" | \"span\" | \"div\" | \"label\" | \"strong\" | \"em\" | \"small\";\n\nexport type TextVariant = \"body\" | \"muted\" | \"subtle\" | \"primary\" | \"accent\" | \"danger\";\nexport type TextSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n// TextWeight is a re-export of the shared FontWeight scale. Keeping a\n// component-local alias preserves the public type name (backwards compat)\n// while the underlying scale stays single-sourced in utils/typography.ts.\nexport type TextWeight = FontWeight;\n\ntype TextOwnProps<E extends TextElement> = {\n /** Element to render. */\n as?: E;\n /** Color variant. */\n variant?: TextVariant;\n /**\n * Font size on the type scale.\n * @default \"sm\"\n */\n size?: Responsive<TextSize>;\n /** Font weight. */\n weight?: Responsive<TextWeight>;\n /** Truncate overflowing text with an ellipsis instead of wrapping. */\n truncate?: boolean;\n};\n\n// Deliberate exception to the general \"use ComponentProps<'tag'>\" rule other components follow.\n// With the polymorphic `as` prop, TypeScript cannot narrow the `ref` prop's type against the\n// resolved element E — allowing `ref` here would silently accept mismatched ref types\n// (e.g. a ref typed for HTMLParagraphElement on `<Text as=\"span\" ref={…} />`). Keeping `ref`\n// out of the prop type entirely is safer than a misleading loose type; don't switch this\n// to ComponentProps for consistency with the other components.\nexport type TextProps<E extends TextElement = \"p\"> = TextOwnProps<E> &\n Omit<ComponentPropsWithoutRef<E>, keyof TextOwnProps<E>>;\n\n// Exhaustive over TextElement so adding a tag without classifying it fails to typecheck,\n// instead of silently leaving truncate's inline-block fix un-applied for that tag.\nconst INLINE_TEXT_ELEMENTS: Record<TextElement, boolean> = {\n p: false,\n div: false,\n span: true,\n label: true,\n strong: true,\n em: true,\n small: true,\n};\n\nexport function Text<E extends TextElement = \"p\">({\n as,\n variant = \"body\",\n size,\n weight,\n truncate = false,\n className,\n style,\n ...rest\n}: TextProps<E>) {\n const tag = as ?? \"p\";\n const sizeVars = resolveResponsive(size, \"--_text-size\", fontSizeToVar);\n const weightVars = resolveResponsive(weight, \"--_text-weight\", weightToValue);\n\n const classes = cx(\n \"ps1ui-text\",\n `ps1ui-text--${variant}`,\n truncate && \"ps1ui-text--truncate\",\n truncate && INLINE_TEXT_ELEMENTS[tag] && \"ps1ui-text--truncate-inline\",\n className,\n );\n\n // Caller style first, resolved vars last: `--_*` is the reserved internal\n // prefix and wins on purpose — responsive values are prop-controlled. Cast\n // because csstype has no index signature for `--*` keys (React 19 included).\n const mergedStyle: CSSProperties = {\n ...style,\n ...sizeVars,\n ...weightVars,\n } as CSSProperties;\n\n return createElement(tag, { ...rest, className: classes, style: mergedStyle });\n}\n"],"mappings":";;;;;AA0CA,MAAM,uBAAqD;CACzD,GAAG;CACH,KAAK;CACL,MAAM;CACN,OAAO;CACP,QAAQ;CACR,IAAI;CACJ,OAAO;AACT;AAEA,SAAgB,KAAkC,EAChD,IACA,UAAU,QACV,MACA,QACA,WAAW,OACX,WACA,OACA,GAAG,QACY;CACf,MAAM,MAAM,MAAM;CAClB,MAAM,WAAW,kBAAkB,MAAM,gBAAgB,aAAa;CACtE,MAAM,aAAa,kBAAkB,QAAQ,kBAAkB,aAAa;CAE5E,MAAM,UAAU,GACd,cACA,eAAe,WACf,YAAY,wBACZ,YAAY,qBAAqB,QAAQ,+BACzC,SACF;CAKA,MAAM,cAA6B;EACjC,GAAG;EACH,GAAG;EACH,GAAG;CACL;CAEA,OAAO,cAAc,KAAK;EAAE,GAAG;EAAM,WAAW;EAAS,OAAO;CAAY,CAAC;AAC/E"}
@@ -0,0 +1,7 @@
1
+ import { ComponentProps } from "react";
2
+ //#region src/components/Textarea/Textarea.d.ts
3
+ type TextareaProps = ComponentProps<"textarea">;
4
+ declare function Textarea({ className, ...rest }: TextareaProps): import("react").JSX.Element;
5
+ //#endregion
6
+ export { Textarea, TextareaProps };
7
+ //# sourceMappingURL=Textarea.d.mts.map
@@ -0,0 +1,14 @@
1
+ import { cx } from "../../utils/cx.mjs";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region src/components/Textarea/Textarea.tsx
4
+ function Textarea({ className, ...rest }) {
5
+ const classes = cx("ps1ui-textarea", className);
6
+ return /* @__PURE__ */ jsx("textarea", {
7
+ ...rest,
8
+ className: classes
9
+ });
10
+ }
11
+ //#endregion
12
+ export { Textarea };
13
+
14
+ //# sourceMappingURL=Textarea.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Textarea.mjs","names":[],"sources":["../../../src/components/Textarea/Textarea.tsx"],"sourcesContent":["import type { ComponentProps } from \"react\";\nimport { cx } from \"../../utils/cx\";\n\nexport type TextareaProps = ComponentProps<\"textarea\">;\n\nexport function Textarea({ className, ...rest }: TextareaProps) {\n const classes = cx(\"ps1ui-textarea\", className);\n return <textarea {...rest} className={classes} />;\n}\n"],"mappings":";;;AAKA,SAAgB,SAAS,EAAE,WAAW,GAAG,QAAuB;CAC9D,MAAM,UAAU,GAAG,kBAAkB,SAAS;CAC9C,OAAO,oBAAC,YAAD;EAAU,GAAI;EAAM,WAAW;CAAU,CAAA;AAClD"}