@filipgutica/ui 1.1.1 → 1.3.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
@@ -39,6 +39,39 @@ The [component docs](https://filipgutica.github.io/ui/#/components/button)
39
39
  cover examples, props, slots, and events. Place inputs and selects inside
40
40
  `UiField` to connect labels, descriptions, and errors automatically.
41
41
 
42
+ ## Page navigation
43
+
44
+ `UiDrawer` provides modal focus containment, Escape dismissal, and trigger focus
45
+ restoration. Keep native navigation in the server-rendered page until hydration;
46
+ the application decides when to replace it and when to close the drawer at a
47
+ desktop breakpoint. To focus a link destination after closing, prevent the
48
+ cancelable `closeAutoFocus` event and focus that heading.
49
+
50
+ `UiTabs` renders every labelled panel during SSR and initial hydration. After
51
+ mounting, it adds keyboard-accessible tabs and hides inactive panels without
52
+ unmounting them. Use unique item values and keep page anchor headings outside
53
+ the panels. Re-align an initial hash after layout settles if enhancement changes
54
+ the page height.
55
+
56
+ Track page sections from a component's setup function:
57
+
58
+ ```ts
59
+ import { useActiveSection } from '@filipgutica/ui'
60
+
61
+ const activeSection = useActiveSection({
62
+ targetIds: ['overview', 'install', 'commands'],
63
+ })
64
+ ```
65
+
66
+ The returned readonly ref contains the last supplied heading above the root's
67
+ CSS `scroll-padding-top` inset, or the final section at the document bottom.
68
+ IDs must remain stable and in page order; missing elements are ignored after
69
+ mounting. Bind matching links to `aria-current="location"`. Scrolling, resizing,
70
+ layout changes, completed transitions, and restored pages update the value.
71
+ The composable does not change focus, the URL, or browser history, and removes
72
+ its listeners and observer when the component unmounts. During SSR it returns
73
+ the first supplied ID without accessing browser globals.
74
+
42
75
  ## Themes
43
76
 
44
77
  Import a VS Code JSON or JSONC theme in the browser:
@@ -5,6 +5,7 @@ export interface UiCodeBlockProps {
5
5
  copyable?: boolean;
6
6
  lineNumbers?: boolean;
7
7
  wrap?: boolean;
8
+ variant?: 'default' | 'compact';
8
9
  }
9
10
  type __VLS_Slots = {
10
11
  actions?(): unknown;
@@ -0,0 +1,23 @@
1
+ export interface UiDrawerProps {
2
+ open: boolean;
3
+ title: string;
4
+ }
5
+ type __VLS_Slots = {
6
+ default(): unknown;
7
+ trigger?(): unknown;
8
+ };
9
+ declare const __VLS_base: import("vue").DefineComponent<UiDrawerProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
10
+ "update:open": (value: boolean) => any;
11
+ closeAutoFocus: (value: Event) => any;
12
+ }, string, import("vue").PublicProps, Readonly<UiDrawerProps> & Readonly<{
13
+ "onUpdate:open"?: (value: boolean) => any;
14
+ onCloseAutoFocus?: (value: Event) => any;
15
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
16
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
17
+ declare const _default: typeof __VLS_export;
18
+ export default _default;
19
+ type __VLS_WithSlots<T, S> = T & {
20
+ new (): {
21
+ $slots: S;
22
+ };
23
+ };
@@ -0,0 +1,27 @@
1
+ export interface UiTabItem {
2
+ value: string;
3
+ label: string;
4
+ }
5
+ export interface UiTabsProps {
6
+ items: readonly UiTabItem[];
7
+ label: string;
8
+ modelValue: string;
9
+ }
10
+ type __VLS_Slots = {
11
+ panel(props: {
12
+ value: string;
13
+ }): unknown;
14
+ };
15
+ declare const __VLS_base: import("vue").DefineComponent<UiTabsProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
16
+ "update:modelValue": (value: string) => any;
17
+ }, string, import("vue").PublicProps, Readonly<UiTabsProps> & Readonly<{
18
+ "onUpdate:modelValue"?: (value: string) => any;
19
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
21
+ declare const _default: typeof __VLS_export;
22
+ export default _default;
23
+ type __VLS_WithSlots<T, S> = T & {
24
+ new (): {
25
+ $slots: S;
26
+ };
27
+ };
package/dist/index.d.ts CHANGED
@@ -12,6 +12,8 @@ export { default as UiCheckbox } from './components/UiCheckbox.vue';
12
12
  export type { UiCheckboxProps } from './components/UiCheckbox.vue';
13
13
  export { default as UiDialog } from './components/UiDialog.vue';
14
14
  export type { UiDialogProps } from './components/UiDialog.vue';
15
+ export { default as UiDrawer } from './components/UiDrawer.vue';
16
+ export type { UiDrawerProps } from './components/UiDrawer.vue';
15
17
  export { default as UiField } from './components/UiField.vue';
16
18
  export type { UiFieldProps } from './components/UiField.vue';
17
19
  export { default as UiInput } from './components/UiInput.vue';
@@ -26,7 +28,11 @@ export { default as UiSelect } from './components/UiSelect.vue';
26
28
  export type { UiSelectProps } from './components/UiSelect.vue';
27
29
  export { default as UiSlider } from './components/UiSlider.vue';
28
30
  export type { UiSliderProps } from './components/UiSlider.vue';
31
+ export { default as UiTabs } from './components/UiTabs.vue';
32
+ export type { UiTabItem, UiTabsProps } from './components/UiTabs.vue';
29
33
  /** @deprecated Use UiCard for structured content. */
30
34
  export { default as UiSurface } from './components/UiSurface.vue';
31
35
  export type { UiSurfaceProps } from './components/UiSurface.vue';
32
36
  export type { UiControlSize } from './control.js';
37
+ export { useActiveSection } from './use-active-section.js';
38
+ export type { ActiveSectionOptions } from './use-active-section.js';
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { Fragment as e, computed as t, createBlock as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, createTextVNode as o, createVNode as s, defineComponent as c, inject as l, mergeModels as u, mergeProps as d, normalizeStyle as f, openBlock as p, provide as m, ref as h, renderList as g, renderSlot as _, resolveDynamicComponent as v, shallowRef as ee, toDisplayString as y, unref as b, useModel as x, useSlots as S, watch as te, withCtx as C } from "vue";
2
- import { CheckboxIndicator as w, CheckboxRoot as T, DialogClose as E, DialogContent as D, DialogDescription as O, DialogOverlay as k, DialogPortal as A, DialogRoot as j, DialogTitle as M, Primitive as N, ProgressIndicator as P, ProgressRoot as F, RadioGroupItem as I, RadioGroupRoot as L, SliderRange as R, SliderRoot as z, SliderThumb as B, SliderTrack as V } from "reka-ui";
3
- import { useTimeoutFn as H } from "@vueuse/core";
1
+ import { Fragment as e, computed as t, createBlock as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, createTextVNode as o, createVNode as s, defineComponent as c, inject as l, mergeModels as u, mergeProps as d, normalizeStyle as f, onMounted as p, onUnmounted as m, openBlock as h, provide as g, readonly as _, ref as v, renderList as y, renderSlot as b, resolveDynamicComponent as x, shallowRef as S, toDisplayString as C, unref as w, useId as T, useModel as E, useSlots as ee, watch as te, withCtx as D } from "vue";
2
+ import { CheckboxIndicator as O, CheckboxRoot as k, DialogClose as A, DialogContent as j, DialogDescription as ne, DialogOverlay as M, DialogPortal as N, DialogRoot as P, DialogTitle as F, DialogTrigger as I, Primitive as L, ProgressIndicator as R, ProgressRoot as z, RadioGroupItem as B, RadioGroupRoot as V, SliderRange as H, SliderRoot as U, SliderThumb as W, SliderTrack as G } from "reka-ui";
3
+ import { useTimeoutFn as K } from "@vueuse/core";
4
4
  //#endregion
5
5
  //#region src/components/UiButton.vue
6
- var U = /* @__PURE__ */ c({
6
+ var q = /* @__PURE__ */ c({
7
7
  __name: "UiButton",
8
8
  props: {
9
9
  disabled: {
@@ -19,7 +19,7 @@ var U = /* @__PURE__ */ c({
19
19
  variant: { default: "primary" }
20
20
  },
21
21
  setup(e) {
22
- return (t, r) => (p(), n(b(N), {
22
+ return (t, r) => (h(), n(w(L), {
23
23
  as: "button",
24
24
  class: "fg-button",
25
25
  "data-size": e.size,
@@ -28,7 +28,7 @@ var U = /* @__PURE__ */ c({
28
28
  disabled: e.disabled || e.loading,
29
29
  type: e.type
30
30
  }, {
31
- default: C(() => [_(t.$slots, "default")]),
31
+ default: D(() => [b(t.$slots, "default")]),
32
32
  _: 3
33
33
  }, 8, [
34
34
  "data-size",
@@ -38,49 +38,65 @@ var U = /* @__PURE__ */ c({
38
38
  "type"
39
39
  ]));
40
40
  }
41
- }), W = { class: "fg-card" }, G = {
41
+ }), J = { class: "fg-card" }, Y = {
42
42
  key: 0,
43
43
  class: "fg-card__header"
44
- }, K = {
44
+ }, X = {
45
45
  key: 1,
46
46
  class: "fg-card__actions"
47
- }, q = {
47
+ }, Z = {
48
48
  key: 1,
49
49
  class: "fg-card__body"
50
- }, J = {
50
+ }, re = {
51
51
  key: 2,
52
52
  class: "fg-card__footer"
53
- }, Y = /* @__PURE__ */ c({
53
+ }, ie = /* @__PURE__ */ c({
54
54
  __name: "UiCard",
55
55
  props: {
56
56
  title: { default: () => void 0 },
57
57
  titleTag: { default: "h3" }
58
58
  },
59
59
  setup(e) {
60
- let t = S();
61
- return (a, s) => (p(), i("div", W, [
62
- e.title || t.title || t.actions ? (p(), i("header", G, [e.title || t.title ? (p(), n(v(e.titleTag), {
60
+ let t = ee();
61
+ return (a, s) => (h(), i("div", J, [
62
+ e.title || t.title || t.actions ? (h(), i("header", Y, [e.title || t.title ? (h(), n(x(e.titleTag), {
63
63
  key: 0,
64
64
  class: "fg-card__title"
65
65
  }, {
66
- default: C(() => [_(a.$slots, "title", {}, () => [o(y(e.title), 1)])]),
66
+ default: D(() => [b(a.$slots, "title", {}, () => [o(C(e.title), 1)])]),
67
67
  _: 3
68
- })) : r("", !0), t.actions ? (p(), i("div", K, [_(a.$slots, "actions")])) : r("", !0)])) : r("", !0),
69
- t.default ? (p(), i("div", q, [_(a.$slots, "default")])) : r("", !0),
70
- t.footer ? (p(), i("footer", J, [_(a.$slots, "footer")])) : r("", !0)
68
+ })) : r("", !0), t.actions ? (h(), i("div", X, [b(a.$slots, "actions")])) : r("", !0)])) : r("", !0),
69
+ t.default ? (h(), i("div", Z, [b(a.$slots, "default")])) : r("", !0),
70
+ t.footer ? (h(), i("footer", re, [b(a.$slots, "footer")])) : r("", !0)
71
71
  ]));
72
72
  }
73
- }), X = ["data-wrap", "data-line-numbers"], Z = { class: "fg-code-block__header" }, ne = { class: "fg-code-block__title" }, re = {
73
+ }), ae = [
74
+ "data-wrap",
75
+ "data-line-numbers",
76
+ "data-variant"
77
+ ], oe = {
74
78
  key: 0,
79
+ class: "fg-code-block__header"
80
+ }, se = {
81
+ key: 0,
82
+ class: "fg-code-block__title"
83
+ }, ce = {
84
+ key: 1,
75
85
  class: "fg-code-block__language"
76
- }, ie = { class: "fg-code-block__actions" }, ae = ["disabled"], oe = ["aria-label"], se = {
86
+ }, le = { class: "fg-code-block__actions" }, ue = ["disabled", "title"], de = {
87
+ viewBox: "0 0 24 24",
88
+ fill: "none",
89
+ stroke: "currentColor",
90
+ "stroke-width": "1.5",
91
+ "aria-hidden": "true"
92
+ }, fe = {
93
+ key: 0,
94
+ d: "m5 12 4 4L19 6"
95
+ }, pe = { key: 0 }, me = ["aria-label"], he = {
77
96
  key: 0,
78
97
  class: "fg-code-block__number",
79
98
  "aria-hidden": "true"
80
- }, ce = { class: "fg-code-block__text" }, le = {
81
- class: "fg-code-block__status",
82
- role: "status"
83
- }, ue = /* @__PURE__ */ c({
99
+ }, ge = { class: "fg-code-block__text" }, _e = ["data-state"], ve = /* @__PURE__ */ c({
84
100
  __name: "UiCodeBlock",
85
101
  props: {
86
102
  code: {},
@@ -97,10 +113,11 @@ var U = /* @__PURE__ */ c({
97
113
  wrap: {
98
114
  type: Boolean,
99
115
  default: !1
100
- }
116
+ },
117
+ variant: { default: "default" }
101
118
  },
102
119
  setup(n) {
103
- let s = ee(), c = h(""), l = t(() => s.value ?? n.code.split("\n").map((e) => [{ content: e }])), u = {
120
+ let s = S(), c = v(""), l = t(() => s.value ?? n.code.split("\n").map((e) => [{ content: e }])), u = {
104
121
  vue: "vue",
105
122
  ts: "typescript",
106
123
  typescript: "typescript",
@@ -136,93 +153,93 @@ var U = /* @__PURE__ */ c({
136
153
  i || (c.value = "Syntax highlighting unavailable. Showing plain text.");
137
154
  }
138
155
  }, { immediate: !0 });
139
- let d = h(!1), { start: m } = H(() => {
156
+ let d = v(!1), { start: p } = K(() => {
140
157
  d.value = !1;
141
- }, 1500, { immediate: !1 }), v = h(!1), b = h(""), x = async () => {
142
- if (b.value = "", d.value = !1, typeof navigator > "u" || !navigator.clipboard?.writeText) {
143
- b.value = "Clipboard unavailable. Select the code to copy it.";
158
+ }, 1500, { immediate: !1 }), m = v(!1), g = v(""), _ = t(() => d.value && !g.value), x = t(() => n.variant === "compact" && _.value && !c.value), w = t(() => g.value ? g.value : n.variant === "compact" && c.value ? d.value ? `Code copied to clipboard. ${c.value}` : c.value : d.value ? "Code copied to clipboard." : c.value), T = async () => {
159
+ if (g.value = "", d.value = !1, typeof navigator > "u" || !navigator.clipboard?.writeText) {
160
+ g.value = "Clipboard unavailable. Select the code to copy it.";
144
161
  return;
145
162
  }
146
- v.value = !0;
163
+ m.value = !0;
147
164
  try {
148
- await navigator.clipboard.writeText(n.code), d.value = !0, m();
165
+ await navigator.clipboard.writeText(n.code), d.value = !0, p();
149
166
  } catch {
150
- b.value = "Could not copy. Select the code to copy it.";
167
+ g.value = "Could not copy. Select the code to copy it.";
151
168
  } finally {
152
- v.value = !1;
169
+ m.value = !1;
153
170
  }
154
171
  };
155
- return (t, s) => (p(), i("div", {
172
+ return (t, s) => (h(), i("div", {
156
173
  class: "fg-code-block",
157
174
  "data-wrap": n.wrap,
158
- "data-line-numbers": n.lineNumbers
175
+ "data-line-numbers": n.lineNumbers,
176
+ "data-variant": n.variant
159
177
  }, [
160
- a("div", Z, [
161
- a("span", ne, y(n.title || n.language), 1),
162
- n.title ? (p(), i("span", re, y(n.language), 1)) : r("", !0),
163
- a("div", ie, [_(t.$slots, "actions"), n.copyable ? (p(), i("button", {
178
+ n.variant === "default" || n.copyable || t.$slots.actions ? (h(), i("div", oe, [
179
+ n.variant === "default" ? (h(), i("span", se, C(n.title || n.language), 1)) : r("", !0),
180
+ n.title && n.variant === "default" ? (h(), i("span", ce, C(n.language), 1)) : r("", !0),
181
+ a("div", le, [b(t.$slots, "actions"), n.copyable ? (h(), i("button", {
164
182
  key: 0,
165
183
  type: "button",
166
184
  class: "fg-code-block__copy",
167
- disabled: v.value,
185
+ disabled: m.value,
168
186
  "aria-label": "Copy code",
169
- onClick: x
170
- }, [s[0] ||= a("svg", {
171
- viewBox: "0 0 24 24",
172
- fill: "none",
173
- stroke: "currentColor",
174
- "stroke-width": "1.5",
175
- "aria-hidden": "true"
176
- }, [a("rect", {
187
+ title: n.variant === "compact" ? _.value ? "Copied" : "Copy code" : void 0,
188
+ onClick: T
189
+ }, [(h(), i("svg", de, [n.variant === "compact" && _.value ? (h(), i("path", fe)) : (h(), i(e, { key: 1 }, [s[0] ||= a("rect", {
177
190
  x: "8",
178
191
  y: "8",
179
192
  width: "12",
180
193
  height: "13",
181
194
  rx: "2"
182
- }), a("path", { d: "M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3" })], -1), o(" " + y(d.value && !b.value ? "Copied" : "Copy"), 1)], 8, ae)) : r("", !0)])
183
- ]),
195
+ }, null, -1), s[1] ||= a("path", { d: "M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3" }, null, -1)], 64))])), n.variant === "default" ? (h(), i("span", pe, C(_.value ? "Copied" : "Copy"), 1)) : r("", !0)], 8, ue)) : r("", !0)])
196
+ ])) : r("", !0),
184
197
  a("pre", {
185
198
  class: "fg-code-block__pre",
186
199
  tabindex: "0",
187
200
  "aria-label": n.title || `${n.language} code`
188
- }, [a("code", null, [(p(!0), i(e, null, g(l.value, (t, s) => (p(), i("span", {
201
+ }, [a("code", null, [(h(!0), i(e, null, y(l.value, (t, s) => (h(), i("span", {
189
202
  key: s,
190
203
  class: "fg-code-block__line"
191
- }, [n.lineNumbers ? (p(), i("span", se, y(s + 1), 1)) : r("", !0), a("span", ce, [(p(!0), i(e, null, g(t, (e, t) => (p(), i("span", {
204
+ }, [n.lineNumbers ? (h(), i("span", he, C(s + 1), 1)) : r("", !0), a("span", ge, [(h(!0), i(e, null, y(t, (e, t) => (h(), i("span", {
192
205
  key: t,
193
206
  style: f({ color: e.color })
194
- }, y(e.content), 5))), 128)), o(y(s < l.value.length - 1 ? "\n" : ""), 1)])]))), 128))])], 8, oe),
195
- a("span", le, y(b.value || (d.value ? "Code copied to clipboard." : c.value)), 1)
196
- ], 8, X));
207
+ }, C(e.content), 5))), 128)), o(C(s < l.value.length - 1 ? "\n" : ""), 1)])]))), 128))])], 8, me),
208
+ a("span", {
209
+ class: "fg-code-block__status",
210
+ role: "status",
211
+ "data-state": x.value ? "success" : void 0
212
+ }, C(w.value), 9, _e)
213
+ ], 8, ae));
197
214
  }
198
- }), de = /* @__PURE__ */ c({
215
+ }), ye = /* @__PURE__ */ c({
199
216
  __name: "UiAlert",
200
217
  props: { tone: { default: "neutral" } },
201
218
  setup(e) {
202
- return (t, r) => (p(), n(b(N), {
219
+ return (t, r) => (h(), n(w(L), {
203
220
  as: "div",
204
221
  class: "fg-alert",
205
222
  "data-tone": e.tone,
206
223
  role: e.tone === "danger" || e.tone === "error" ? "alert" : "status"
207
224
  }, {
208
- default: C(() => [_(t.$slots, "default")]),
225
+ default: D(() => [b(t.$slots, "default")]),
209
226
  _: 3
210
227
  }, 8, ["data-tone", "role"]));
211
228
  }
212
- }), fe = /* @__PURE__ */ c({
229
+ }), be = /* @__PURE__ */ c({
213
230
  __name: "UiBadge",
214
231
  props: { tone: { default: "neutral" } },
215
232
  setup(e) {
216
- return (t, r) => (p(), n(b(N), {
233
+ return (t, r) => (h(), n(w(L), {
217
234
  as: "span",
218
235
  class: "fg-badge",
219
236
  "data-tone": e.tone
220
237
  }, {
221
- default: C(() => [_(t.$slots, "default")]),
238
+ default: D(() => [b(t.$slots, "default")]),
222
239
  _: 3
223
240
  }, 8, ["data-tone"]));
224
241
  }
225
- }), pe = ["data-size", "data-disabled"], me = { class: "fg-checkbox__label" }, he = /* @__PURE__ */ c({
242
+ }), xe = ["data-size", "data-disabled"], Se = { class: "fg-checkbox__label" }, Ce = /* @__PURE__ */ c({
226
243
  __name: "UiCheckbox",
227
244
  props: {
228
245
  disabled: {
@@ -240,27 +257,28 @@ var U = /* @__PURE__ */ c({
240
257
  let n = t, r = (e) => {
241
258
  n("update:modelValue", e === !0);
242
259
  };
243
- return (t, n) => (p(), i("label", {
260
+ return (t, n) => (h(), i("label", {
244
261
  class: "fg-checkbox",
245
262
  "data-size": e.size,
246
263
  "data-disabled": e.disabled ? "true" : void 0
247
- }, [s(b(T), {
264
+ }, [s(w(k), {
248
265
  class: "fg-checkbox__control",
249
266
  disabled: e.disabled,
250
267
  "model-value": e.modelValue,
251
268
  "onUpdate:modelValue": r
252
269
  }, {
253
- default: C(() => [s(b(w), { class: "fg-checkbox__indicator" }, {
254
- default: C(() => [...n[0] ||= [o("✓", -1)]]),
270
+ default: D(() => [s(w(O), { class: "fg-checkbox__indicator" }, {
271
+ default: D(() => [...n[0] ||= [o("✓", -1)]]),
255
272
  _: 1
256
273
  })]),
257
274
  _: 1
258
- }, 8, ["disabled", "model-value"]), a("span", me, [_(t.$slots, "default")])], 8, pe));
275
+ }, 8, ["disabled", "model-value"]), a("span", Se, [b(t.$slots, "default")])], 8, xe));
259
276
  }
260
- }), ge = { class: "fg-dialog__header" }, _e = { class: "fg-dialog__body" }, ve = {
277
+ }), we = { class: "fg-dialog__header" }, Te = { class: "fg-dialog__body" }, Ee = {
261
278
  key: 0,
262
279
  class: "fg-dialog__footer"
263
- }, ye = /* @__PURE__ */ c({
280
+ }, De = /* @__PURE__ */ c({
281
+ inheritAttrs: !1,
264
282
  __name: "UiDialog",
265
283
  props: {
266
284
  description: { default: () => void 0 },
@@ -270,43 +288,86 @@ var U = /* @__PURE__ */ c({
270
288
  emits: ["update:open"],
271
289
  setup(e, { emit: t }) {
272
290
  let c = t;
273
- return (t, l) => (p(), n(b(j), {
291
+ return (t, l) => (h(), n(w(P), {
274
292
  open: e.open,
275
293
  "onUpdate:open": l[0] ||= (e) => c("update:open", e)
276
294
  }, {
277
- default: C(() => [s(b(A), null, {
278
- default: C(() => [s(b(k), { class: "fg-dialog__overlay" }), s(b(D), {
295
+ default: D(() => [s(w(N), null, {
296
+ default: D(() => [s(w(M), { class: "fg-dialog__overlay" }), s(w(j), d({
279
297
  class: "fg-dialog__content",
280
298
  "aria-modal": "true"
281
- }, {
282
- default: C(() => [
283
- a("div", ge, [a("div", null, [s(b(M), { class: "fg-dialog__title" }, {
284
- default: C(() => [o(y(e.title), 1)]),
299
+ }, t.$attrs), {
300
+ default: D(() => [
301
+ a("div", we, [a("div", null, [s(w(F), { class: "fg-dialog__title" }, {
302
+ default: D(() => [o(C(e.title), 1)]),
285
303
  _: 1
286
- }), e.description ? (p(), n(b(O), {
304
+ }), e.description ? (h(), n(w(ne), {
287
305
  key: 0,
288
306
  class: "fg-dialog__description"
289
307
  }, {
290
- default: C(() => [o(y(e.description), 1)]),
308
+ default: D(() => [o(C(e.description), 1)]),
291
309
  _: 1
292
- })) : r("", !0)]), s(b(E), {
310
+ })) : r("", !0)]), s(w(A), {
293
311
  class: "fg-dialog__close",
294
312
  "aria-label": "Close dialog"
295
313
  }, {
296
- default: C(() => [...l[1] ||= [o(" × ", -1)]]),
314
+ default: D(() => [...l[1] ||= [o(" × ", -1)]]),
297
315
  _: 1
298
316
  })]),
299
- a("div", _e, [_(t.$slots, "default")]),
300
- t.$slots.footer ? (p(), i("div", ve, [_(t.$slots, "footer")])) : r("", !0)
317
+ a("div", Te, [b(t.$slots, "default")]),
318
+ t.$slots.footer ? (h(), i("div", Ee, [b(t.$slots, "footer")])) : r("", !0)
301
319
  ]),
302
320
  _: 3
303
- })]),
321
+ }, 16)]),
304
322
  _: 3
305
323
  })]),
306
324
  _: 3
307
325
  }, 8, ["open"]));
308
326
  }
309
- }), Q = Symbol("UiFieldContext"), be = ["data-invalid"], $ = ["for"], xe = ["id"], Se = ["id"], Ce = /* @__PURE__ */ c({
327
+ }), Oe = { class: "fg-drawer__header" }, ke = { class: "fg-drawer__body" }, Ae = /* @__PURE__ */ c({
328
+ inheritAttrs: !1,
329
+ __name: "UiDrawer",
330
+ props: {
331
+ open: { type: Boolean },
332
+ title: {}
333
+ },
334
+ emits: ["update:open", "closeAutoFocus"],
335
+ setup(e, { emit: t }) {
336
+ let i = t;
337
+ return (t, c) => (h(), n(w(P), {
338
+ open: e.open,
339
+ "onUpdate:open": c[1] ||= (e) => i("update:open", e)
340
+ }, {
341
+ default: D(() => [t.$slots.trigger ? (h(), n(w(I), {
342
+ key: 0,
343
+ "as-child": ""
344
+ }, {
345
+ default: D(() => [b(t.$slots, "trigger")]),
346
+ _: 3
347
+ })) : r("", !0), s(w(N), null, {
348
+ default: D(() => [s(w(M), { class: "fg-drawer__overlay" }), s(w(j), d({
349
+ class: "fg-drawer__content",
350
+ "aria-modal": "true",
351
+ "aria-describedby": void 0
352
+ }, t.$attrs, { onCloseAutoFocus: c[0] ||= (e) => i("closeAutoFocus", e) }), {
353
+ default: D(() => [a("div", Oe, [s(w(F), { class: "fg-drawer__title" }, {
354
+ default: D(() => [o(C(e.title), 1)]),
355
+ _: 1
356
+ }), s(w(A), {
357
+ class: "fg-drawer__close",
358
+ "aria-label": "Close drawer"
359
+ }, {
360
+ default: D(() => [...c[2] ||= [o(" × ", -1)]]),
361
+ _: 1
362
+ })]), a("div", ke, [b(t.$slots, "default")])]),
363
+ _: 3
364
+ }, 16)]),
365
+ _: 3
366
+ })]),
367
+ _: 3
368
+ }, 8, ["open"]));
369
+ }
370
+ }), Q = Symbol("UiFieldContext"), je = ["data-invalid"], Me = ["for"], Ne = ["id"], Pe = ["id"], Fe = /* @__PURE__ */ c({
310
371
  __name: "UiField",
311
372
  props: {
312
373
  controlId: {},
@@ -316,38 +377,38 @@ var U = /* @__PURE__ */ c({
316
377
  },
317
378
  setup(e) {
318
379
  let n = t(() => e.description && !e.error ? `${e.controlId}-description` : void 0), o = t(() => e.error ? `${e.controlId}-error` : void 0), s = t(() => !!e.error);
319
- return m(Q, {
380
+ return g(Q, {
320
381
  controlId: t(() => e.controlId),
321
382
  descriptionId: n,
322
383
  errorId: o,
323
384
  invalid: s
324
- }), (t, s) => (p(), i("div", {
385
+ }), (t, s) => (h(), i("div", {
325
386
  class: "fg-field",
326
387
  "data-invalid": e.error ? "true" : void 0
327
388
  }, [
328
389
  a("label", {
329
390
  class: "fg-field__label",
330
391
  for: e.controlId
331
- }, y(e.label), 9, $),
332
- _(t.$slots, "default", {
392
+ }, C(e.label), 9, Me),
393
+ b(t.$slots, "default", {
333
394
  descriptionId: n.value,
334
395
  errorId: o.value
335
396
  }),
336
- e.description && !e.error ? (p(), i("p", {
397
+ e.description && !e.error ? (h(), i("p", {
337
398
  key: 0,
338
399
  id: n.value,
339
400
  class: "fg-field__description",
340
401
  "data-field-description": ""
341
- }, y(e.description), 9, xe)) : r("", !0),
342
- e.error ? (p(), i("p", {
402
+ }, C(e.description), 9, Ne)) : r("", !0),
403
+ e.error ? (h(), i("p", {
343
404
  key: 1,
344
405
  id: o.value,
345
406
  class: "fg-field__error",
346
407
  "data-field-error": ""
347
- }, y(e.error), 9, Se)) : r("", !0)
348
- ], 8, be));
408
+ }, C(e.error), 9, Pe)) : r("", !0)
409
+ ], 8, je));
349
410
  }
350
- }), we = /* @__PURE__ */ c({
411
+ }), Ie = /* @__PURE__ */ c({
351
412
  __name: "UiInput",
352
413
  props: {
353
414
  disabled: {
@@ -368,13 +429,13 @@ var U = /* @__PURE__ */ c({
368
429
  let r = l(Q, void 0), i = t, a = (e) => {
369
430
  e.currentTarget instanceof HTMLInputElement && i("update:modelValue", e.currentTarget.value);
370
431
  };
371
- return (t, i) => (p(), n(b(N), {
372
- id: b(r)?.controlId.value ?? e.id,
432
+ return (t, i) => (h(), n(w(L), {
433
+ id: w(r)?.controlId.value ?? e.id,
373
434
  as: "input",
374
435
  class: "fg-input",
375
- "aria-describedby": b(r)?.descriptionId.value,
376
- "aria-errormessage": b(r)?.errorId.value,
377
- "aria-invalid": e.invalid || b(r)?.invalid.value ? "true" : void 0,
436
+ "aria-describedby": w(r)?.descriptionId.value,
437
+ "aria-errormessage": w(r)?.errorId.value,
438
+ "aria-invalid": e.invalid || w(r)?.invalid.value ? "true" : void 0,
378
439
  "data-size": e.size,
379
440
  disabled: e.disabled,
380
441
  type: e.type,
@@ -391,7 +452,7 @@ var U = /* @__PURE__ */ c({
391
452
  "value"
392
453
  ]));
393
454
  }
394
- }), Te = 100, Ee = /* @__PURE__ */ c({
455
+ }), Le = 100, Re = /* @__PURE__ */ c({
395
456
  __name: "UiProgress",
396
457
  props: {
397
458
  label: {},
@@ -400,19 +461,19 @@ var U = /* @__PURE__ */ c({
400
461
  },
401
462
  setup(e) {
402
463
  let r = t(() => {
403
- let t = Number.isFinite(e.max) && e.max > 0 ? e.max : Te;
464
+ let t = Number.isFinite(e.max) && e.max > 0 ? e.max : Le;
404
465
  return {
405
466
  max: t,
406
467
  value: e.value === null || !Number.isFinite(e.value) ? null : Math.min(t, Math.max(0, e.value))
407
468
  };
408
469
  }), i = t(() => r.value.value === null ? 0 : r.value.value / r.value.max * 100);
409
- return (t, a) => (p(), n(b(F), {
470
+ return (t, a) => (h(), n(w(z), {
410
471
  class: "fg-progress",
411
472
  "aria-label": e.label,
412
473
  max: r.value.max,
413
474
  "model-value": r.value.value
414
475
  }, {
415
- default: C(() => [s(b(P), {
476
+ default: D(() => [s(w(R), {
416
477
  class: "fg-progress__indicator",
417
478
  style: f({ transform: `translateX(-${100 - i.value}%)` })
418
479
  }, null, 8, ["style"])]),
@@ -423,7 +484,7 @@ var U = /* @__PURE__ */ c({
423
484
  "model-value"
424
485
  ]));
425
486
  }
426
- }), De = { class: "fg-radio-card__content" }, Oe = /* @__PURE__ */ c({
487
+ }), ze = { class: "fg-radio-card__content" }, Be = /* @__PURE__ */ c({
427
488
  __name: "UiRadioCard",
428
489
  props: {
429
490
  disabled: {
@@ -433,19 +494,19 @@ var U = /* @__PURE__ */ c({
433
494
  value: {}
434
495
  },
435
496
  setup(e) {
436
- return (t, r) => (p(), n(b(I), {
497
+ return (t, r) => (h(), n(w(B), {
437
498
  class: "fg-radio-card",
438
499
  disabled: e.disabled,
439
500
  value: e.value
440
501
  }, {
441
- default: C(() => [r[0] ||= a("span", {
502
+ default: D(() => [r[0] ||= a("span", {
442
503
  class: "fg-radio-card__indicator",
443
504
  "aria-hidden": "true"
444
- }, null, -1), a("span", De, [_(t.$slots, "default")])]),
505
+ }, null, -1), a("span", ze, [b(t.$slots, "default")])]),
445
506
  _: 3
446
507
  }, 8, ["disabled", "value"]));
447
508
  }
448
- }), ke = /* @__PURE__ */ c({
509
+ }), Ve = /* @__PURE__ */ c({
449
510
  __name: "UiRadioCardGroup",
450
511
  props: /*@__PURE__*/ u({
451
512
  disabled: {
@@ -463,7 +524,7 @@ var U = /* @__PURE__ */ c({
463
524
  }),
464
525
  emits: ["update:modelValue"],
465
526
  setup(e) {
466
- let t = x(e, "modelValue"), r = (t) => {
527
+ let t = E(e, "modelValue"), r = (t) => {
467
528
  if (t.altKey || t.ctrlKey || t.metaKey || t.shiftKey) return;
468
529
  let n = t.currentTarget, r = t.target;
469
530
  if (!(n instanceof HTMLElement) || !(r instanceof HTMLElement)) return;
@@ -479,14 +540,14 @@ var U = /* @__PURE__ */ c({
479
540
  let d = o[u];
480
541
  !d || d === r || (t.preventDefault(), d.focus(), d.click());
481
542
  };
482
- return (i, a) => (p(), n(b(L), d({
543
+ return (i, a) => (h(), n(w(V), d({
483
544
  modelValue: t.value,
484
545
  "onUpdate:modelValue": a[0] ||= (e) => t.value = e,
485
546
  class: "fg-radio-card-group",
486
547
  disabled: e.disabled,
487
548
  loop: e.loop
488
549
  }, e.orientation === void 0 ? {} : { orientation: e.orientation }, { onKeydown: r }), {
489
- default: C(() => [_(i.$slots, "default")]),
550
+ default: D(() => [b(i.$slots, "default")]),
490
551
  _: 3
491
552
  }, 16, [
492
553
  "modelValue",
@@ -494,7 +555,7 @@ var U = /* @__PURE__ */ c({
494
555
  "loop"
495
556
  ]));
496
557
  }
497
- }), Ae = /* @__PURE__ */ c({
558
+ }), $ = /* @__PURE__ */ c({
498
559
  __name: "UiSelect",
499
560
  props: {
500
561
  disabled: {
@@ -514,19 +575,19 @@ var U = /* @__PURE__ */ c({
514
575
  let r = l(Q, void 0), i = t, a = (e) => {
515
576
  e.currentTarget instanceof HTMLSelectElement && i("update:modelValue", e.currentTarget.value);
516
577
  };
517
- return (t, i) => (p(), n(b(N), {
518
- id: b(r)?.controlId.value ?? e.id,
578
+ return (t, i) => (h(), n(w(L), {
579
+ id: w(r)?.controlId.value ?? e.id,
519
580
  as: "select",
520
581
  class: "fg-select",
521
- "aria-describedby": b(r)?.descriptionId.value,
522
- "aria-errormessage": b(r)?.errorId.value,
523
- "aria-invalid": e.invalid || b(r)?.invalid.value ? "true" : void 0,
582
+ "aria-describedby": w(r)?.descriptionId.value,
583
+ "aria-errormessage": w(r)?.errorId.value,
584
+ "aria-invalid": e.invalid || w(r)?.invalid.value ? "true" : void 0,
524
585
  "data-size": e.size,
525
586
  disabled: e.disabled,
526
587
  value: e.modelValue,
527
588
  onChange: a
528
589
  }, {
529
- default: C(() => [_(t.$slots, "default")]),
590
+ default: D(() => [b(t.$slots, "default")]),
530
591
  _: 3
531
592
  }, 8, [
532
593
  "id",
@@ -538,7 +599,7 @@ var U = /* @__PURE__ */ c({
538
599
  "value"
539
600
  ]));
540
601
  }
541
- }), je = /* @__PURE__ */ c({
602
+ }), He = /* @__PURE__ */ c({
542
603
  __name: "UiSlider",
543
604
  props: {
544
605
  disabled: {
@@ -571,7 +632,7 @@ var U = /* @__PURE__ */ c({
571
632
  e !== void 0 && o("update:modelValue", e);
572
633
  }
573
634
  });
574
- return (t, r) => (p(), n(b(z), {
635
+ return (t, r) => (h(), n(w(U), {
575
636
  modelValue: c.value,
576
637
  "onUpdate:modelValue": r[0] ||= (e) => c.value = e,
577
638
  class: "fg-slider",
@@ -581,15 +642,15 @@ var U = /* @__PURE__ */ c({
581
642
  min: a.value.min,
582
643
  step: a.value.step
583
644
  }, {
584
- default: C(() => [s(b(V), { class: "fg-slider__track" }, {
585
- default: C(() => [s(b(R), { class: "fg-slider__range" })]),
645
+ default: D(() => [s(w(G), { class: "fg-slider__track" }, {
646
+ default: D(() => [s(w(H), { class: "fg-slider__range" })]),
586
647
  _: 1
587
- }), s(b(B), {
588
- id: b(i)?.controlId.value ?? e.id,
648
+ }), s(w(W), {
649
+ id: w(i)?.controlId.value ?? e.id,
589
650
  class: "fg-slider__thumb",
590
- "aria-describedby": b(i)?.descriptionId.value,
591
- "aria-errormessage": b(i)?.errorId.value,
592
- "aria-invalid": b(i)?.invalid.value ? "true" : void 0,
651
+ "aria-describedby": w(i)?.descriptionId.value,
652
+ "aria-errormessage": w(i)?.errorId.value,
653
+ "aria-invalid": w(i)?.invalid.value ? "true" : void 0,
593
654
  "aria-label": e.label
594
655
  }, null, 8, [
595
656
  "id",
@@ -608,22 +669,118 @@ var U = /* @__PURE__ */ c({
608
669
  "step"
609
670
  ]));
610
671
  }
611
- }), Me = /* @__PURE__ */ c({
672
+ }), Ue = { class: "fg-tabs" }, We = ["aria-label"], Ge = [
673
+ "id",
674
+ "aria-controls",
675
+ "aria-selected",
676
+ "tabindex",
677
+ "onClick",
678
+ "onFocus"
679
+ ], Ke = [
680
+ "id",
681
+ "role",
682
+ "aria-labelledby",
683
+ "hidden",
684
+ "tabindex"
685
+ ], qe = ["id", "hidden"], Je = /* @__PURE__ */ c({
686
+ __name: "UiTabs",
687
+ props: {
688
+ items: {},
689
+ label: {},
690
+ modelValue: {}
691
+ },
692
+ emits: ["update:modelValue"],
693
+ setup(n, { emit: o }) {
694
+ let s = o, c = T(), l = v(!1), u = t(() => n.items.some((e) => e.value === n.modelValue) ? n.modelValue : n.items[0]?.value), d = (e) => `${c}-tab-${encodeURIComponent(e)}`, f = (e) => `${c}-panel-${encodeURIComponent(e)}`, m = (e) => `${c}-heading-${encodeURIComponent(e)}`;
695
+ p(() => {
696
+ l.value = !0;
697
+ });
698
+ let g = (e) => {
699
+ e !== u.value && s("update:modelValue", e);
700
+ }, _ = (e) => {
701
+ if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || ![
702
+ "ArrowLeft",
703
+ "ArrowRight",
704
+ "Home",
705
+ "End"
706
+ ].includes(e.key)) return;
707
+ let t = e.currentTarget, n = e.target;
708
+ if (!(t instanceof HTMLElement) || !(n instanceof HTMLButtonElement)) return;
709
+ let r = [...t.querySelectorAll("[role=\"tab\"]")], i = r.indexOf(n);
710
+ if (i < 0 || r.length === 0) return;
711
+ let a = getComputedStyle(t).direction === "rtl", o = e.key === (a ? "ArrowRight" : "ArrowLeft"), s = r[e.key === "Home" ? 0 : e.key === "End" ? r.length - 1 : (i + (o ? -1 : 1) + r.length) % r.length];
712
+ s && (e.preventDefault(), s.focus());
713
+ };
714
+ return (t, o) => (h(), i("div", Ue, [l.value ? (h(), i("div", {
715
+ key: 0,
716
+ class: "fg-tabs__list",
717
+ role: "tablist",
718
+ "aria-label": n.label,
719
+ onKeydown: _
720
+ }, [(h(!0), i(e, null, y(n.items, (e) => (h(), i("button", {
721
+ id: d(e.value),
722
+ key: e.value,
723
+ type: "button",
724
+ class: "fg-tabs__tab",
725
+ role: "tab",
726
+ "aria-controls": f(e.value),
727
+ "aria-selected": e.value === u.value,
728
+ tabindex: e.value === u.value ? 0 : -1,
729
+ onClick: (t) => g(e.value),
730
+ onFocus: (t) => g(e.value)
731
+ }, C(e.label), 41, Ge))), 128))], 40, We)) : r("", !0), (h(!0), i(e, null, y(n.items, (e) => (h(), i("section", {
732
+ id: f(e.value),
733
+ key: e.value,
734
+ class: "fg-tabs__panel",
735
+ role: l.value ? "tabpanel" : void 0,
736
+ "aria-labelledby": l.value ? d(e.value) : m(e.value),
737
+ hidden: l.value && e.value !== u.value,
738
+ tabindex: l.value ? 0 : void 0
739
+ }, [a("h3", {
740
+ id: m(e.value),
741
+ class: "fg-tabs__heading",
742
+ hidden: l.value
743
+ }, C(e.label), 9, qe), b(t.$slots, "panel", { value: e.value })], 8, Ke))), 128))]));
744
+ }
745
+ }), Ye = /* @__PURE__ */ c({
612
746
  __name: "UiSurface",
613
747
  props: {
614
748
  as: { default: "section" },
615
749
  padding: { default: "default" }
616
750
  },
617
751
  setup(e) {
618
- return (t, r) => (p(), n(b(N), {
752
+ return (t, r) => (h(), n(w(L), {
619
753
  as: e.as,
620
754
  class: "fg-surface",
621
755
  "data-padding": e.padding
622
756
  }, {
623
- default: C(() => [_(t.$slots, "default")]),
757
+ default: D(() => [b(t.$slots, "default")]),
624
758
  _: 3
625
759
  }, 8, ["as", "data-padding"]));
626
760
  }
627
- });
761
+ }), Xe = ({ targetIds: e }) => {
762
+ let t = v(e[0]), n, r, i;
763
+ return p(() => {
764
+ let a = e.flatMap((e) => {
765
+ let t = document.getElementById(e);
766
+ return t ? [{
767
+ id: e,
768
+ element: t
769
+ }] : [];
770
+ }), o = () => {
771
+ n = void 0;
772
+ let e = document.documentElement, r = Number.parseFloat(getComputedStyle(e).scrollPaddingTop) || 0, i = a[0]?.id;
773
+ for (let e of a) e.element.getBoundingClientRect().top <= r + 1 && (i = e.id);
774
+ window.scrollY > 0 && window.scrollY + window.innerHeight >= e.scrollHeight - 2 && (i = a.at(-1)?.id), t.value = i;
775
+ }, s = () => {
776
+ n === void 0 && (n = requestAnimationFrame(o));
777
+ };
778
+ window.addEventListener("scroll", s, { passive: !0 }), window.addEventListener("resize", s), window.addEventListener("pageshow", s), document.body.addEventListener("transitionend", s), typeof ResizeObserver < "u" && (r = new ResizeObserver(s), r.observe(document.body)), i = () => {
779
+ window.removeEventListener("scroll", s), window.removeEventListener("resize", s), window.removeEventListener("pageshow", s), document.body.removeEventListener("transitionend", s);
780
+ }, o();
781
+ }), m(() => {
782
+ i?.(), r?.disconnect(), n !== void 0 && cancelAnimationFrame(n);
783
+ }), _(t);
784
+ };
628
785
  //#endregion
629
- export { de as UiAlert, fe as UiBadge, U as UiButton, Y as UiCard, he as UiCheckbox, ue as UiCodeBlock, ye as UiDialog, Ce as UiField, we as UiInput, Ee as UiProgress, Oe as UiRadioCard, ke as UiRadioCardGroup, Ae as UiSelect, je as UiSlider, Me as UiSurface };
786
+ export { ye as UiAlert, be as UiBadge, q as UiButton, ie as UiCard, Ce as UiCheckbox, ve as UiCodeBlock, De as UiDialog, Ae as UiDrawer, Fe as UiField, Ie as UiInput, Re as UiProgress, Be as UiRadioCard, Ve as UiRadioCardGroup, $ as UiSelect, He as UiSlider, Ye as UiSurface, Je as UiTabs, Xe as useActiveSection };
@@ -0,0 +1,7 @@
1
+ import type { Ref } from 'vue';
2
+ export interface ActiveSectionOptions {
3
+ /** Section IDs in page order. Missing elements are ignored after mounting. */
4
+ targetIds: readonly string[];
5
+ }
6
+ /** Tracks the last section heading above the document's scroll-padding inset. */
7
+ export declare const useActiveSection: ({ targetIds }: ActiveSectionOptions) => Readonly<Ref<string | undefined>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@filipgutica/ui",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "A compact Vue design system with Tailwind CSS and VS Code theme support",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -10,6 +10,7 @@
10
10
  "dist",
11
11
  "src/theme.css",
12
12
  "src/components.css",
13
+ "src/components/*.css",
13
14
  "README.md",
14
15
  "LICENSE"
15
16
  ],
@@ -0,0 +1,100 @@
1
+ @layer components {
2
+ .fg-drawer__overlay {
3
+ position: fixed;
4
+ inset: 0;
5
+ z-index: 50;
6
+ background: var(--color-overlay);
7
+ }
8
+
9
+ .fg-drawer__content {
10
+ position: fixed;
11
+ inset-block: 0;
12
+ left: 0;
13
+ z-index: 51;
14
+ display: grid;
15
+ grid-template-rows: auto minmax(0, 1fr);
16
+ width: min(calc(var(--sidebar) + var(--space-12)), calc(100vw - var(--space-12)));
17
+ height: 100vh;
18
+ height: 100dvh;
19
+ overflow: hidden;
20
+ border-right: 1px solid var(--color-border-subtle);
21
+ color: var(--color-text);
22
+ background: var(--color-surface-raised);
23
+ box-shadow: var(--shadow-dialog);
24
+ font-family: var(--font-ui);
25
+ font-size: var(--text-md);
26
+ font-weight: var(--weight-regular);
27
+ line-height: var(--leading);
28
+ }
29
+
30
+ .fg-drawer__header {
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: space-between;
34
+ gap: var(--space-3);
35
+ padding: var(--space-3) var(--space-4);
36
+ border-bottom: 1px solid var(--color-border-subtle);
37
+ }
38
+
39
+ .fg-drawer__title {
40
+ min-width: 0;
41
+ margin: 0;
42
+ font-size: var(--text-md);
43
+ font-weight: var(--weight-medium);
44
+ overflow-wrap: anywhere;
45
+ }
46
+
47
+ .fg-drawer__close {
48
+ display: inline-grid;
49
+ flex: none;
50
+ min-width: var(--control-lg);
51
+ min-height: var(--control-lg);
52
+ padding: 0;
53
+ place-items: center;
54
+ border: 0;
55
+ border-radius: var(--radius-control);
56
+ color: var(--color-muted);
57
+ background: transparent;
58
+ font: inherit;
59
+ font-size: var(--text-xl);
60
+ cursor: pointer;
61
+ }
62
+
63
+ .fg-drawer__close:hover {
64
+ color: var(--color-text);
65
+ background: var(--color-row-hover);
66
+ }
67
+
68
+ .fg-drawer__body {
69
+ min-height: 0;
70
+ overflow: auto;
71
+ overscroll-behavior: contain;
72
+ padding: var(--space-4);
73
+ }
74
+
75
+ .fg-drawer__close:focus-visible,
76
+ .fg-drawer__content:focus-visible {
77
+ outline: 2px solid var(--color-focus);
78
+ outline-offset: 2px;
79
+ }
80
+
81
+ .fg-drawer__overlay[data-state="open"] { animation: fg-drawer-fade-in var(--duration) var(--ease-out); }
82
+ .fg-drawer__overlay[data-state="closed"] { animation: fg-drawer-fade-out var(--duration-fast) var(--ease-out); }
83
+ .fg-drawer__content[data-state="open"] { animation: fg-drawer-slide-in var(--duration) var(--ease-out); }
84
+ .fg-drawer__content[data-state="closed"] { animation: fg-drawer-slide-out var(--duration-fast) var(--ease-out); }
85
+
86
+ @keyframes fg-drawer-fade-in { from { opacity: 0; } to { opacity: 1; } }
87
+ @keyframes fg-drawer-fade-out { from { opacity: 1; } to { opacity: 0; } }
88
+ @keyframes fg-drawer-slide-in { from { transform: translateX(-100%); } to { transform: translateX(0); } }
89
+ @keyframes fg-drawer-slide-out { from { transform: translateX(0); } to { transform: translateX(-100%); } }
90
+
91
+ :root.high-contrast .fg-drawer__content {
92
+ border-width: 2px;
93
+ border-color: var(--color-border);
94
+ }
95
+
96
+ @media (prefers-reduced-motion: reduce) {
97
+ .fg-drawer__overlay,
98
+ .fg-drawer__content { animation: none; }
99
+ }
100
+ }
@@ -0,0 +1,52 @@
1
+ @layer components {
2
+ .fg-tabs {
3
+ min-width: 0;
4
+ font-family: var(--font-ui);
5
+ font-size: var(--text-md);
6
+ line-height: var(--leading);
7
+ }
8
+
9
+ .fg-tabs__list {
10
+ display: flex;
11
+ max-width: 100%;
12
+ overflow-x: auto;
13
+ border-bottom: 1px solid var(--color-border-subtle);
14
+ }
15
+
16
+ .fg-tabs__tab {
17
+ flex: none;
18
+ min-height: var(--control-md);
19
+ padding: var(--space-2) var(--space-3);
20
+ border: 0;
21
+ border-bottom: 2px solid transparent;
22
+ color: var(--color-muted);
23
+ background: transparent;
24
+ font: inherit;
25
+ white-space: nowrap;
26
+ cursor: pointer;
27
+ }
28
+
29
+ .fg-tabs__tab:hover { color: var(--color-text); }
30
+ .fg-tabs__tab[aria-selected="true"] {
31
+ border-bottom-color: var(--color-link);
32
+ color: var(--color-text);
33
+ font-weight: var(--weight-medium);
34
+ }
35
+
36
+ .fg-tabs__panel { min-width: 0; }
37
+ .fg-tabs__panel[hidden], .fg-tabs__heading[hidden] { display: none; }
38
+ .fg-tabs__heading {
39
+ margin-block: var(--space-4) var(--space-2);
40
+ font-size: var(--text-md);
41
+ font-weight: var(--weight-medium);
42
+ }
43
+
44
+ .fg-tabs__tab:focus-visible, .fg-tabs__panel:focus-visible {
45
+ outline: 2px solid var(--color-focus);
46
+ outline-offset: -2px;
47
+ }
48
+
49
+ @media (max-width: 640px) {
50
+ .fg-tabs__tab { min-height: var(--control-lg); }
51
+ }
52
+ }
@@ -1,3 +1,6 @@
1
+ @import './components/drawer.css';
2
+ @import './components/tabs.css';
3
+
1
4
  @layer components {
2
5
  /* Package-owned typography keeps controls consistent across consuming apps. */
3
6
  .fg-button, .fg-input, .fg-select, .fg-checkbox, .fg-field,
@@ -34,7 +37,10 @@
34
37
  .fg-code-block__copy {
35
38
  display: inline-flex;
36
39
  align-items: center;
40
+ justify-content: center;
37
41
  gap: 0.4rem;
42
+ min-width: var(--control-lg);
43
+ min-height: var(--control-lg);
38
44
  padding: 0.25rem 0.4rem;
39
45
  border: 0;
40
46
  border-radius: 0.25rem;
@@ -55,6 +61,51 @@
55
61
  .fg-code-block[data-wrap="true"] .fg-code-block__text { min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
56
62
  .fg-code-block__status:empty { display: none; }
57
63
  .fg-code-block__status:not(:empty) { display: block; padding: 0.5rem 0.75rem; font-size: 0.75rem; }
64
+ .fg-code-block[data-variant="compact"] {
65
+ position: relative;
66
+ display: grid;
67
+ grid-template-columns: minmax(0, 1fr) auto;
68
+ align-items: center;
69
+ }
70
+ .fg-code-block[data-variant="compact"] .fg-code-block__header {
71
+ grid-column: 2;
72
+ grid-row: 1;
73
+ min-height: 0;
74
+ padding: var(--space-1);
75
+ border: 0;
76
+ background: transparent;
77
+ }
78
+ .fg-code-block[data-variant="compact"] .fg-code-block__actions {
79
+ gap: var(--space-1);
80
+ margin-left: 0;
81
+ }
82
+ .fg-code-block[data-variant="compact"] .fg-code-block__copy {
83
+ padding: 0;
84
+ border-radius: var(--radius-control);
85
+ }
86
+ .fg-code-block[data-variant="compact"] .fg-code-block__pre {
87
+ grid-column: 1;
88
+ grid-row: 1;
89
+ min-width: 0;
90
+ padding: var(--space-2) var(--space-3);
91
+ }
92
+ .fg-code-block[data-variant="compact"] .fg-code-block__status:not(:empty) {
93
+ grid-column: 1 / -1;
94
+ padding: var(--space-2) var(--space-3);
95
+ font-size: var(--text-sm);
96
+ overflow-wrap: anywhere;
97
+ }
98
+ .fg-code-block[data-variant="compact"] .fg-code-block__status[data-state="success"] {
99
+ position: absolute;
100
+ top: 0;
101
+ left: 0;
102
+ width: 1px;
103
+ height: 1px;
104
+ padding: 0;
105
+ overflow: hidden;
106
+ clip-path: inset(50%);
107
+ white-space: nowrap;
108
+ }
58
109
  .fg-button,
59
110
  .fg-input,
60
111
  .fg-select,