@filipgutica/ui 1.11.0 → 1.11.1

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 (3) hide show
  1. package/README.md +60 -53
  2. package/package.json +1 -1
  3. package/src/site.css +21 -2
package/README.md CHANGED
@@ -1,7 +1,14 @@
1
1
  # @filipgutica/ui
2
2
 
3
- Compact Vue 3.5+ components built on [Reka UI](https://reka-ui.com), with
4
- Tailwind CSS 4 and VS Code theme support.
3
+ @filipgutica/ui is a compact Vue 3.5+ component library built on
4
+ [Reka UI](https://reka-ui.com) and styled with Tailwind CSS 4. You can recolor
5
+ it with a VS Code color theme: the theme supplies the colors, and the library
6
+ keeps typography, spacing, and component structure the same.
7
+
8
+ Use it when you want dense, calm controls: neutral surfaces with one accent
9
+ color, compact sizes on a 4px rhythm, and visible keyboard focus. It also
10
+ provides the shared header, navigation, and page components of Filip's project
11
+ sites.
5
12
 
6
13
  [Docs and playground](https://filipgutica.github.io/ui/) ·
7
14
  [GitHub](https://github.com/filipgutica/ui) ·
@@ -9,6 +16,9 @@ Tailwind CSS 4 and VS Code theme support.
9
16
 
10
17
  ## Install
11
18
 
19
+ You need a Vue 3.5+ app whose stylesheet Tailwind CSS 4 already builds. This
20
+ package does not install Tailwind.
21
+
12
22
  ```sh
13
23
  pnpm add @filipgutica/ui
14
24
  ```
@@ -22,6 +32,9 @@ Import the styles from your application stylesheet:
22
32
  @import "@filipgutica/ui/components.css";
23
33
  ```
24
34
 
35
+ The third import adds Graphite Indigo, the palette of the project sites. Leave
36
+ it out to keep the base theme from `theme.css`, with blue actions.
37
+
25
38
  ## Use
26
39
 
27
40
  ```vue
@@ -37,45 +50,12 @@ import { UiButton } from '@filipgutica/ui'
37
50
  ```
38
51
 
39
52
  The [component docs](https://filipgutica.github.io/ui/#/components/button)
40
- cover examples, props, slots, and events. Place inputs and selects inside
41
- `UiField` to connect labels, descriptions, and errors automatically.
42
-
43
- ## Page navigation
44
-
45
- `UiDrawer` provides modal focus containment, Escape dismissal, and trigger focus
46
- restoration. Keep native navigation in the server-rendered page until hydration;
47
- the application decides when to replace it and when to close the drawer at a
48
- desktop breakpoint. To focus a link destination after closing, prevent the
49
- cancelable `closeAutoFocus` event and focus that heading.
50
-
51
- `UiTabs` renders every labelled panel during SSR and initial hydration. After
52
- mounting, it adds keyboard-accessible tabs and hides inactive panels without
53
- unmounting them. A tab strip narrower than its tabs scrolls, fades each clipped
54
- edge, and keeps the selected tab in view when it resizes. Use unique item values
55
- and keep page anchor headings outside the panels. Re-align an initial hash after
56
- layout settles if enhancement changes the page height.
57
-
58
- Track page sections from a component's setup function:
59
-
60
- ```ts
61
- import { useActiveSection } from '@filipgutica/ui'
62
-
63
- const activeSection = useActiveSection({
64
- targetIds: ['overview', 'install', 'commands'],
65
- })
66
- ```
67
-
68
- The returned readonly ref contains the last supplied heading above the root's
69
- CSS `scroll-padding-top` inset, or the final section at the document bottom.
70
- IDs must remain stable and in page order; missing elements are ignored after
71
- mounting. Bind matching links to `aria-current="location"`. Scrolling, resizing,
72
- layout changes, completed transitions, and restored pages update the value. After
73
- a jump to a section, through a `#id` link, a hash change, or a deep link, that
74
- section stays current until the reader scrolls away from where the jump lands, so
75
- a page too short to bring it to the top still marks it.
76
- The composable does not change focus, the URL, or browser history, and removes
77
- its listeners and observer when the component unmounts. During SSR it returns
78
- the first supplied ID without accessing browser globals.
53
+ show an example, a sandbox, and the props, slots, and events of the components
54
+ in `@filipgutica/ui`. Read the
55
+ [Drawer](https://filipgutica.github.io/ui/#/components/drawer) and
56
+ [Tabs](https://filipgutica.github.io/ui/#/components/tabs) pages before you use
57
+ them for page navigation: they explain how each one behaves before and after
58
+ hydration.
79
59
 
80
60
  ## Themes
81
61
 
@@ -88,20 +68,47 @@ const theme = parseVsCodeTheme({ source, fileName })
88
68
  applySemanticTheme({ root: document.documentElement, theme })
89
69
  ```
90
70
 
91
- Themes supply colors; the library keeps typography, spacing, and component
92
- structure consistent. Open VSX search and imports are available through
93
- `createOpenVsxThemeService` from `@filipgutica/ui/open-vsx`. It runs on Node
94
- or in a browser with HTTPS and Web Crypto. Browser requests depend on the
95
- registry and its resource host allowing cross-origin access.
71
+ The theme must define a background color and a foreground color, such as
72
+ `editor.background` and `editor.foreground`.
73
+
74
+ To let people search for and import themes from Open VSX, use
75
+ `createOpenVsxThemeService` from `@filipgutica/ui/open-vsx`. It runs on Node or
76
+ in a browser with HTTPS and Web Crypto. In a browser, it works only when the
77
+ registry and its resource host allow cross-origin requests.
78
+
79
+ The theme search-and-install dialog on the docs site is not exported. Build
80
+ your own picker from the exported components and theme helpers.
96
81
 
97
- The gallery's search-and-install dialog is docs-only, not an exported component.
98
- Applications can build their own picker with the exported components and theme
99
- helpers. `UiSiteHeader` includes the built-in System, Light, and Dark control.
82
+ ## Track the current section
83
+
84
+ `useActiveSection` tells you which section of a long page the reader is on,
85
+ so you can mark its link. Call it from a component's setup function:
86
+
87
+ ```ts
88
+ import { useActiveSection } from '@filipgutica/ui'
89
+
90
+ const activeSection = useActiveSection({
91
+ targetIds: ['overview', 'install', 'commands'],
92
+ })
93
+ ```
100
94
 
101
- The install steps above import Graphite Indigo, the palette of the project
102
- sites, after `theme.css`. To keep the base theme instead, with blue actions and
103
- warm greys, leave out the `themes/graphite-indigo.css` import. Graphite Indigo
104
- stays opt-in: `theme.css` alone still gives the base theme.
95
+ Bind `aria-current="location"` to the link whose section matches the value.
96
+
97
+ - The returned readonly ref holds the last supplied heading above the root's
98
+ CSS `scroll-padding-top` inset. At the bottom of the document, it holds the
99
+ final section.
100
+ - Keep the IDs stable and in page order. IDs with no element on the page are
101
+ ignored after mounting.
102
+ - Scrolling, resizing, layout changes, completed transitions, and restored
103
+ pages update the value.
104
+ - After a jump to a section, through a `#id` link, a hash change, or a deep
105
+ link, that section stays current until the reader scrolls away from where the
106
+ jump lands. So a page too short to bring the section to the top still marks
107
+ it.
108
+ - It does not change focus, the URL, or browser history. It removes its
109
+ listeners and observer when the component unmounts.
110
+ - During SSR it returns the first supplied ID without accessing browser
111
+ globals.
105
112
 
106
113
  ## Project sites
107
114
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@filipgutica/ui",
3
- "version": "1.11.0",
3
+ "version": "1.11.1",
4
4
  "description": "A compact Vue design system with Tailwind CSS and VS Code theme support",
5
5
  "type": "module",
6
6
  "sideEffects": [
package/src/site.css CHANGED
@@ -222,6 +222,8 @@
222
222
  flex: none;
223
223
  border: 1px solid var(--color-border-subtle);
224
224
  border-radius: var(--radius-control);
225
+ /* A muted track, so the pressed choice reads as a raised thumb in both themes. */
226
+ background: var(--color-surface-muted);
225
227
  }
226
228
  .fg-site-appearance__choice {
227
229
  display: inline-flex;
@@ -240,7 +242,16 @@
240
242
  .fg-site-appearance__choice:first-child { border-radius: var(--radius-control) 0 0 var(--radius-control); }
241
243
  .fg-site-appearance__choice:last-child { border-radius: 0 var(--radius-control) var(--radius-control) 0; }
242
244
  .fg-site-appearance__choice:hover { color: var(--color-text); background: var(--color-row-hover); }
243
- .fg-site-appearance__choice[aria-pressed="true"] { color: var(--color-text); background: var(--color-row-selected); }
245
+ /* The ring gives the pressed choice 3:1 against the track in both themes (WCAG 1.4.11). */
246
+ .fg-site-appearance__choice[aria-pressed="true"] {
247
+ color: var(--color-text);
248
+ background: var(--color-row-selected);
249
+ box-shadow: inset 0 0 0 1px var(--color-border);
250
+ }
251
+ /* Forced colors drop the fill and the ring, so the pressed choice takes the system highlight. */
252
+ @media (forced-colors: active) {
253
+ .fg-site-appearance__choice[aria-pressed="true"] { forced-color-adjust: none; color: HighlightText; background: Highlight; }
254
+ }
244
255
 
245
256
  /* Focus is a 2px outline outside the control, which forced-colors mode keeps. An
246
257
  * inset ring is used only where a scrolling ancestor would clip an outer one. */
@@ -367,7 +378,7 @@
367
378
  .fg-capture__screen .tb1 { background: var(--t1, #f0776c); }
368
379
  .fg-capture__screen .tb2 { background: var(--t2, #4cc38a); }
369
380
  .fg-capture__screen .tb3 { background: var(--t3, #e2a54b); }
370
- .fg-capture__screen .tb4 { background: var(--t4, #7d97ff); }
381
+ .fg-capture__screen .tb4 { background: var(--t4, #3354c4); }
371
382
  .fg-capture__screen .tb5 { background: var(--t5, #c792ea); }
372
383
  .fg-capture__screen .tb6 { background: var(--t6, #5fd3d9); }
373
384
  .fg-capture__screen .tb7 { background: var(--t7, #c8c8d0); }
@@ -382,6 +393,8 @@
382
393
  .fg-capture__screen .tf-dim { color: var(--t-dim, #a0a0ab); }
383
394
  .fg-capture__screen .tf-bg { color: var(--t-bg, #131316); }
384
395
  .fg-capture__screen .tb-fg { background: var(--t-fg, #e6e6ea); }
396
+ /* Text on the light selection row stays dark, whatever its own colour class. */
397
+ .fg-capture__screen .tb8 { color: #1b1b1f; }
385
398
  .fg-capture__screen .tbold { font-weight: var(--weight-semibold); }
386
399
  .fg-capture__screen .tital { font-style: italic; }
387
400
  .fg-capture__screen .tul { text-decoration: underline; text-underline-offset: 0.18em; }
@@ -612,6 +625,12 @@
612
625
  }
613
626
  }
614
627
  @media (max-width: 640px) {
628
+ /* Phones: three or more site links do not fit beside the project menu, so the menu and
629
+ * the header controls share the first row and the links take the second. That keeps the
630
+ * header to two rows. With fewer links, the header keeps its wrapping row. */
631
+ .fg-site-header__inner:has(.fg-site-header__links > :nth-child(3)) .fg-site-header__end { display: contents; }
632
+ .fg-site-header__inner:has(.fg-site-header__links > :nth-child(3)) .fg-site-projects { margin-inline-end: auto; }
633
+ .fg-site-header__inner:has(.fg-site-header__links > :nth-child(3)) .fg-site-header__links { order: 1; flex-basis: 100%; }
615
634
  .fg-site-docs { padding-inline: var(--space-4); }
616
635
  /* Docs-shell pages keep one header row on phones and repeat these links in their footer. */
617
636
  .fg-site-docs .fg-site-header__links { display: none; }