@ultimat3/ui 22.15.0 → 24.0.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 (65) hide show
  1. package/CATALOG.md +45 -14
  2. package/CLAUDE.md +16 -52
  3. package/README.md +69 -13
  4. package/package.json +6 -5
  5. package/src/a11y.ts +24 -10
  6. package/src/catalog/parse-component.ts +85 -12
  7. package/src/catalog/render-catalog.ts +21 -1
  8. package/src/components/Avatar.module.scss +1 -1
  9. package/src/components/BarChart.module.scss +12 -1
  10. package/src/components/BarChart.tsx +41 -15
  11. package/src/components/Button.tsx +3 -8
  12. package/src/components/Card.module.scss +1 -1
  13. package/src/components/Checkbox.module.scss +4 -4
  14. package/src/components/Checkbox.tsx +6 -0
  15. package/src/components/CommandPalette.module.scss +8 -5
  16. package/src/components/DataTable.module.scss +2 -0
  17. package/src/components/DataTable.tsx +135 -71
  18. package/src/components/Dialog.module.scss +2 -2
  19. package/src/components/Dropzone.module.scss +6 -4
  20. package/src/components/Dropzone.tsx +9 -8
  21. package/src/components/FileInput.module.scss +4 -2
  22. package/src/components/FileInput.tsx +6 -2
  23. package/src/components/Form.module.scss +2 -2
  24. package/src/components/Input.module.scss +1 -1
  25. package/src/components/Link.tsx +64 -12
  26. package/src/components/Meter.tsx +3 -3
  27. package/src/components/Pagination.tsx +89 -37
  28. package/src/components/Popover.module.scss +17 -20
  29. package/src/components/Popover.tsx +12 -5
  30. package/src/components/Radio.module.scss +2 -2
  31. package/src/components/Select.module.scss +3 -3
  32. package/src/components/Skeleton.module.scss +1 -1
  33. package/src/components/Spinner.module.scss +3 -3
  34. package/src/components/Switch.module.scss +2 -2
  35. package/src/components/Table.module.scss +2 -2
  36. package/src/components/Tabs.module.scss +11 -5
  37. package/src/components/Textarea.tsx +7 -4
  38. package/src/components/ThemeToggle.module.scss +1 -1
  39. package/src/components/Toast.module.scss +2 -2
  40. package/src/components/Tooltip.module.scss +8 -3
  41. package/src/components/bar-chart-view.ts +35 -5
  42. package/src/components/button-classes.ts +25 -0
  43. package/src/components/file-input-view.ts +31 -12
  44. package/src/components/link-classes.ts +38 -0
  45. package/src/components/link-target.ts +34 -1
  46. package/src/components/meter-view.ts +13 -0
  47. package/src/components/qr-matrix.ts +16 -23
  48. package/src/components/qr-reader.ts +187 -0
  49. package/src/components/relative-time-view.ts +11 -4
  50. package/src/form/form-binding.ts +9 -8
  51. package/src/icons/build-icons.ts +96 -20
  52. package/src/index.ts +15 -9
  53. package/src/sass-probe.ts +55 -0
  54. package/src/theme/brand.ts +22 -3
  55. package/src/theme/theme.ts +35 -10
  56. package/src/toast/toast-state.ts +0 -0
  57. package/src/tokens/_index.scss +3 -1
  58. package/src/tokens/_mixins.scss +56 -8
  59. package/src/tokens/_motion.scss +4 -0
  60. package/src/tokens/_stroke.scss +20 -0
  61. package/src/tokens/_units.scss +47 -0
  62. package/src/tokens/reset.scss +4 -4
  63. package/src/tokens/theme.scss +2 -0
  64. package/src/tokens/tokens.ts +6 -0
  65. package/src/theme/inline-script.ts +0 -49
@@ -0,0 +1,47 @@
1
+ // Computed lengths: px → rem, and one fluid `clamp()` between two viewport widths. Functions only —
2
+ // nothing here emits CSS. The scale maps restate a constant; these compute a value from one.
3
+
4
+ @use 'sass:math';
5
+ @use 'sass:meta';
6
+ @use 'sass:string';
7
+
8
+ /// The one conversion both functions read, so `fluid(16px, 32px)` and `rem(16px)` cannot disagree
9
+ /// about what a px is — and so a refusal names the function the author actually called.
10
+ @function -to-rem($value, $root, $caller, $example) {
11
+ @if meta.type-of($value) != 'number' or not (math.is-unitless($value) or math.unit($value) == 'px' or math.unit($value) == 'rem') {
12
+ @error string.unquote('X_UI_INVALID_VALUE: #{$caller}() expected px, rem or a unitless px count — got `#{meta.inspect($value)}`. fix: pass a px length, e.g. #{$example}');
13
+ }
14
+ @if math.is-unitless($value) {
15
+ @return math.div($value * 1px, $root) * 1rem;
16
+ }
17
+ @if math.unit($value) == 'rem' {
18
+ @return $value;
19
+ }
20
+ @return math.div($value, $root) * 1rem;
21
+ }
22
+
23
+ /// px → rem against the root size. Lengths are rem so a layout holds at 200% text zoom; a unit
24
+ /// that cannot be converted is a build error rather than a size that is subtly off.
25
+ @function rem($value, $root: 16px) {
26
+ @return -to-rem($value, $root, 'rem', 'rem(24px)');
27
+ }
28
+
29
+ /// One `clamp()` with a real slope and intercept: `$min` at a viewport `$from` wide, `$max` at
30
+ /// `$to`, linear between and held outside. Type and space then scale with the viewport instead of
31
+ /// stepping at a breakpoint. `$min` above `$max` is a size that shrinks, and is legal.
32
+ @function fluid($min, $max, $from: 20rem, $to: 80rem) {
33
+ $min: -to-rem($min, 16px, 'fluid', 'fluid(16px, 32px)');
34
+ $max: -to-rem($max, 16px, 'fluid', 'fluid(16px, 32px)');
35
+ $from: -to-rem($from, 16px, 'fluid', 'fluid(16px, 32px)');
36
+ $to: -to-rem($to, 16px, 'fluid', 'fluid(16px, 32px)');
37
+
38
+ @if $from >= $to {
39
+ @error string.unquote('X_UI_INVALID_VALUE: fluid() needs $from below $to — got `#{$from}` and `#{$to}`. fix: pass the narrower viewport first, e.g. fluid(1rem, 2rem, 20rem, 80rem)');
40
+ }
41
+
42
+ $slope: math.div($max - $min, $to - $from);
43
+ $intercept: $min - $slope * $from;
44
+ // `clamp(MIN, VAL, MAX)` with MIN above MAX answers MIN at every width, so the bounds are
45
+ // ordered here rather than trusted to be.
46
+ @return clamp(math.min($min, $max), $intercept + $slope * 100vw, math.max($min, $max));
47
+ }
@@ -115,8 +115,8 @@
115
115
 
116
116
  // Themed keyboard focus for anything the component layer forgot.
117
117
  :where(:focus-visible) {
118
- outline: 2px solid t.role('accent');
119
- outline-offset: 2px;
118
+ outline: t.stroke(thick) solid t.role('accent');
119
+ outline-offset: t.stroke(thick);
120
120
  }
121
121
 
122
122
  :where(:focus:not(:focus-visible)) {
@@ -153,9 +153,9 @@ html:has(dialog:modal) {
153
153
  *,
154
154
  *::before,
155
155
  *::after {
156
- animation-duration: 0.01ms !important;
156
+ animation-duration: t.$still !important;
157
157
  animation-iteration-count: 1 !important;
158
- transition-duration: 0.01ms !important;
158
+ transition-duration: t.$still !important;
159
159
  scroll-behavior: auto !important;
160
160
  }
161
161
  }
@@ -7,6 +7,7 @@
7
7
  @use 'radius';
8
8
  @use 'shadow';
9
9
  @use 'space';
10
+ @use 'stroke';
10
11
  @use 'typography';
11
12
  @use 'z';
12
13
 
@@ -14,6 +15,7 @@
14
15
  @include shadow.emit;
15
16
  @include space.emit;
16
17
  @include radius.emit;
18
+ @include stroke.emit;
17
19
  @include typography.emit;
18
20
  @include motion.emit;
19
21
  @include z.emit;
@@ -119,6 +119,12 @@ export const radiusTokens = {
119
119
 
120
120
  export type RadiusName = keyof typeof radiusTokens;
121
121
 
122
+ export const strokeTokens = {
123
+ hairline: '1px',
124
+ thick: '2px',
125
+ heavy: '3px',
126
+ } as const;
127
+
122
128
  export const zTokens = {
123
129
  base: '0',
124
130
  raised: '10',
@@ -1,49 +0,0 @@
1
- // The blocking <head> snippet that kills the flash of wrong theme. Built from the
2
- // same constants as theme.ts so the two can never disagree. Server/build-only:
3
- // the hash is computed with Bun's hasher for the CSP header.
4
- //
5
- // DEPRECATED `As of 20.2.0`: the framework's boot inlines this script itself, from
6
- // `@ultimat3/render`'s `themeScript({ fallback })` with `theme.defaultMode` in `app.config.ts` as
7
- // the fallback, and admits it to the CSP — an app no longer writes either half. This module stays
8
- // exported for one major so an app that inlined it by hand keeps building; it is removed in 21.
9
-
10
- import { runtimeMissingError } from '../errors';
11
- import { THEME_ATTRIBUTE, THEME_MEDIA_QUERY, THEME_STORAGE_KEY } from './theme';
12
-
13
- /**
14
- * Minified on purpose — this runs before first paint and is inlined into every
15
- * document. Reads the explicit choice, falls back to the OS, and always stamps
16
- * `data-theme` so screenshots and Playwright runs are deterministic.
17
- */
18
- export const THEME_INLINE_SCRIPT =
19
- `try{var d=document.documentElement,` +
20
- `s=localStorage.getItem('${THEME_STORAGE_KEY}');` +
21
- `d.setAttribute('${THEME_ATTRIBUTE}',` +
22
- `s==='light'||s==='dark'?s:` +
23
- `matchMedia('${THEME_MEDIA_QUERY}').matches?'dark':'light')}catch(e){}`;
24
-
25
- /** The exact tag to inline. No attributes, so the CSP hash stays valid. */
26
- export function themeInlineScriptTag(): string {
27
- return `<script>${THEME_INLINE_SCRIPT}</script>`;
28
- }
29
-
30
- let cachedHash: string | null = null;
31
-
32
- /** `sha256-<base64>` for `script-src`. Stable for a given script text. */
33
- export function themeInlineScriptHash(): string {
34
- if (cachedHash !== null) return cachedHash;
35
- if (typeof Bun === 'undefined') {
36
- throw runtimeMissingError(
37
- 'Bun.CryptoHasher to hash the theme inline script',
38
- 'call themeInlineScriptHash() during build or SSR, never in browser code',
39
- );
40
- }
41
- const digest = new Bun.CryptoHasher('sha256').update(THEME_INLINE_SCRIPT).digest('base64');
42
- cachedHash = `sha256-${digest}`;
43
- return cachedHash;
44
- }
45
-
46
- /** Quoted CSP source expression, ready to concatenate into a `script-src`. */
47
- export function themeInlineScriptCspSource(): string {
48
- return `'${themeInlineScriptHash()}'`;
49
- }