@filipgutica/ui 1.10.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.
- package/README.md +60 -53
- package/package.json +1 -1
- package/src/site.css +21 -2
- package/src/themes/graphite-indigo.css +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# @filipgutica/ui
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
Tailwind CSS 4
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
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
|
-
|
|
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, #
|
|
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; }
|