@ultimat3/ui 22.14.0 → 23.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.
- package/CATALOG.md +43 -12
- package/CLAUDE.md +13 -49
- package/README.md +59 -4
- package/package.json +6 -5
- package/src/catalog/parse-component.ts +85 -12
- package/src/catalog/render-catalog.ts +21 -1
- package/src/components/Avatar.module.scss +1 -1
- package/src/components/BarChart.module.scss +12 -1
- package/src/components/BarChart.tsx +41 -15
- package/src/components/Button.tsx +3 -8
- package/src/components/Card.module.scss +1 -1
- package/src/components/Checkbox.module.scss +4 -4
- package/src/components/Checkbox.tsx +6 -0
- package/src/components/CommandPalette.module.scss +1 -1
- package/src/components/DataTable.module.scss +2 -0
- package/src/components/DataTable.tsx +90 -29
- package/src/components/Dialog.module.scss +2 -2
- package/src/components/Dropzone.module.scss +3 -3
- package/src/components/FileInput.module.scss +1 -1
- package/src/components/Form.module.scss +2 -2
- package/src/components/Input.module.scss +1 -1
- package/src/components/Link.tsx +64 -12
- package/src/components/Pagination.tsx +89 -37
- package/src/components/Radio.module.scss +2 -2
- package/src/components/Select.module.scss +3 -3
- package/src/components/Skeleton.module.scss +1 -1
- package/src/components/Spinner.module.scss +3 -3
- package/src/components/Switch.module.scss +2 -2
- package/src/components/Table.module.scss +2 -2
- package/src/components/Tabs.module.scss +5 -5
- package/src/components/ThemeToggle.module.scss +1 -1
- package/src/components/Toast.module.scss +2 -2
- package/src/components/Tooltip.module.scss +1 -1
- package/src/components/bar-chart-view.ts +29 -3
- package/src/components/button-classes.ts +25 -0
- package/src/components/link-classes.ts +38 -0
- package/src/components/link-target.ts +13 -0
- package/src/index.ts +13 -3
- package/src/sass-probe.ts +55 -0
- package/src/tokens/_index.scss +3 -1
- package/src/tokens/_mixins.scss +56 -8
- package/src/tokens/_motion.scss +4 -0
- package/src/tokens/_stroke.scss +20 -0
- package/src/tokens/_units.scss +47 -0
- package/src/tokens/reset.scss +4 -4
- package/src/tokens/theme.scss +2 -0
- package/src/tokens/tokens.ts +6 -0
package/src/tokens/_mixins.scss
CHANGED
|
@@ -4,15 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
@use 'sass:list';
|
|
6
6
|
@use 'sass:map';
|
|
7
|
+
@use 'sass:string';
|
|
7
8
|
@use 'breakpoints';
|
|
8
9
|
@use 'colors';
|
|
9
10
|
@use 'motion';
|
|
10
11
|
@use 'radius';
|
|
11
12
|
@use 'space';
|
|
13
|
+
@use 'stroke';
|
|
12
14
|
@use 'typography';
|
|
15
|
+
@use 'units';
|
|
13
16
|
|
|
14
17
|
/// Keyboard-only focus, drawn from the accent role so it themes automatically.
|
|
15
|
-
@mixin focus-ring(
|
|
18
|
+
@mixin focus-ring(
|
|
19
|
+
$role: 'accent',
|
|
20
|
+
$width: stroke.stroke(thick),
|
|
21
|
+
$offset: stroke.stroke(thick)
|
|
22
|
+
) {
|
|
16
23
|
&:focus-visible {
|
|
17
24
|
outline: $width solid colors.role($role);
|
|
18
25
|
outline-offset: $offset;
|
|
@@ -81,13 +88,54 @@
|
|
|
81
88
|
container-name: $name;
|
|
82
89
|
}
|
|
83
90
|
|
|
84
|
-
|
|
91
|
+
// --- Viewport breakpoints ----------------------------------------------------
|
|
92
|
+
// Three intents over ONE map, so a rung is defined in one place: at and above (`respond-to`),
|
|
93
|
+
// below (`respond-down`), and from one rung up to the next (`respond-between`). Prefer a container
|
|
94
|
+
// query to any of them — reach here only when the layout depends on the viewport itself.
|
|
95
|
+
|
|
96
|
+
/// What a max-width arm stops short of its rung by. `max-width: 768px` and `min-width: 768px` both
|
|
97
|
+
/// match at exactly 768px, which is how one rule ends up applying in two ranges at once; 0.02px is
|
|
98
|
+
/// below any width a browser reports and above the rounding that would collapse it.
|
|
99
|
+
$-under: 0.02px;
|
|
100
|
+
|
|
101
|
+
/// A rung's width. Compared as TEXT as well as by value: `2xl` in a map is the number 2 with the
|
|
102
|
+
/// unit `xl`, so the string `'2xl'` every stylesheet writes is a different key to `map.get`.
|
|
103
|
+
@function -rung($bp, $mixin) {
|
|
104
|
+
@each $name, $width in breakpoints.$breakpoints {
|
|
105
|
+
@if '#{$name}' == '#{$bp}' {
|
|
106
|
+
@return $width;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
@error string.unquote('X_TOKEN_UNKNOWN: #{$mixin}("#{$bp}") — that breakpoint is not in $breakpoints. fix: use one of #{map.keys(breakpoints.$breakpoints)}');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// At a rung and wider. Named rungs, never a raw px value.
|
|
85
113
|
@mixin respond-to($bp) {
|
|
86
|
-
@
|
|
87
|
-
@
|
|
114
|
+
@media (min-width: -rung($bp, 'respond-to')) {
|
|
115
|
+
@content;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Narrower than a rung — the complement of `respond-to` on the same rung, with no shared pixel.
|
|
120
|
+
@mixin respond-down($bp) {
|
|
121
|
+
@media (max-width: (-rung($bp, 'respond-down') - $-under)) {
|
|
122
|
+
@content;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// From `$from` up to, and not including, `$to`.
|
|
127
|
+
@mixin respond-between($from, $to) {
|
|
128
|
+
$min: -rung($from, 'respond-between');
|
|
129
|
+
$max: -rung($to, 'respond-between');
|
|
130
|
+
|
|
131
|
+
@if $min == $max {
|
|
132
|
+
@error string.unquote('X_UI_INVALID_VALUE: respond-between("#{$from}", "#{$to}") is an empty range — both ends are #{$min}. fix: write respond-to("#{$from}"), or name a higher rung as $to');
|
|
133
|
+
}
|
|
134
|
+
@if $min > $max {
|
|
135
|
+
@error string.unquote('X_UI_INVALID_VALUE: respond-between("#{$from}", "#{$to}") is reversed — #{$min} is not below #{$max}. fix: write respond-between("#{$to}", "#{$from}")');
|
|
88
136
|
}
|
|
89
137
|
|
|
90
|
-
@media (min-width:
|
|
138
|
+
@media (min-width: $min) and (max-width: ($max - $-under)) {
|
|
91
139
|
@content;
|
|
92
140
|
}
|
|
93
141
|
}
|
|
@@ -156,8 +204,8 @@
|
|
|
156
204
|
scrollbar-color: colors.role($thumb) colors.role($track);
|
|
157
205
|
|
|
158
206
|
&::-webkit-scrollbar {
|
|
159
|
-
width: 10px;
|
|
160
|
-
height: 10px;
|
|
207
|
+
width: units.rem(10px);
|
|
208
|
+
height: units.rem(10px);
|
|
161
209
|
}
|
|
162
210
|
|
|
163
211
|
&::-webkit-scrollbar-track {
|
|
@@ -165,7 +213,7 @@
|
|
|
165
213
|
}
|
|
166
214
|
|
|
167
215
|
&::-webkit-scrollbar-thumb {
|
|
168
|
-
border:
|
|
216
|
+
border: stroke.stroke(thick) solid transparent;
|
|
169
217
|
border-radius: radius.radius(pill);
|
|
170
218
|
background: colors.role($thumb);
|
|
171
219
|
background-clip: content-box;
|
package/src/tokens/_motion.scss
CHANGED
|
@@ -16,6 +16,10 @@ $easing: (
|
|
|
16
16
|
spring: cubic-bezier(0.34, 1.56, 0.64, 1),
|
|
17
17
|
);
|
|
18
18
|
|
|
19
|
+
/// What reduced motion collapses every duration to. Never `0`: an animation that takes no time
|
|
20
|
+
/// fires no `animationend`, and a component waiting on that event waits forever.
|
|
21
|
+
$still: 0.01ms;
|
|
22
|
+
|
|
19
23
|
@mixin emit {
|
|
20
24
|
:root {
|
|
21
25
|
@each $name, $value in $duration {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Line weights: a border, an outline, a focus ring, an indicator. px on purpose — a line that
|
|
2
|
+
// grew with the reader's text size would be a different design at 200% zoom, not a bigger one.
|
|
3
|
+
|
|
4
|
+
$stroke: (
|
|
5
|
+
hairline: 1px,
|
|
6
|
+
thick: 2px,
|
|
7
|
+
heavy: 3px,
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
@mixin emit {
|
|
11
|
+
:root {
|
|
12
|
+
@each $name, $value in $stroke {
|
|
13
|
+
--stroke-#{$name}: #{$value};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@function stroke($name) {
|
|
19
|
+
@return var(--stroke-#{$name});
|
|
20
|
+
}
|
|
@@ -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
|
+
}
|
package/src/tokens/reset.scss
CHANGED
|
@@ -115,8 +115,8 @@
|
|
|
115
115
|
|
|
116
116
|
// Themed keyboard focus for anything the component layer forgot.
|
|
117
117
|
:where(:focus-visible) {
|
|
118
|
-
outline:
|
|
119
|
-
outline-offset:
|
|
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:
|
|
156
|
+
animation-duration: t.$still !important;
|
|
157
157
|
animation-iteration-count: 1 !important;
|
|
158
|
-
transition-duration:
|
|
158
|
+
transition-duration: t.$still !important;
|
|
159
159
|
scroll-behavior: auto !important;
|
|
160
160
|
}
|
|
161
161
|
}
|
package/src/tokens/theme.scss
CHANGED
|
@@ -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;
|
package/src/tokens/tokens.ts
CHANGED
|
@@ -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',
|