@filipgutica/ui 1.0.0 → 1.1.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/README.md CHANGED
@@ -48,6 +48,10 @@ import { UiButton } from '@filipgutica/ui'
48
48
  </script>
49
49
  ```
50
50
 
51
+ `UiSelect` retains native form and keyboard behavior. Browsers with
52
+ `appearance: base-select` support show a themed menu; other browsers use their
53
+ native menu.
54
+
51
55
  `UiButton`, `UiInput`, `UiSelect`, `UiCheckbox`, and `UiSlider` share
52
56
  `size="sm | md | lg"`
53
57
  and default to the tighter `md` density. Existing `UiButton` values remain
@@ -78,7 +82,7 @@ should use `UiCard`.
78
82
 
79
83
  ## Design conventions
80
84
 
81
- Use a compact, Geist-inspired hierarchy: 14px for UI body text, 13px for
85
+ Use a compact system-font hierarchy: 14px for UI body text, 13px for
82
86
  secondary text, and 18px for dialog titles. Treat Open VSX/VS Code themes as
83
87
  the color authority, and use semantic tokens and package components instead of
84
88
  hard-coded provider color keys.
@@ -96,6 +100,11 @@ these once at the application root rather than restyling each component.
96
100
  status, `UiAlert` for feedback, and `UiCodeBlock` for source code.
97
101
  - Put `UiInput` and `UiSelect` in `UiField` with a visible label. Supply help
98
102
  text or an error when needed so the field exposes the matching ARIA relationship.
103
+ - Select menus use the same surface, type, radius, and spacing tokens as other
104
+ controls. Keep option rows compact, show a checkmark for the selected value, and
105
+ use quiet hover fills with visible keyboard focus.
106
+ - Use raised surfaces and subtle borders for standalone cards. Align title, content,
107
+ and footer insets; use a quiet divider for footer metadata and avoid card shadows.
99
108
  - Keep borders quiet and skip decorative grids. Preserve visible
100
109
  `:focus-visible` states, use 44px targets for touch-oriented actions, and provide
101
110
  an accessible name for every interactive control.
@@ -147,13 +156,29 @@ Build the provider-neutral static assets:
147
156
  pnpm docs:build
148
157
  ```
149
158
 
150
- The docs use hash routes, so the static assets do not require rewrite rules.
159
+ The docs use hash routes and relative asset paths, so the static assets work
160
+ under a repository path without rewrite rules.
151
161
  The local Vite server also provides the same-origin `/api/open-vsx` endpoint.
152
162
  The theme picker uses this endpoint to search and import Open VSX themes.
153
163
 
154
- A production host must provide the same API endpoint for live Open VSX imports.
155
- Netlify and Amplify can use a serverless adapter. GitHub Pages needs an external
156
- API or a catalog of normalized themes built in advance.
164
+ ### GitHub Pages
165
+
166
+ The GitHub Pages site URL is [filipgutica.github.io/ui](https://filipgutica.github.io/ui/).
167
+ The `docs.yml` workflow builds and deploys `docs-dist` on every push to `main`.
168
+ It can also be started manually from the Actions tab. In the repository's
169
+ **Settings → Pages**, select **GitHub Actions** as the build and deployment source.
170
+
171
+ GitHub Pages serves static files and cannot run the Open VSX API. The workflow
172
+ sets `VITE_STATIC_HOST=true` to disable live Open VSX search and imports;
173
+ built-in themes and local theme-file imports remain available. To build the
174
+ same static configuration locally:
175
+
176
+ ```sh
177
+ VITE_STATIC_HOST=true pnpm docs:build
178
+ ```
179
+
180
+ Other production hosts must provide `/api/open-vsx` for live Open VSX imports.
181
+ Netlify and Amplify can use a serverless adapter.
157
182
 
158
183
  ## Development
159
184
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@filipgutica/ui",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "A compact Vue design system with Tailwind CSS and VS Code theme support",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -4,9 +4,9 @@
4
4
  .fg-radio-card, .fg-badge, .fg-alert, .fg-surface, .fg-card,
5
5
  .fg-dialog__content, .fg-code-block {
6
6
  font-family: var(--font-ui);
7
- font-size: 0.875rem;
8
- font-weight: 400;
9
- line-height: 1.5;
7
+ font-size: var(--text-md);
8
+ font-weight: var(--weight-regular);
9
+ line-height: var(--leading);
10
10
  }
11
11
 
12
12
  .fg-code-block {
@@ -62,12 +62,7 @@
62
62
  border: 1px solid transparent;
63
63
  border-radius: var(--radius-control);
64
64
  font-family: var(--font-ui);
65
- transition:
66
- background-color var(--duration-fast) ease,
67
- border-color var(--duration-fast) ease,
68
- color var(--duration-fast) ease,
69
- box-shadow var(--duration-fast) ease,
70
- transform var(--duration-fast) ease;
65
+ transition: transform var(--duration-fast) var(--ease-out);
71
66
  }
72
67
 
73
68
  .fg-button {
@@ -133,15 +128,15 @@
133
128
  font-size: 1rem;
134
129
  }
135
130
 
136
- .fg-button[data-size="sm"] { min-height: 2rem; }
137
- .fg-button[data-size="md"] { min-height: 2.25rem; }
138
- .fg-button[data-size="lg"] { min-height: 2.75rem; }
131
+ .fg-button[data-size="sm"] { min-height: var(--control-sm); }
132
+ .fg-button[data-size="md"] { min-height: var(--control-md); }
133
+ .fg-button[data-size="lg"] { min-height: var(--control-lg); }
139
134
  .fg-input[data-size="sm"],
140
- .fg-select[data-size="sm"] { height: 2rem; }
135
+ .fg-select[data-size="sm"] { height: var(--control-sm); }
141
136
  .fg-input[data-size="md"],
142
- .fg-select[data-size="md"] { height: 2.25rem; }
137
+ .fg-select[data-size="md"] { height: var(--control-md); }
143
138
  .fg-input[data-size="lg"],
144
- .fg-select[data-size="lg"] { height: 2.75rem; }
139
+ .fg-select[data-size="lg"] { height: var(--control-lg); }
145
140
 
146
141
  .fg-button[data-size="sm"] { padding: 0 0.6rem; }
147
142
  .fg-button[data-size="md"] { padding: 0 0.75rem; }
@@ -149,13 +144,13 @@
149
144
 
150
145
  /* Deprecated UiButton aliases retained for existing consumers. */
151
146
  .fg-button[data-size="compact"] {
152
- min-height: 2.25rem;
147
+ min-height: var(--control-md);
153
148
  padding: 0 0.7rem;
154
149
  font-size: 0.8125rem;
155
150
  }
156
151
 
157
152
  .fg-button[data-size="default"] {
158
- min-height: 2.75rem;
153
+ min-height: var(--control-lg);
159
154
  padding: 0 1rem;
160
155
  font-size: 1rem;
161
156
  }
@@ -163,7 +158,7 @@
163
158
  .fg-button[data-variant="secondary"] {
164
159
  border-color: var(--color-border);
165
160
  color: var(--color-text);
166
- background: var(--color-surface-raised);
161
+ background: transparent;
167
162
  }
168
163
 
169
164
  .fg-button[data-variant="secondary"]:hover:not(:disabled),
@@ -188,7 +183,7 @@
188
183
  border-color: var(--color-error);
189
184
  color: var(--color-error);
190
185
  background: var(--color-error-surface);
191
- box-shadow: inset 0 0 0 1px var(--color-error);
186
+ box-shadow: none;
192
187
  }
193
188
 
194
189
  .fg-button[data-variant="text"] {
@@ -229,6 +224,61 @@
229
224
  background-size: 0.3rem 0.3rem;
230
225
  }
231
226
 
227
+ /* Keep native form semantics; older browsers retain the native picker. */
228
+ @supports (appearance: base-select) {
229
+ .fg-select:not([multiple]):not([size]),
230
+ .fg-select:not([multiple]):not([size])::picker(select) {
231
+ appearance: base-select;
232
+ }
233
+ .fg-select:not([multiple]):not([size]) {
234
+ position: relative;
235
+ align-items: center;
236
+ background-image: none;
237
+ cursor: pointer;
238
+ }
239
+ .fg-select:not([multiple]):not([size]):disabled { cursor: not-allowed; }
240
+ .fg-select:not([multiple]):not([size])::picker-icon { display: none; }
241
+ .fg-select:not([multiple]):not([size])::after {
242
+ position: absolute;
243
+ right: var(--space-3);
244
+ width: var(--space-2);
245
+ height: var(--space-2);
246
+ border-right: 1.5px solid var(--color-muted);
247
+ border-bottom: 1.5px solid var(--color-muted);
248
+ transform: translateY(-2px) rotate(45deg);
249
+ content: "";
250
+ }
251
+ .fg-select:not([multiple]):not([size])::picker(select) {
252
+ min-width: anchor-size(width);
253
+ max-height: min(20rem, 50dvh);
254
+ overflow: auto;
255
+ margin-block: var(--space-1);
256
+ padding: var(--space-1);
257
+ border: 1px solid var(--color-border-subtle);
258
+ border-radius: var(--radius-panel);
259
+ color: var(--color-text);
260
+ background: var(--color-input-bg);
261
+ box-shadow: var(--shadow-dialog);
262
+ font: var(--weight-regular) var(--text-md) / var(--leading) var(--font-ui);
263
+ }
264
+ .fg-select:not([multiple]):not([size]) option {
265
+ min-height: var(--control-sm);
266
+ gap: var(--space-2);
267
+ padding: var(--space-2) var(--space-3);
268
+ border-radius: var(--radius-control);
269
+ color: var(--color-text);
270
+ background: transparent;
271
+ cursor: pointer;
272
+ }
273
+ .fg-select:not([multiple]):not([size]) option:checked { color: var(--color-row-selected-text); background: var(--color-row-selected); }
274
+ .fg-select:not([multiple]):not([size]) option:hover:not(:disabled), .fg-select:not([multiple]):not([size]) option:focus-visible { background: var(--color-row-hover); outline: 2px solid var(--color-focus); outline-offset: -2px; }
275
+ .fg-select:not([multiple]):not([size]) option:disabled { color: var(--color-muted); opacity: 0.58; cursor: not-allowed; }
276
+ .fg-select:not([multiple]):not([size]) option::checkmark { order: 1; margin-left: auto; color: var(--color-link); }
277
+ @media (pointer: coarse) {
278
+ .fg-select:not([multiple]):not([size]) option { min-height: var(--control-lg); }
279
+ }
280
+ }
281
+
232
282
  .fg-input[data-size="sm"] { padding: 0 0.55rem; }
233
283
  .fg-input[data-size="md"] { padding: 0 0.65rem; }
234
284
  .fg-input[data-size="lg"] { padding: 0 0.7rem; }
@@ -304,21 +354,21 @@
304
354
  .fg-checkbox__label { line-height: 1.4; }
305
355
 
306
356
  .fg-checkbox[data-size="sm"] {
307
- min-height: 2rem;
357
+ min-height: var(--control-sm);
308
358
  align-items: center;
309
359
  gap: 0.45rem;
310
360
  font-size: 0.8125rem;
311
361
  }
312
362
 
313
363
  .fg-checkbox[data-size="md"] {
314
- min-height: 2.25rem;
364
+ min-height: var(--control-md);
315
365
  align-items: center;
316
366
  gap: 0.55rem;
317
367
  font-size: 0.875rem;
318
368
  }
319
369
 
320
370
  .fg-checkbox[data-size="lg"] {
321
- min-height: 2.75rem;
371
+ min-height: var(--control-lg);
322
372
  align-items: center;
323
373
  gap: 0.65rem;
324
374
  font-size: 1rem;
@@ -365,10 +415,7 @@
365
415
  box-shadow: none;
366
416
  text-align: left;
367
417
  cursor: pointer;
368
- transition:
369
- border-color var(--duration-fast) ease,
370
- box-shadow var(--duration-fast) ease,
371
- transform var(--duration-fast) ease;
418
+ transition: transform var(--duration-fast) var(--ease-out);
372
419
  }
373
420
 
374
421
  .fg-radio-card:hover:not([data-disabled]) {
@@ -377,7 +424,8 @@
377
424
 
378
425
  .fg-radio-card[data-state="checked"] {
379
426
  border-color: var(--color-link);
380
- box-shadow: 0 0 0 1px var(--color-link);
427
+ outline: 1px solid var(--color-link);
428
+ outline-offset: -2px;
381
429
  }
382
430
 
383
431
  .fg-radio-card[data-disabled] {
@@ -461,30 +509,48 @@
461
509
  .fg-surface[data-padding="default"] { padding: 1rem; }
462
510
 
463
511
  .fg-card {
512
+ min-width: 0;
464
513
  overflow: hidden;
465
514
  border: 1px solid var(--color-border-subtle);
466
515
  border-radius: var(--radius-panel);
516
+ color: var(--color-text);
467
517
  background: var(--color-surface-raised);
468
518
  box-shadow: none;
469
519
  }
470
520
 
471
- .fg-card__header,
472
- .fg-card__footer {
521
+ .fg-card__header {
473
522
  display: flex;
474
523
  align-items: center;
475
524
  justify-content: space-between;
476
- gap: 0.75rem;
477
- padding: 0.75rem 1rem;
525
+ gap: var(--space-3);
526
+ padding: var(--space-4);
478
527
  }
479
528
 
529
+ .fg-card__title {
530
+ min-width: 0;
531
+ margin: 0;
532
+ color: var(--color-text);
533
+ font-size: var(--text-md);
534
+ font-weight: var(--weight-medium);
535
+ line-height: var(--leading);
536
+ overflow-wrap: anywhere;
537
+ }
538
+ .fg-card__actions { display: flex; flex: none; align-items: center; gap: var(--space-2); margin-left: auto; }
539
+ .fg-card__body { min-width: 0; padding: var(--space-4); }
540
+ .fg-card__header + .fg-card__body { padding-top: 0; }
480
541
  .fg-card__footer {
481
- background: var(--color-surface);
542
+ display: flex;
543
+ flex-wrap: wrap;
544
+ align-items: center;
545
+ justify-content: space-between;
546
+ gap: var(--space-3);
547
+ margin-inline: var(--space-4);
548
+ padding-block: var(--space-3);
549
+ border-top: 1px solid var(--color-border-subtle);
550
+ color: var(--color-muted);
551
+ font-size: var(--text-sm);
482
552
  }
483
553
 
484
- .fg-card__title { margin: 0; font-size: 0.875rem; font-weight: 600; line-height: 1.4; }
485
- .fg-card__actions { display: flex; align-items: center; gap: 0.5rem; margin-left: auto; }
486
- .fg-card__body { padding: 1rem; }
487
-
488
554
  .fg-progress {
489
555
  position: relative;
490
556
  height: 0.55rem;
@@ -511,9 +577,9 @@
511
577
  user-select: none;
512
578
  }
513
579
 
514
- .fg-slider[data-size="sm"] { min-height: 2rem; }
515
- .fg-slider[data-size="md"] { min-height: 2.25rem; }
516
- .fg-slider[data-size="lg"] { min-height: 2.75rem; }
580
+ .fg-slider[data-size="sm"] { min-height: var(--control-sm); }
581
+ .fg-slider[data-size="md"] { min-height: var(--control-md); }
582
+ .fg-slider[data-size="lg"] { min-height: var(--control-lg); }
517
583
 
518
584
  .fg-slider__track {
519
585
  position: relative;
@@ -606,7 +672,7 @@
606
672
  display: inline-grid;
607
673
  flex: none;
608
674
  width: 2.75rem;
609
- min-height: 2.75rem;
675
+ min-height: var(--control-lg);
610
676
  padding: 0;
611
677
  place-items: center;
612
678
  color: var(--color-muted);
package/src/theme.css CHANGED
@@ -2,47 +2,73 @@
2
2
 
3
3
  :root {
4
4
  color-scheme: light;
5
- --font-ui: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
6
- --font-mono: 'SFMono-Regular', Consolas, monospace;
7
- --color-bg: #ffffff;
8
- --color-surface: #f7f8fa;
5
+ --font-ui: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif;
6
+ --font-mono: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
7
+ --text-xs: 0.75rem;
8
+ --text-sm: 0.8125rem;
9
+ --text-md: 0.875rem;
10
+ --text-lg: 1rem;
11
+ --text-xl: 1.25rem;
12
+ --text-2xl: 1.5rem;
13
+ --weight-regular: 400;
14
+ --weight-medium: 500;
15
+ --weight-semibold: 600;
16
+ --leading-tight: 1.25;
17
+ --leading: 1.5;
18
+ --space-1: 0.25rem;
19
+ --space-2: 0.5rem;
20
+ --space-3: 0.75rem;
21
+ --space-4: 1rem;
22
+ --space-5: 1.25rem;
23
+ --space-6: 1.5rem;
24
+ --space-8: 2rem;
25
+ --space-12: 3rem;
26
+ --space-16: 4rem;
27
+ --control-sm: 2rem;
28
+ --control-md: 2.25rem;
29
+ --control-lg: 2.75rem;
30
+ --measure: 68ch;
31
+ --page-max: 60rem;
32
+ --sidebar: 14rem;
33
+ --color-bg: #fcfcfd;
34
+ --color-surface: #f6f6f8;
9
35
  --color-surface-raised: #fff;
10
- --color-surface-muted: #eef1f5;
11
- --color-text: #101828;
12
- --color-muted: #5d6979;
13
- --color-link: #155eef;
14
- --color-accent: #4d7c0f;
15
- --color-focus: #155eef;
36
+ --color-surface-muted: #efeff2;
37
+ --color-text: #1b1b1f;
38
+ --color-muted: #62626d;
39
+ --color-link: #3d63dd;
40
+ --color-accent: #3d63dd;
41
+ --color-focus: #3d63dd;
16
42
  --color-border: #667085;
17
- --color-border-subtle: #d8dee8;
18
- --color-button-bg: #155eef;
43
+ --color-border-subtle: #e9e9ed;
44
+ --color-button-bg: #3d63dd;
19
45
  --color-button-text: #fff;
20
- --color-button-hover: #004eeb;
46
+ --color-button-hover: #3354c4;
21
47
  --color-input-bg: #fff;
22
- --color-input-text: #101828;
48
+ --color-input-text: #1b1b1f;
23
49
  --color-input-border: #667085;
24
50
  --color-input-placeholder: #667085;
25
- --color-row-hover: #f2f4f7;
26
- --color-row-selected: #eaf1ff;
27
- --color-row-selected-text: #143d8f;
28
- --color-code: #0c1426;
29
- --color-code-text: #edf2ff;
30
- --color-syntax-comment: #a8b3c7;
31
- --color-syntax-keyword: #cba6f7;
32
- --color-syntax-string: #a6e3a1;
33
- --color-syntax-number: #fab387;
34
- --color-syntax-function: #89b4fa;
35
- --color-syntax-type: #f9e2af;
36
- --color-syntax-variable: #edf2ff;
37
- --color-syntax-operator: #89dceb;
38
- --color-syntax-punctuation: #cdd6f4;
39
- --color-syntax-tag: #89b4fa;
40
- --color-syntax-attribute: #f9e2af;
51
+ --color-row-hover: #efeff2;
52
+ --color-row-selected: #e9ebf8;
53
+ --color-row-selected-text: #3354c4;
54
+ --color-code: #f6f6f8;
55
+ --color-code-text: #1b1b1f;
56
+ --color-syntax-comment: #62626d;
57
+ --color-syntax-keyword: #3d63dd;
58
+ --color-syntax-string: #1f7a52;
59
+ --color-syntax-number: #854a0e;
60
+ --color-syntax-function: #3d63dd;
61
+ --color-syntax-type: #854a0e;
62
+ --color-syntax-variable: #1b1b1f;
63
+ --color-syntax-operator: #62626d;
64
+ --color-syntax-punctuation: #62626d;
65
+ --color-syntax-tag: #3d63dd;
66
+ --color-syntax-attribute: #854a0e;
41
67
  --color-error: #c01048;
42
68
  --color-warning: #854a0e;
43
69
  --color-success: #067647;
44
70
  --color-info: #175cd3;
45
- --color-progress: #155eef;
71
+ --color-progress: #3d63dd;
46
72
  --color-shadow: rgb(16 24 40 / 10%);
47
73
  --color-overlay: rgb(0 0 0 / 42%);
48
74
  --color-error-surface: color-mix(in srgb, var(--color-error) 12%, var(--color-surface));
@@ -52,43 +78,57 @@
52
78
  --radius-control: 0.375rem;
53
79
  --radius-panel: 0.5rem;
54
80
  --radius-pill: 999px;
55
- --duration-fast: 140ms;
56
- --shadow-control: 0 1px 2px rgb(16 24 40 / 8%);
57
- --shadow-dialog: 0 24px 80px rgb(5 12 24 / 28%);
81
+ --duration-fast: 120ms;
82
+ --duration: 180ms;
83
+ --ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
84
+ --shadow-control: none;
85
+ --shadow-dialog: 0 8px 24px rgb(16 16 24 / 12%);
58
86
  }
59
87
 
60
88
  :root.dark {
61
89
  color-scheme: dark;
62
- --color-bg: #0b1020;
63
- --color-surface: #111827;
64
- --color-surface-raised: #151d2f;
65
- --color-surface-muted: #202a3d;
66
- --color-text: #f4f7ff;
67
- --color-muted: #a8b3c7;
68
- --color-link: #8fb4ff;
69
- --color-accent: #b7ef53;
70
- --color-focus: #8fb4ff;
90
+ --color-bg: #0f0f11;
91
+ --color-surface: #151518;
92
+ --color-surface-raised: #1a1a1e;
93
+ --color-surface-muted: #1f1f24;
94
+ --color-text: #ececf0;
95
+ --color-muted: #a0a0ab;
96
+ --color-link: #7d97ff;
97
+ --color-accent: #7d97ff;
98
+ --color-focus: #7d97ff;
71
99
  --color-border: #647189;
72
- --color-border-subtle: #2d384d;
73
- --color-button-bg: #6e9cff;
74
- --color-button-text: #071126;
75
- --color-button-hover: #8fb4ff;
76
- --color-input-bg: #0d1526;
77
- --color-input-text: #f4f7ff;
100
+ --color-border-subtle: #222227;
101
+ --color-button-bg: #7d97ff;
102
+ --color-button-text: #0d1020;
103
+ --color-button-hover: #97acff;
104
+ --color-input-bg: #151518;
105
+ --color-input-text: #ececf0;
78
106
  --color-input-border: #647189;
79
- --color-input-placeholder: #8995aa;
80
- --color-row-hover: #1b2538;
81
- --color-row-selected: #1b315b;
82
- --color-row-selected-text: #dbe7ff;
83
- --color-code: #070d19;
84
- --color-code-text: #edf2ff;
107
+ --color-input-placeholder: #a0a0ab;
108
+ --color-row-hover: #1f1f24;
109
+ --color-row-selected: #1f2233;
110
+ --color-row-selected-text: #ececf0;
111
+ --color-code: #151518;
112
+ --color-code-text: #ececf0;
113
+ --color-syntax-comment: #a0a0ab;
114
+ --color-syntax-keyword: #97acff;
115
+ --color-syntax-string: #6fd3a0;
116
+ --color-syntax-number: #e8b56a;
117
+ --color-syntax-function: #97acff;
118
+ --color-syntax-type: #e8b56a;
119
+ --color-syntax-variable: #ececf0;
120
+ --color-syntax-operator: #a0a0ab;
121
+ --color-syntax-punctuation: #a0a0ab;
122
+ --color-syntax-tag: #97acff;
123
+ --color-syntax-attribute: #e8b56a;
85
124
  --color-error: #ff8aad;
86
125
  --color-warning: #f8c477;
87
126
  --color-success: #75e0a7;
88
127
  --color-info: #84adff;
89
- --color-progress: #8fb4ff;
128
+ --color-progress: #7d97ff;
90
129
  --color-shadow: rgb(0 0 0 / 32%);
91
130
  --color-overlay: rgb(0 0 0 / 62%);
131
+ --shadow-dialog: 0 8px 24px rgb(0 0 0 / 45%);
92
132
  }
93
133
 
94
134
  @theme inline {