tyrell-components 1.0.0-TC52 → 1.0.0-TC53

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 (105) hide show
  1. package/css/tyrell-theme.css +25 -2
  2. package/css/tyrell.css +4 -0
  3. package/dist/tyrell-colors-static.css +1 -316
  4. package/dist/tyrell-theme.css +1 -1443
  5. package/dist/tyrell.css +1 -1390
  6. package/dist/tyrell.js +1 -1
  7. package/lib/base/ty-component.js +196 -196
  8. package/lib/components/button.d.ts +1 -1
  9. package/lib/components/button.js +260 -259
  10. package/lib/components/calendar-month.d.ts +2 -2
  11. package/lib/components/calendar-month.js +426 -416
  12. package/lib/components/calendar-navigation.js +238 -238
  13. package/lib/components/calendar.js +605 -602
  14. package/lib/components/checkbox.d.ts +1 -1
  15. package/lib/components/checkbox.js +273 -272
  16. package/lib/components/copy.d.ts +1 -1
  17. package/lib/components/copy.js +279 -278
  18. package/lib/components/date-picker.d.ts +1 -1
  19. package/lib/components/date-picker.js +1103 -1108
  20. package/lib/components/file-upload.js +404 -404
  21. package/lib/components/icon.js +141 -141
  22. package/lib/components/input.d.ts +1 -1
  23. package/lib/components/input.js +593 -596
  24. package/lib/components/modal.js +319 -319
  25. package/lib/components/option.js +167 -167
  26. package/lib/components/popup.js +233 -233
  27. package/lib/components/radio.d.ts +2 -2
  28. package/lib/components/radio.js +407 -406
  29. package/lib/components/resize-observer.js +67 -67
  30. package/lib/components/scroll-container.js +294 -294
  31. package/lib/components/select.d.ts +1 -1
  32. package/lib/components/select.js +1524 -1532
  33. package/lib/components/selected-tags.js +184 -184
  34. package/lib/components/step.js +62 -62
  35. package/lib/components/switch.d.ts +1 -1
  36. package/lib/components/switch.js +214 -213
  37. package/lib/components/tab.js +62 -62
  38. package/lib/components/tabs.js +507 -507
  39. package/lib/components/tag.d.ts +1 -1
  40. package/lib/components/tag.js +236 -235
  41. package/lib/components/textarea.d.ts +1 -1
  42. package/lib/components/textarea.js +457 -456
  43. package/lib/components/tooltip.js +283 -283
  44. package/lib/components/wizard.js +384 -384
  45. package/lib/icons/heroicons/micro.js +392 -392
  46. package/lib/icons/heroicons/mini.js +382 -382
  47. package/lib/icons/heroicons/outline.js +337 -337
  48. package/lib/icons/heroicons/solid.js +407 -407
  49. package/lib/icons/lucide.js +22843 -22843
  50. package/lib/icons/material/filled.js +9653 -9653
  51. package/lib/icons/material/outlined.js +10271 -10271
  52. package/lib/icons/material/round.js +10495 -10495
  53. package/lib/icons/material/sharp.js +9905 -9905
  54. package/lib/icons/material/two-tone.js +12792 -12792
  55. package/lib/index.js +21 -21
  56. package/lib/styles/button.js +176 -288
  57. package/lib/styles/calendar-month.js +123 -183
  58. package/lib/styles/calendar-navigation.js +60 -79
  59. package/lib/styles/calendar.js +8 -9
  60. package/lib/styles/checkbox.js +45 -72
  61. package/lib/styles/copy.js +67 -91
  62. package/lib/styles/custom-scrollbar.js +80 -100
  63. package/lib/styles/date-picker.js +261 -331
  64. package/lib/styles/field-size.d.ts +6 -0
  65. package/lib/styles/field-size.js +91 -0
  66. package/lib/styles/file-upload.js +139 -169
  67. package/lib/styles/icon.js +92 -156
  68. package/lib/styles/input.js +125 -241
  69. package/lib/styles/modal.js +67 -102
  70. package/lib/styles/option.d.ts +1 -1
  71. package/lib/styles/option.js +47 -67
  72. package/lib/styles/popup.js +34 -65
  73. package/lib/styles/radio.js +69 -96
  74. package/lib/styles/resize-observer.js +2 -5
  75. package/lib/styles/scroll-container.js +100 -130
  76. package/lib/styles/select-base.js +525 -778
  77. package/lib/styles/select.js +140 -228
  78. package/lib/styles/step.js +22 -29
  79. package/lib/styles/switch.js +34 -55
  80. package/lib/styles/tab.js +22 -29
  81. package/lib/styles/tabs.js +171 -252
  82. package/lib/styles/tag.js +135 -188
  83. package/lib/styles/textarea.d.ts +1 -1
  84. package/lib/styles/textarea.js +205 -271
  85. package/lib/styles/tooltip.js +3 -3
  86. package/lib/styles/wizard.js +170 -226
  87. package/lib/types/common.d.ts +2 -0
  88. package/lib/types/common.js +6 -5
  89. package/lib/utils/calendar-utils.js +100 -100
  90. package/lib/utils/custom-scrollbar.js +272 -272
  91. package/lib/utils/flavor-sheet.js +14 -14
  92. package/lib/utils/icon-registry.js +127 -127
  93. package/lib/utils/loader-registry.js +3 -3
  94. package/lib/utils/locale.js +34 -34
  95. package/lib/utils/mobile.js +1 -1
  96. package/lib/utils/number-format.js +54 -54
  97. package/lib/utils/positioning.js +212 -212
  98. package/lib/utils/property-capture.js +36 -36
  99. package/lib/utils/property-manager.js +125 -125
  100. package/lib/utils/resize-observer.js +23 -23
  101. package/lib/utils/scroll-lock.js +57 -57
  102. package/lib/utils/styles.js +17 -17
  103. package/lib/version.d.ts +1 -1
  104. package/lib/version.js +1 -1
  105. package/package.json +4 -4
package/lib/index.js CHANGED
@@ -35,25 +35,25 @@ import { registerIcons, getIcon, hasIcon, getIconNames, getCacheInfo, clearIcons
35
35
  export { registerIcons, getIcon, hasIcon, getIconNames, getCacheInfo, clearIcons, getCachedIcon };
36
36
  import { getSize as getResizeSize, onResize as subscribeResize, getAllSizes } from './utils/resize-observer.js';
37
37
  if (typeof window !== 'undefined') {
38
- window.tyVersion = VERSION;
39
- console.log(`%c[tyrell-components]%c v${VERSION}`, 'color:#06b6d4;font-weight:600', 'color:inherit;font-weight:400');
40
- window.tyIcons = {
41
- register: (icons) => {
42
- const count = Object.keys(icons).length;
43
- registerIcons(icons);
44
- setTimeout(() => {
45
- console.log(`✅ Registered ${count} icons (cached for instant reload)`);
46
- }, 0);
47
- },
48
- get: (name) => getIcon(name),
49
- has: (name) => hasIcon(name),
50
- list: () => getIconNames(),
51
- cacheInfo: () => getCacheInfo(),
52
- clearCache: () => clearIcons()
53
- };
54
- window.tyResizeObserver = {
55
- getSize: (id) => getResizeSize(id),
56
- onResize: (id, callback) => subscribeResize(id, callback),
57
- get sizes() { return getAllSizes(); }
58
- };
38
+ window.tyVersion = VERSION;
39
+ console.log(`%c[tyrell-components]%c v${VERSION}`, 'color:#06b6d4;font-weight:600', 'color:inherit;font-weight:400');
40
+ window.tyIcons = {
41
+ register: (icons) => {
42
+ const count = Object.keys(icons).length;
43
+ registerIcons(icons);
44
+ setTimeout(() => {
45
+ console.log(`✅ Registered ${count} icons (cached for instant reload)`);
46
+ }, 0);
47
+ },
48
+ get: (name) => getIcon(name),
49
+ has: (name) => hasIcon(name),
50
+ list: () => getIconNames(),
51
+ cacheInfo: () => getCacheInfo(),
52
+ clearCache: () => clearIcons()
53
+ };
54
+ window.tyResizeObserver = {
55
+ getSize: (id) => getResizeSize(id),
56
+ onResize: (id, callback) => subscribeResize(id, callback),
57
+ get sizes() { return getAllSizes(); }
58
+ };
59
59
  }
@@ -1,16 +1,16 @@
1
1
  import { FLAVORS } from '../types/common.js';
2
2
  const tokenRef = (prefix, f, suffix, fb) => fb
3
- ? `var(${prefix}-${f}${suffix}, var(${prefix}-${fb}${suffix}))`
4
- : `var(${prefix}-${f}${suffix})`;
3
+ ? `var(${prefix}-${f}${suffix}, var(${prefix}-${fb}${suffix}))`
4
+ : `var(${prefix}-${f}${suffix})`;
5
5
  const nudged = (fillExpr, offsetDial) => `oklch(from ${fillExpr} calc(l + var(--ty-${offsetDial})) c h)`;
6
6
  const nudgedMid = (fillExpr, offsetDial) => `oklch(from ${fillExpr} calc(l + sign(0.5 - l) * abs(var(--ty-${offsetDial}))) c h)`;
7
7
  const solidFlavor = (f, fb) => {
8
- const solid = (suffix) => tokenRef('--ty-solid', f, suffix, fb);
9
- const borderL = f === 'neutral'
10
- ? ' --_border-l: var(--ty-button-border-l-neutral, var(--ty-button-border-l, -0.15));'
11
- : ' --_border-l: var(--_muted-border-l, var(--ty-button-border-l, -0.15));';
12
- const plusNudge = f === 'neutral' ? nudgedMid : nudged;
13
- return `
8
+ const solid = (suffix) => tokenRef('--ty-solid', f, suffix, fb);
9
+ const borderL = f === 'neutral'
10
+ ? ' --_border-l: var(--ty-button-border-l-neutral, var(--ty-button-border-l, -0.15));'
11
+ : ' --_border-l: var(--_muted-border-l, var(--ty-button-border-l, -0.15));';
12
+ const plusNudge = f === 'neutral' ? nudgedMid : nudged;
13
+ return `
14
14
  button.solid.${f} { --_ring: ${tokenRef('--ty-color', f, '', fb)}; --_solid-bg: var(--ty-button-bg, var(--_muted-solid-bg, ${solid('')})); --_solid-rest: var(--ty-button-bg, var(--_muted-solid-bg, ${solid('')}));${borderL} color: var(--ty-button-color, var(--_muted-solid-fg, ${solid('-fg')})); }
15
15
  button.solid.${f}.tone-plus { --_solid-bg: var(--ty-button-bg, var(--_muted-solid-bg, ${solid('-strong')})); --_solid-rest: var(--ty-button-bg, var(--_muted-solid-bg, ${solid('-strong')})); color: var(--ty-button-color, var(--_muted-solid-fg, ${solid('-strong-fg')})); }
16
16
  button.solid.${f}.tone-minus { --_solid-bg: var(--ty-button-bg, var(--_muted-solid-bg, ${solid('-soft')})); --_solid-rest: var(--ty-button-bg, var(--_muted-solid-bg, ${solid('-soft')})); color: var(--ty-button-color, var(--_muted-solid-fg, ${solid('-soft-fg')})); }
@@ -23,416 +23,304 @@ button.solid.${f}.tone-minus:active:not(:disabled) { --_solid-bg: ${nudged(solid
23
23
  `;
24
24
  };
25
25
  const outlinedFlavor = (f, fb) => {
26
- const color = (suffix) => tokenRef('--ty-color', f, suffix, fb);
27
- const textInk = (suffix) => (f === 'neutral' && suffix === '-strong' ? `var(--ty-solid-neutral${suffix})` : color(suffix));
28
- return `
26
+ const color = (suffix) => tokenRef('--ty-color', f, suffix, fb);
27
+ const textInk = (suffix) => (f === 'neutral' && suffix === '-strong' ? `var(--ty-solid-neutral${suffix})` : color(suffix));
28
+ return `
29
29
  button.outlined.${f} {
30
- color: var(--ty-button-color, var(--_muted-outlined-line, ${textInk('')}));
31
- border-color: var(--ty-button-border, var(--_muted-outlined-line, ${textInk('')}));
30
+ color: var(--ty-button-color, var(--_muted-outlined-line, ${textInk('')}));
31
+ border-color: var(--ty-button-border, var(--_muted-outlined-line, ${textInk('')}));
32
32
  }
33
33
  button.outlined.${f}.tone-plus {
34
- color: var(--ty-button-color, var(--_muted-outlined-line, ${textInk('-strong')}));
35
- border-color: var(--ty-button-border, var(--_muted-outlined-line, ${textInk('-strong')}));
34
+ color: var(--ty-button-color, var(--_muted-outlined-line, ${textInk('-strong')}));
35
+ border-color: var(--ty-button-border, var(--_muted-outlined-line, ${textInk('-strong')}));
36
36
  }
37
37
  button.outlined.${f}.tone-minus {
38
- color: var(--ty-button-color, var(--_muted-outlined-line, ${textInk('-soft')}));
39
- border-color: var(--ty-button-border, var(--_muted-outlined-line, ${textInk('-soft')}));
38
+ color: var(--ty-button-color, var(--_muted-outlined-line, ${textInk('-soft')}));
39
+ border-color: var(--ty-button-border, var(--_muted-outlined-line, ${textInk('-soft')}));
40
40
  }
41
41
  button.outlined.${f}:hover:not(:disabled) {
42
- background: var(--ty-button-bg-hover, ${tokenRef('--ty-bg', f, '-soft', fb)});
43
- }
44
- /* Rest is whatever tone you picked; hover/active/focus always escalates to
45
- the SAME peak — most-emphasized ("+") color, regardless of starting
46
- tone. tone-plus is already there at rest, so this is a no-op for it.
47
- :hover only, :not(.muted) — muted's hover-reveal is a DIFFERENT contract
48
- (reveal the plain button's RESTING color, tested directly against it);
49
- this rule would outrank that fallback and show strong instead.
50
- :active/:focus-visible apply to muted too (no exclusion) — muted's own
51
- reveal-on-press mechanism has much lower specificity than this rule, so
52
- it simply wins outright, landing muted and plain on the same escalated
53
- color when both are pressed — which is what muted's OWN test expects. */
42
+ background: var(--ty-button-bg-hover, ${tokenRef('--ty-bg', f, '-soft', fb)});
43
+ }
54
44
  button.outlined.${f}:not(.muted):hover:not(:disabled),
55
45
  button.outlined.${f}:active:not(:disabled),
56
46
  button.outlined.${f}:focus-visible {
57
- color: var(--ty-button-color, ${textInk('-strong')});
58
- border-color: var(--ty-button-border, ${textInk('-strong')});
47
+ color: var(--ty-button-color, ${textInk('-strong')});
48
+ border-color: var(--ty-button-border, ${textInk('-strong')});
59
49
  }
60
50
  `;
61
51
  };
62
52
  const ghostFlavor = (f, fb) => {
63
- const color = (suffix) => tokenRef('--ty-color', f, suffix, fb);
64
- const ink = (suffix) => (f === 'neutral' && suffix === '-strong' ? `var(--ty-solid-neutral${suffix})` : color(suffix));
65
- return `
53
+ const color = (suffix) => tokenRef('--ty-color', f, suffix, fb);
54
+ const ink = (suffix) => (f === 'neutral' && suffix === '-strong' ? `var(--ty-solid-neutral${suffix})` : color(suffix));
55
+ return `
66
56
  button.ghost.${f} { color: var(--ty-button-color, var(--_muted-ghost-fg, ${ink('')})); }
67
57
  button.ghost.${f}.tone-plus { color: var(--ty-button-color, var(--_muted-ghost-fg, ${ink('-strong')})); }
68
58
  button.ghost.${f}.tone-minus { color: var(--ty-button-color, var(--_muted-ghost-fg, ${ink('-soft')})); }
69
59
  button.ghost.${f}:hover:not(:disabled) {
70
- background: var(--ty-button-bg-hover, ${tokenRef('--ty-bg', f, '-soft', fb)});
60
+ background: var(--ty-button-bg-hover, ${tokenRef('--ty-bg', f, '-soft', fb)});
71
61
  }
72
- /* See outlinedFlavor above — same escalation-to-peak, same :hover-only
73
- :not(.muted) split, text only. */
74
62
  button.ghost.${f}:not(.muted):hover:not(:disabled),
75
63
  button.ghost.${f}:active:not(:disabled),
76
64
  button.ghost.${f}:focus-visible {
77
- color: var(--ty-button-color, ${ink('-strong')});
65
+ color: var(--ty-button-color, ${ink('-strong')});
78
66
  }
79
67
  `;
80
68
  };
81
69
  export const buttonCustomFlavorCss = (base) => solidFlavor(base, 'neutral') + outlinedFlavor(base, 'neutral') + ghostFlavor(base, 'neutral');
82
70
  export const buttonStyles = `
83
71
  :host {
84
- display: inline-block;
85
- font-family: inherit;
72
+ display: inline-block;
73
+ font-family: inherit;
86
74
  }
87
-
88
75
  :host([wide]) {
89
- display: flex;
90
- flex-grow: 1;
76
+ display: flex;
77
+ flex-grow: 1;
91
78
  }
92
-
93
79
  button {
94
- /* Native form controls do NOT inherit font from the shadow host — the UA
95
- stylesheet sets one on them, which beats inheritance. Must be explicit
96
- here or the button silently falls back to the system font. */
97
- font-family: inherit;
98
- position: relative;
99
- display: flex;
100
- align-items: center;
101
- justify-content: center;
102
- gap: var(--ty-spacing-1);
103
- font-size: var(--ty-font-xs);
104
- line-height: var(--ty-leading-xs);
105
- letter-spacing: var(--ty-tracking-xs);
106
- /* Mode-flipped weight dial (tyrell.css): medium in light, normal in dark —
107
- light-on-dark text blooms, a fixed semibold read heavy there. Override
108
- with --ty-weight-action at any scope. */
109
- font-weight: var(--ty-weight-action, var(--ty-font-medium));
110
- white-space: nowrap;
111
- cursor: pointer;
112
- user-select: none;
113
- transition: var(--ty-local-transition, background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease);
114
- background: transparent;
115
- color: var(--ty-color-neutral);
116
- border: 1px solid transparent;
117
- }
118
-
80
+ font-family: inherit;
81
+ position: relative;
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ gap: var(--ty-spacing-1);
86
+ font-size: var(--ty-font-xs);
87
+ line-height: var(--ty-leading-xs);
88
+ letter-spacing: var(--ty-tracking-xs);
89
+ font-weight: var(--ty-weight-action, var(--ty-font-medium));
90
+ white-space: nowrap;
91
+ cursor: pointer;
92
+ user-select: none;
93
+ transition: var(--ty-local-transition, background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease);
94
+ background: transparent;
95
+ color: var(--ty-color-neutral);
96
+ border: 1px solid transparent;
97
+ }
119
98
  button:not(.action) {
120
- min-width: 4rem;
99
+ min-width: 4rem;
121
100
  }
122
-
123
101
  button:not(.pill) {
124
- border-radius: var(--ty-radius-md);
102
+ border-radius: var(--ty-radius-md);
125
103
  }
126
-
127
104
  button.xs:not(.pill) { border-radius: var(--ty-button-radius-xs, var(--ty-radius-md)); }
128
105
  button.sm:not(.pill) { border-radius: var(--ty-button-radius-sm, var(--ty-radius-md)); }
129
106
  button.md:not(.pill) { border-radius: var(--ty-button-radius-md, var(--ty-radius-md)); }
130
107
  button.lg:not(.pill) { border-radius: var(--ty-button-radius-lg, var(--ty-radius-md)); }
131
108
  button.xl:not(.pill) { border-radius: var(--ty-button-radius-xl, var(--ty-radius-md)); }
132
-
133
109
  button:focus-visible {
134
- outline: none;
110
+ outline: none;
135
111
  }
136
-
137
112
  button:disabled {
138
- cursor: not-allowed;
139
- opacity: 0.6;
113
+ cursor: not-allowed;
114
+ opacity: 0.6;
140
115
  }
141
-
142
- /* Spinner overlays the button center; original content kept in flow but
143
- hidden via visibility so width/height are preserved (no layout shift). */
144
116
  .loader-icon {
145
- display: none;
146
- align-items: center;
147
- justify-content: center;
148
- flex-shrink: 0;
149
- width: 1em;
150
- height: 1em;
151
- color: currentColor;
117
+ display: none;
118
+ align-items: center;
119
+ justify-content: center;
120
+ flex-shrink: 0;
121
+ width: 1em;
122
+ height: 1em;
123
+ color: currentColor;
152
124
  }
153
125
  .loader-icon svg {
154
- width: 100%;
155
- height: 100%;
126
+ width: 100%;
127
+ height: 100%;
156
128
  }
157
129
  button.loading {
158
- cursor: wait;
130
+ cursor: wait;
159
131
  }
160
132
  button.loading > *:not(.loader-icon) {
161
- visibility: hidden;
133
+ visibility: hidden;
162
134
  }
163
135
  button.loading > .loader-icon {
164
- display: inline-flex;
165
- position: absolute;
166
- top: 50%;
167
- left: 50%;
168
- transform: translate(-50%, -50%);
169
- animation: ty-button-spin 0.7s linear infinite;
136
+ display: inline-flex;
137
+ position: absolute;
138
+ top: 50%;
139
+ left: 50%;
140
+ transform: translate(-50%, -50%);
141
+ animation: ty-button-spin 0.7s linear infinite;
170
142
  }
171
143
  @keyframes ty-button-spin {
172
- to { transform: translate(-50%, -50%) rotate(360deg); }
144
+ to { transform: translate(-50%, -50%) rotate(360deg); }
173
145
  }
174
146
  @media (prefers-reduced-motion: reduce) {
175
- button.loading > .loader-icon {
176
- animation-duration: 1.6s;
177
- }
147
+ button.loading > .loader-icon {
148
+ animation-duration: 1.6s;
149
+ }
178
150
  }
179
-
180
151
  ::slotted(*) {
181
- display: inline-flex;
182
- align-items: center;
183
- justify-content: center;
152
+ display: inline-flex;
153
+ align-items: center;
154
+ justify-content: center;
184
155
  }
185
-
186
156
  ::slotted(ty-icon) {
187
- flex-shrink: 0;
157
+ flex-shrink: 0;
188
158
  }
189
-
190
159
  :host([wide]) button {
191
- flex-grow: 1;
192
- }
193
-
194
- /* Buttons run a 4px ladder (24-40px). Shared scale with fields
195
- (--ty-size-sm/md/lg — see tyrell.css): field sm = button md (32px),
196
- field md = button lg (36px), field lg = button xl (40px) — a button
197
- and a field of the paired size sit flush, same height, in a row. */
198
-
160
+ flex-grow: 1;
161
+ }
199
162
  button.xs {
200
- padding: 0 var(--ty-spacing-2);
201
- font-size: var(--ty-font-xs);
202
- line-height: var(--ty-leading-xs);
203
- letter-spacing: var(--ty-tracking-xs);
204
- gap: var(--ty-spacing-1);
205
- height: 1.5rem; /* 24px */
206
- }
207
-
163
+ padding: 0 var(--ty-spacing-2);
164
+ font-size: var(--ty-font-xs);
165
+ line-height: var(--ty-leading-xs);
166
+ letter-spacing: var(--ty-tracking-xs);
167
+ gap: var(--ty-spacing-1);
168
+ height: 1.5rem;
169
+ }
208
170
  button.sm {
209
- padding: 0 var(--ty-spacing-2);
210
- font-size: var(--ty-font-xs);
211
- line-height: var(--ty-leading-xs);
212
- letter-spacing: var(--ty-tracking-xs);
213
- gap: var(--ty-spacing-1);
214
- height: 1.75rem; /* 28px */
215
- }
216
-
171
+ padding: 0 var(--ty-spacing-2);
172
+ font-size: var(--ty-font-xs);
173
+ line-height: var(--ty-leading-xs);
174
+ letter-spacing: var(--ty-tracking-xs);
175
+ gap: var(--ty-spacing-1);
176
+ height: 1.75rem;
177
+ }
217
178
  button.md {
218
- padding: 0.375rem var(--ty-spacing-3);
219
- font-size: var(--ty-font-sm);
220
- line-height: var(--ty-leading-sm);
221
- letter-spacing: var(--ty-tracking-sm);
222
- gap: var(--ty-spacing-2);
223
- height: 2rem; /* 32px — matches field sm */
224
- }
225
-
179
+ padding: 0.375rem var(--ty-spacing-3);
180
+ font-size: var(--ty-font-sm);
181
+ line-height: var(--ty-leading-sm);
182
+ letter-spacing: var(--ty-tracking-sm);
183
+ gap: var(--ty-spacing-2);
184
+ height: 2rem;
185
+ }
226
186
  button.lg {
227
- padding: 0.375rem var(--ty-spacing-4);
228
- font-size: var(--ty-font-sm);
229
- line-height: var(--ty-leading-sm);
230
- letter-spacing: var(--ty-tracking-sm);
231
- gap: var(--ty-spacing-2);
232
- height: 2.25rem; /* 36px — matches field md */
233
- }
234
-
187
+ padding: 0.375rem var(--ty-spacing-4);
188
+ font-size: var(--ty-font-sm);
189
+ line-height: var(--ty-leading-sm);
190
+ letter-spacing: var(--ty-tracking-sm);
191
+ gap: var(--ty-spacing-2);
192
+ height: 2.25rem;
193
+ }
235
194
  button.xl {
236
- padding: var(--ty-spacing-2) var(--ty-spacing-6);
237
- font-size: var(--ty-font-base);
238
- line-height: var(--ty-leading-base);
239
- letter-spacing: var(--ty-tracking-base);
240
- gap: var(--ty-spacing-2);
241
- height: 2.5rem; /* 40px — matches field lg */
242
- }
243
-
244
- /* Action = icon-only square */
195
+ padding: var(--ty-spacing-2) var(--ty-spacing-6);
196
+ font-size: var(--ty-font-base);
197
+ line-height: var(--ty-leading-base);
198
+ letter-spacing: var(--ty-tracking-base);
199
+ gap: var(--ty-spacing-2);
200
+ height: 2.5rem;
201
+ }
245
202
  button.action {
246
- gap: 0px !important;
247
- height: 2rem;
248
- width: 2rem;
249
- padding: 0px !important;
203
+ gap: 0px !important;
204
+ height: 2rem;
205
+ width: 2rem;
206
+ padding: 0px !important;
250
207
  }
251
-
252
208
  button.action ::slotted(ty-icon) {
253
- height: 1rem;
254
- width: 1rem;
209
+ height: 1rem;
210
+ width: 1rem;
255
211
  }
256
-
257
212
  button.action.xs { height: 1.375rem; width: 1.375rem; }
258
213
  button.action.xs ::slotted(ty-icon) { height: 0.75rem; width: 0.75rem; }
259
-
260
214
  button.action.sm { height: 1.5rem; width: 1.5rem; }
261
215
  button.action.sm ::slotted(ty-icon) { height: 0.875rem; width: 0.875rem; }
262
-
263
216
  button.action.lg { height: 2.25rem; width: 2.25rem; }
264
217
  button.action.lg ::slotted(ty-icon) { height: 1.125rem; width: 1.125rem; }
265
-
266
218
  button.action.xl { height: 2.5rem; width: 2.5rem; }
267
219
  button.action.xl ::slotted(ty-icon) { height: 1.25rem; width: 1.25rem; }
268
-
269
- /* MUTED: show the neutral tokens at rest instead of the flavor color; reveal the
270
- real flavor on interaction. Implemented as a fallback tier the flavor
271
- rules above already read (--_muted-*), which sits behind the public
272
- --ty-button-{bg,color,border} override slots and ahead of the flavor's
273
- own token — so a custom brand color still wins, muted or not. Unsetting
274
- --_muted-* on interaction falls the var() chain through to whatever's
275
- next (the user's override, or the real flavor color).
276
- Hover reveal is gated to real pointers (touch has no hover); :active /
277
- :focus-visible cover the tap case so touch still gets the color on press. */
278
-
279
- /* --_muted-solid-* reads the SOLID neutral ink tokens — a fill's L doesn't
280
- need page contrast. --_muted-outlined-line / --_muted-ghost-fg are bare
281
- text/border and DO — they stay on the regular text ladder (it already
282
- inverts correctly per mode), except tone-plus, which is legible AND
283
- loud on the ink dial (16.5:1 dark, matches solid's "+" identity). Base/
284
- soft on ink measured 1.01/1.20 dark contrast against the canvas —
285
- invisible; WCAG minimum is 4.5. */
286
220
  button.muted {
287
- --_muted-solid-bg: var(--ty-solid-neutral);
288
- --_muted-solid-fg: var(--ty-solid-neutral-fg);
289
- --_muted-outlined-line: var(--ty-color-neutral);
290
- --_muted-ghost-fg: var(--ty-color-neutral);
291
- --_muted-border-l: var(--ty-button-border-l-neutral, var(--ty-button-border-l, -0.15));
221
+ --_muted-solid-bg: var(--ty-solid-neutral);
222
+ --_muted-solid-fg: var(--ty-solid-neutral-fg);
223
+ --_muted-outlined-line: var(--ty-color-neutral);
224
+ --_muted-ghost-fg: var(--ty-color-neutral);
225
+ --_muted-border-l: var(--ty-button-border-l-neutral, var(--ty-button-border-l, -0.15));
292
226
  }
293
227
  button.muted.tone-plus {
294
- --_muted-solid-bg: var(--ty-solid-neutral-strong);
295
- /* fg must track the tone's fill: neutral+ is inverted ink in dark mode
296
- (white fill), so inheriting base neutral's white fg would be
297
- white-on-white. */
298
- --_muted-solid-fg: var(--ty-solid-neutral-strong-fg);
299
- --_muted-outlined-line: var(--ty-solid-neutral-strong);
300
- --_muted-ghost-fg: var(--ty-solid-neutral-strong);
228
+ --_muted-solid-bg: var(--ty-solid-neutral-strong);
229
+ --_muted-solid-fg: var(--ty-solid-neutral-strong-fg);
230
+ --_muted-outlined-line: var(--ty-solid-neutral-strong);
231
+ --_muted-ghost-fg: var(--ty-solid-neutral-strong);
301
232
  }
302
233
  button.muted.tone-minus {
303
- --_muted-solid-bg: var(--ty-solid-neutral-soft);
304
- --_muted-solid-fg: var(--ty-solid-neutral-soft-fg);
305
- --_muted-outlined-line: var(--ty-color-neutral-soft);
306
- --_muted-ghost-fg: var(--ty-color-neutral-soft);
234
+ --_muted-solid-bg: var(--ty-solid-neutral-soft);
235
+ --_muted-solid-fg: var(--ty-solid-neutral-soft-fg);
236
+ --_muted-outlined-line: var(--ty-color-neutral-soft);
237
+ --_muted-ghost-fg: var(--ty-color-neutral-soft);
307
238
  }
308
239
  @media (hover: hover) and (pointer: fine) {
309
- button.muted:hover:not(:disabled) {
310
- --_muted-solid-bg: unset;
311
- --_muted-solid-fg: unset;
312
- --_muted-outlined-line: unset;
313
- --_muted-ghost-fg: unset;
314
- --_muted-border-l: unset;
315
- }
240
+ button.muted:hover:not(:disabled) {
241
+ --_muted-solid-bg: unset;
242
+ --_muted-solid-fg: unset;
243
+ --_muted-outlined-line: unset;
244
+ --_muted-ghost-fg: unset;
245
+ --_muted-border-l: unset;
246
+ }
316
247
  }
317
248
  button.muted:active:not(:disabled),
318
249
  button.muted:focus-visible:not(:disabled) {
319
- --_muted-solid-bg: unset;
320
- --_muted-solid-fg: unset;
321
- --_muted-outlined-line: unset;
322
- --_muted-ghost-fg: unset;
323
- --_muted-border-l: unset;
250
+ --_muted-solid-bg: unset;
251
+ --_muted-solid-fg: unset;
252
+ --_muted-outlined-line: unset;
253
+ --_muted-ghost-fg: unset;
254
+ --_muted-border-l: unset;
324
255
  }
325
-
326
256
  button.pill {
327
- border-radius: 9999px;
328
- padding-left: 1.25em;
329
- padding-right: 1.25em;
257
+ border-radius: 9999px;
258
+ padding-left: 1.25em;
259
+ padding-right: 1.25em;
330
260
  }
331
-
332
261
  button.pill.xs { padding-left: 1em; padding-right: 1em; }
333
262
  button.pill.sm { padding-left: 1.125em; padding-right: 1.125em; }
334
263
  button.pill.lg { padding-left: 1.5em; padding-right: 1.5em; }
335
264
  button.pill.xl { padding-left: 1.75em; padding-right: 1.75em; }
336
-
337
265
  button.pill:has(ty-icon:only-child),
338
266
  button.pill:has(slot[name="start"]:only-child),
339
267
  button.pill:has(slot[name="end"]:only-child) {
340
- padding: 0;
341
- aspect-ratio: 1;
342
- min-width: var(--ty-size-md);
268
+ padding: 0;
269
+ aspect-ratio: 1;
270
+ min-width: var(--ty-size-md);
343
271
  }
344
-
345
272
  button.pill.xs:has(ty-icon:only-child) { min-width: 1.375rem; min-height: 1.375rem; }
346
273
  button.pill.sm:has(ty-icon:only-child) { min-width: 1.5rem; min-height: 1.5rem; }
347
274
  button.pill.md:has(ty-icon:only-child) { min-width: 2rem; min-height: 2rem; }
348
275
  button.pill.lg:has(ty-icon:only-child) { min-width: 2.25rem; min-height: 2.25rem; }
349
276
  button.pill.xl:has(ty-icon:only-child) { min-width: 2.5rem; min-height: 2.5rem; }
350
-
351
- /* SOLID — flat: one variable per segment. No color math here; all the
352
- --ty-solid-{flavor}-{hover,active,strong,soft} tokens are DERIVED in
353
- tyrell-theme.css (OKLCH) or set literally in tyrell.css. Override any
354
- single token to recolor that one segment. Bare .solid = custom-flavor
355
- fallback, themable via --ty-button-{bg,bg-hover,color}. */
356
-
357
277
  button.solid {
358
- --_ring: var(--ty-color-neutral);
359
- --_solid-bg: var(--ty-button-bg, var(--ty-solid-neutral));
360
- --_solid-rest: var(--ty-button-bg, var(--ty-solid-neutral));
361
- /* Bare .solid is the flavorless default = neutral ink, so it takes the
362
- neutral border offset; colored flavor rules reset this to the plain
363
- dial. */
364
- --_border-l: var(--ty-button-border-l-neutral, var(--ty-button-border-l, -0.15));
365
- background: var(--_solid-bg);
366
- color: var(--ty-button-color, var(--ty-solid-neutral-fg));
367
- /* Depth chrome — border + drop shadow, both scaled by --ty-button-depth.
368
- (A top-edge sheen — inset 0 1px 0 <lightened fill> — used to live here
369
- too; dropped, it read as a dizzying bright line rather than a subtle
370
- highlight.) depth 0 = border matches fill exactly and the shadow goes
371
- transparent — pixel-flat, but the 1px border stays in the layout so
372
- toggling depth never shifts geometry (and solid now shares the same
373
- box as outlined/ghost, which always had a 1px border).
374
- --ty-button-border-l flips sign with the mode (tyrell.css): dark mode
375
- runs a LIGHTER-than-fill hairline (fills read washed-out against dark
376
- surfaces without a lit edge), light mode a slightly darker one.
377
- Direct escape hatches outrank the derivation: --ty-solid-border-color
378
- pins the border to ANY color (the L-offset dials stop mattering), and
379
- --ty-solid-border-width changes thickness (widths ≠ 1px do shift
380
- geometry — that's inherent).
381
- Browsers without relative color drop these declarations: border falls
382
- back to the base button's 1px transparent, shadows to none. */
383
- border: var(--ty-solid-border-width, 1px) solid
384
- var(--ty-solid-border-color,
385
- oklch(from var(--_solid-rest, var(--_solid-bg)) calc(l + var(--_border-l, -0.15) * var(--ty-button-depth, 0.35)) c h));
386
- box-shadow: 0 1px 2px oklch(0 0 0 / calc(0.4 * var(--ty-button-depth, 0.35)));
278
+ --_ring: var(--ty-color-neutral);
279
+ --_solid-bg: var(--ty-button-bg, var(--ty-solid-neutral));
280
+ --_solid-rest: var(--ty-button-bg, var(--ty-solid-neutral));
281
+ --_border-l: var(--ty-button-border-l-neutral, var(--ty-button-border-l, -0.15));
282
+ background: var(--_solid-bg);
283
+ color: var(--ty-button-color, var(--ty-solid-neutral-fg));
284
+ border: var(--ty-solid-border-width, 1px) solid
285
+ var(--ty-solid-border-color,
286
+ oklch(from var(--_solid-rest, var(--_solid-bg)) calc(l + var(--_border-l, -0.15) * var(--ty-button-depth, 0.35)) c h));
287
+ box-shadow: 0 1px 2px oklch(0 0 0 / calc(0.4 * var(--ty-button-depth, 0.35)));
387
288
  }
388
289
  button.solid.tone-plus { --_solid-bg: var(--ty-button-bg, var(--ty-solid-neutral-strong)); --_solid-rest: var(--ty-button-bg, var(--ty-solid-neutral-strong)); color: var(--ty-button-color, var(--ty-solid-neutral-strong-fg)); }
389
290
  button.solid.tone-minus { --_solid-bg: var(--ty-button-bg, var(--ty-solid-neutral-soft)); --_solid-rest: var(--ty-button-bg, var(--ty-solid-neutral-soft)); color: var(--ty-button-color, var(--ty-solid-neutral-soft-fg)); }
390
291
  button.solid:hover:not(:disabled) { --_solid-bg: var(--ty-button-bg-hover, var(--ty-solid-neutral-hover)); }
391
292
  button.solid:active:not(:disabled) { --_solid-bg: var(--ty-solid-neutral-active); }
392
293
  button.solid.tone-plus:hover:not(:disabled) {
393
- --_solid-bg: var(--ty-button-bg-hover, oklch(from var(--ty-solid-neutral-strong) calc(l + sign(0.5 - l) * abs(var(--ty-solid-hover-l))) c h));
294
+ --_solid-bg: var(--ty-button-bg-hover, oklch(from var(--ty-solid-neutral-strong) calc(l + sign(0.5 - l) * abs(var(--ty-solid-hover-l))) c h));
394
295
  }
395
296
  button.solid.tone-plus:active:not(:disabled) {
396
- --_solid-bg: oklch(from var(--ty-solid-neutral-strong) calc(l + sign(0.5 - l) * abs(var(--ty-solid-active-l))) c h);
297
+ --_solid-bg: oklch(from var(--ty-solid-neutral-strong) calc(l + sign(0.5 - l) * abs(var(--ty-solid-active-l))) c h);
397
298
  }
398
299
  button.solid.tone-minus:hover:not(:disabled) {
399
- --_solid-bg: var(--ty-button-bg-hover, oklch(from var(--ty-solid-neutral-soft) calc(l + var(--ty-solid-hover-l)) c h));
300
+ --_solid-bg: var(--ty-button-bg-hover, oklch(from var(--ty-solid-neutral-soft) calc(l + var(--ty-solid-hover-l)) c h));
400
301
  }
401
302
  button.solid.tone-minus:active:not(:disabled) {
402
- --_solid-bg: oklch(from var(--ty-solid-neutral-soft) calc(l + var(--ty-solid-active-l)) c h);
303
+ --_solid-bg: oklch(from var(--ty-solid-neutral-soft) calc(l + var(--ty-solid-active-l)) c h);
403
304
  }
404
-
405
305
  ${FLAVORS.map((f) => solidFlavor(f)).join('')}
406
306
  button.solid:focus-visible {
407
- box-shadow: 0 0 0 2px var(--ty-focus-ring-gap), 0 0 0 4px var(--_ring);
408
- }
409
-
410
- /* OUTLINED — transparent bg, text === border (uses --ty-color-*).
411
- Bare .outlined rule = fallback for custom flavors. Text is bound to
412
- border color, so the fallback chain prefers --ty-button-border, then
413
- --ty-button-color. */
414
-
307
+ box-shadow: 0 0 0 2px var(--ty-focus-ring-gap), 0 0 0 4px var(--_ring);
308
+ }
415
309
  button.outlined {
416
- background: transparent;
417
- color: var(--ty-button-border, var(--ty-button-color, var(--ty-color-neutral)));
418
- border-color: var(--ty-button-border, var(--ty-button-color, var(--ty-color-neutral)));
310
+ background: transparent;
311
+ color: var(--ty-button-border, var(--ty-button-color, var(--ty-color-neutral)));
312
+ border-color: var(--ty-button-border, var(--ty-button-color, var(--ty-color-neutral)));
419
313
  }
420
314
  button.outlined:hover:not(:disabled) {
421
- background: var(--ty-button-bg-hover, var(--ty-bg-neutral-soft));
315
+ background: var(--ty-button-bg-hover, var(--ty-bg-neutral-soft));
422
316
  }
423
-
424
317
  ${FLAVORS.map((f) => outlinedFlavor(f)).join('')}
425
-
426
- /* GHOST — text only, hover bg (uses --ty-color-* + --ty-bg-*-soft).
427
- Bare .ghost rule = fallback for custom flavors. */
428
-
429
318
  button.ghost {
430
- background: transparent;
431
- border: none;
432
- color: var(--ty-button-color, var(--ty-color-neutral));
319
+ background: transparent;
320
+ border: none;
321
+ color: var(--ty-button-color, var(--ty-color-neutral));
433
322
  }
434
323
  button.ghost:hover:not(:disabled) {
435
- background: var(--ty-button-bg-hover, var(--ty-bg-neutral-soft));
324
+ background: var(--ty-button-bg-hover, var(--ty-bg-neutral-soft));
436
325
  }
437
-
438
326
  ${FLAVORS.map((f) => ghostFlavor(f)).join('')}`;