@fastrd/ui-vue 0.1.1 → 0.2.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/NOTICE.md CHANGED
@@ -11,8 +11,9 @@ value/options/change contract and delegates interaction to the native select.
11
11
 
12
12
  The palette follows FastWrite's semantic colors and the approved FastTask
13
13
  workbench design. Only generic UI code and semantic design values are included.
14
- No private artwork, icons, logos, fonts, application data, product-specific
15
- workflows or brand assets are distributed. Upstream product repositories
14
+ No private artwork, icons, logos, application data, product-specific workflows
15
+ or brand assets are distributed in the UI packages. Documentation fonts are
16
+ licensed separately as described below. Upstream product repositories
16
17
  retain ownership of their application-specific code and assets.
17
18
 
18
19
  Source reference revisions: FastPPT `f6bf21f13445c2507719928fa68550552a8fcab2`
@@ -21,3 +22,34 @@ Source reference revisions: FastPPT `f6bf21f13445c2507719928fa68550552a8fcab2`
21
22
  At both revisions, `primitives.ts` has Git blob
22
23
  `c7123af5f8316bc0486bce5c99cfbb2e9ab3dc73` and `use-dialog-focus.ts` has
23
24
  Git blob `3ffbb6c6c7bd5e3c96f2b8c05c5e008dcdfc4cab`.
25
+
26
+ # FastUI notices
27
+
28
+ FastUI is an MIT-licensed design foundation and component library extracted from
29
+ shared interface work in FastR-D products. Product data, authentication, assets
30
+ and application workflows are not included.
31
+
32
+ The design aligns with the 2026-11-11 FastWrite workbench: compact geometry,
33
+ semantic light/dark colors, typography, flat tabs, project lists and focus states.
34
+
35
+ Design research used the fixed checkouts in FastWrite/reference:
36
+
37
+ - assistant-ui/assistant-ui (MIT), f92028b466cba721846de6851e4dddb6c04ecfad:
38
+ composable surfaces and separation of display from runtime state.
39
+ - shadcn-ui/ui (MIT), 8ab27715521cbd4f87ae6886cd989cbb678f6de7:
40
+ semantic tokens, variants and explicit focus/disabled states.
41
+ - radix-ui/primitives (MIT), eb3a2b7091293e27df8f9ba906632d8daa3d81a3:
42
+ keyboard navigation, disabled-item handling and focus order.
43
+ - nextlevelbuilder/ui-ux-pro-max-skill (MIT), 6ae86f61f520d6ca229d2f40e070410638c908c5:
44
+ navigation, deep links and reduced-motion guidance.
45
+ - ayaka-notes/ayakaleaf-pro (AGPL-3.0), 6ce22039d19b516354dff2a8508dd67515ded9f0:
46
+ project-list layout and interaction research only.
47
+
48
+ The workbench implementations are original. No source from these reference libraries,
49
+ including the AGPL project, is copied into the distributed MIT packages. Exact
50
+ repository URLs and revisions are recorded in docs/reference-sources.json.
51
+
52
+ The documentation site self-hosts IBM Plex Sans Variable 5.3.0, Noto Sans SC
53
+ Variable 5.3.1 and JetBrains Mono Variable 5.3.0 through Fontsource. These fonts use
54
+ SIL Open Font License 1.1; their original license texts are shipped with the
55
+ site under docs/public/licenses/fonts/. Font files are not shipped in UI packages.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @fastrd/ui-vue
2
2
 
3
- MIT · 0.1.1 · Vue ^3.5 peer · exact foundation 0.1.1 registry dependency.
3
+ MIT · 0.2.0 · Vue ^3.5 peer · exact foundation 0.2.0 registry dependency.
4
4
 
5
5
  ```vue
6
6
  <script setup>
@@ -49,3 +49,31 @@ modal behavior replaces the original custom focus trap.
49
49
 
50
50
  Class names use fastui-*; no legacy fp-* aliases or application brands are
51
51
  included. Application layouts and preview styles stay with the host.
52
+
53
+
54
+ ## Workbench components
55
+
56
+ `TabBar`: `tabs`, `activeId`, accessible `label`, `onSelect` (Vue `@select`),
57
+ optional `onClose` (Vue `@close`), `closeLabel(tab)` and `dirtyLabel`.
58
+ Each TabItem has a unique DOM `id`, string `label`, optional `controls`,
59
+ `disabled`, `dirty`, `preview`. Host panels use `role="tabpanel"`, `id=controls`
60
+ and `aria-labelledby=activeId`; host owns visibility and active selection after
61
+ close. Automatic activation uses Left/Right, Home/End, RTL and disabled skipping.
62
+ Closing restores focus to the remaining tabbable tab. Empty/all-disabled lists
63
+ have no tabbable tab. Use unique ids across every instance.
64
+
65
+ `SegmentedControl`: `options` ({value,label,disabled?}), `value`, `label`,
66
+ `onChange` (Vue `@change`). A controlled mutually exclusive pressed-button group;
67
+ uses normal Tab order, not radio semantics.
68
+
69
+ `Toolbar` requires `label`; it groups mixed actions/fields with native Tab order.
70
+ `StatusBar` defaults role=status. `Section`, `ListRow`, `Divider` provide surfaces,
71
+ row separators and structure. ListRow is a noninteractive div by default; the
72
+ host owns selection, links, data, search and sorting.
73
+
74
+ Compact control sizes xs/sm/md are 24/25/28px. Typography uses the FastWrite
75
+ Plex/Noto stack with system fallback; fonts are loaded by the host, not the package.
76
+
77
+ `Alert` and `Badge`: tone info/success/warning/danger. Alert defaults role=status,
78
+ or alert for danger. `Loading` exposes a status region and label (default Loading).
79
+ `EmptyState` takes title, default content and an optional `#action` slot.
@@ -0,0 +1,13 @@
1
+ import { type FunctionalComponent, type HTMLAttributes } from 'vue';
2
+ export type Tone = 'info' | 'success' | 'warning' | 'danger';
3
+ export interface FeedbackProps extends HTMLAttributes {
4
+ tone?: Tone;
5
+ }
6
+ export declare const Alert: FunctionalComponent<FeedbackProps>;
7
+ export declare const Badge: FunctionalComponent<FeedbackProps>;
8
+ export declare const EmptyState: FunctionalComponent<HTMLAttributes & {
9
+ title: string;
10
+ }>;
11
+ export declare const Loading: FunctionalComponent<HTMLAttributes & {
12
+ label?: string;
13
+ }>;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from './primitives.js';
2
2
  export { Modal, Select, useDialogFocus } from './interactive.js';
3
3
  export type { ModalProps, SelectProps, SelectItem } from './interactive.js';
4
+ export * from './workbench.js';
5
+ export * from './feedback.js';
package/dist/index.js CHANGED
@@ -106,6 +106,12 @@ var SelectOption = element("SelectOption", "option");
106
106
  var RichText = (props, { attrs }) => h("div", mergeProps(attrs, { innerHTML: props.html }));
107
107
  RichText.props = ["html"];
108
108
  RichText.inheritAttrs = false;
109
+ var Section = element("Section", "section", "fastui-section");
110
+ var StatusBar = (props, { attrs, slots }) => h(props.as ?? "div", mergeProps({ role: "status" }, attrs, { class: "fastui-statusbar" }), slots.default?.());
111
+ StatusBar.props = ["as"];
112
+ StatusBar.inheritAttrs = false;
113
+ var Divider = element("Divider", "hr", "fastui-divider");
114
+ var ListRow = element("ListRow", "div", "fastui-list-row");
109
115
 
110
116
  // packages/vue/src/interactive.ts
111
117
  import {
@@ -193,9 +199,88 @@ Select.props = {
193
199
  onChange: Function
194
200
  };
195
201
  Select.inheritAttrs = false;
202
+
203
+ // packages/vue/src/workbench.ts
204
+ import { h as h3, mergeProps as mergeProps3, nextTick } from "vue";
205
+ import { tabNavigationIndex } from "@fastrd/ui-foundation";
206
+ var TabBar = (props, { attrs }) => {
207
+ const focusId = props.tabs.find((tab) => tab.id === props.activeId && !tab.disabled)?.id ?? props.tabs.find((tab) => !tab.disabled)?.id;
208
+ return h3("div", mergeProps3(attrs, { class: ["fastui-tabs", props.onClose && "fastui-tabs--closable"], role: "group", "aria-label": `${props.label} controls` }), [
209
+ h3("div", { role: "tablist", "aria-label": props.label, "aria-owns": props.tabs.map((tab) => tab.id).join(" ") || void 0 }),
210
+ ...props.tabs.map((tab, index) => h3("div", { key: tab.id, class: "fastui-tab-item" }, [
211
+ h3("button", {
212
+ type: "button",
213
+ id: tab.id,
214
+ role: "tab",
215
+ disabled: tab.disabled,
216
+ "aria-selected": tab.id === props.activeId,
217
+ "aria-controls": tab.controls,
218
+ tabindex: tab.id === focusId ? 0 : -1,
219
+ class: ["fastui-tab", tab.preview && "fastui-tab--preview"],
220
+ onClick: () => props.onSelect(tab.id),
221
+ onKeydown: (event) => {
222
+ if (event.isComposing) return;
223
+ const target = event.currentTarget;
224
+ const next = tabNavigationIndex(event.key, index, props.tabs, getComputedStyle(target).direction === "rtl");
225
+ if (next < 0) return;
226
+ event.preventDefault();
227
+ props.onSelect(props.tabs[next].id);
228
+ const button = target.closest(".fastui-tabs")?.querySelectorAll('[role="tab"]')[next];
229
+ button?.focus();
230
+ button?.scrollIntoView({ block: "nearest", inline: "nearest" });
231
+ }
232
+ }, [h3("span", { class: "fastui-tab-label" }, tab.label), tab.dirty ? h3("span", { class: "fastui-tab-dirty", role: "img", "aria-label": props.dirtyLabel ?? "Unsaved changes" }) : null]),
233
+ props.onClose ? h3("button", {
234
+ type: "button",
235
+ class: "fastui-tab-close",
236
+ disabled: tab.disabled,
237
+ "aria-label": props.closeLabel?.(tab) ?? `Close ${tab.label}`,
238
+ onClick: (event) => {
239
+ const root = event.currentTarget.closest(".fastui-tabs");
240
+ props.onClose?.(tab.id);
241
+ void nextTick(() => {
242
+ if (root.isConnected) root.querySelector('[role="tab"][tabindex="0"]')?.focus();
243
+ });
244
+ }
245
+ }, "\xD7") : null
246
+ ]))
247
+ ]);
248
+ };
249
+ TabBar.props = ["tabs", "activeId", "onSelect", "label", "onClose", "closeLabel", "dirtyLabel"];
250
+ TabBar.inheritAttrs = false;
251
+ var SegmentedControl = (props, { attrs }) => h3("div", mergeProps3(attrs, { class: "fastui-segments", role: "group", "aria-label": props.label }), props.options.map((option) => h3("button", {
252
+ type: "button",
253
+ key: option.value,
254
+ class: "fastui-segment",
255
+ disabled: option.disabled,
256
+ "aria-pressed": option.value === props.value,
257
+ onClick: () => props.onChange(option.value)
258
+ }, option.label)));
259
+ SegmentedControl.props = ["options", "value", "onChange", "label"];
260
+ SegmentedControl.inheritAttrs = false;
261
+ var Toolbar = (props, { attrs, slots }) => h3("div", mergeProps3(attrs, { class: "fastui-toolbar", role: "group", "aria-label": props.label }), slots.default?.());
262
+ Toolbar.props = ["label"];
263
+ Toolbar.inheritAttrs = false;
264
+
265
+ // packages/vue/src/feedback.ts
266
+ import { h as h4, mergeProps as mergeProps4 } from "vue";
267
+ var Alert = (props, { attrs, slots }) => h4("div", mergeProps4({ role: props.tone === "danger" ? "alert" : "status" }, attrs, { class: ["fastui-alert", `fastui-tone-${props.tone ?? "info"}`] }), slots.default?.());
268
+ Alert.props = ["tone"];
269
+ Alert.inheritAttrs = false;
270
+ var Badge = (props, { attrs, slots }) => h4("span", mergeProps4(attrs, { class: ["fastui-badge", `fastui-tone-${props.tone ?? "info"}`] }), slots.default?.());
271
+ Badge.props = ["tone"];
272
+ Badge.inheritAttrs = false;
273
+ var EmptyState = (props, { attrs, slots }) => h4("div", mergeProps4(attrs, { class: "fastui-empty" }), [h4("h2", props.title), slots.default?.(), slots.action?.()]);
274
+ EmptyState.props = ["title"];
275
+ EmptyState.inheritAttrs = false;
276
+ var Loading = (props, { attrs }) => h4("div", mergeProps4({ role: "status" }, attrs, { class: "fastui-loading" }), [h4("span", { class: "fastui-spinner", "aria-hidden": "true" }), props.label ?? "Loading"]);
277
+ Loading.props = ["label"];
278
+ Loading.inheritAttrs = false;
196
279
  export {
280
+ Alert,
197
281
  Article,
198
282
  Aside,
283
+ Badge,
199
284
  Box,
200
285
  Button,
201
286
  Description,
@@ -203,6 +288,8 @@ export {
203
288
  DialogSurface,
204
289
  Disclosure,
205
290
  DisclosureTrigger,
291
+ Divider,
292
+ EmptyState,
206
293
  Field,
207
294
  Fieldset,
208
295
  Footer,
@@ -215,6 +302,8 @@ export {
215
302
  Link,
216
303
  List,
217
304
  ListItem,
305
+ ListRow,
306
+ Loading,
218
307
  Main,
219
308
  Modal,
220
309
  ModalBody,
@@ -222,11 +311,15 @@ export {
222
311
  Panel,
223
312
  Progress,
224
313
  RichText,
314
+ Section,
315
+ SegmentedControl,
225
316
  Select,
226
317
  SelectField,
227
318
  SelectOption,
319
+ StatusBar,
228
320
  Svg,
229
321
  SvgPath,
322
+ TabBar,
230
323
  Table,
231
324
  TableBody,
232
325
  TableCaption,
@@ -238,5 +331,6 @@ export {
238
331
  Text,
239
332
  TextArea,
240
333
  TextField,
334
+ Toolbar,
241
335
  useDialogFocus
242
336
  };
@@ -67,3 +67,7 @@ export declare const SelectOption: FunctionalComponent<ElementProps, {}, any, {}
67
67
  export declare const RichText: FunctionalComponent<HTMLAttributes & {
68
68
  html: string;
69
69
  }>;
70
+ export declare const Section: FunctionalComponent<ElementProps, {}, any, {}>;
71
+ export declare const StatusBar: FunctionalComponent<ElementProps>;
72
+ export declare const Divider: FunctionalComponent<ElementProps, {}, any, {}>;
73
+ export declare const ListRow: FunctionalComponent<ElementProps, {}, any, {}>;
@@ -0,0 +1,34 @@
1
+ import { type FunctionalComponent, type HTMLAttributes } from 'vue';
2
+ export interface TabItem {
3
+ id: string;
4
+ label: string;
5
+ controls?: string;
6
+ disabled?: boolean;
7
+ dirty?: boolean;
8
+ preview?: boolean;
9
+ }
10
+ export interface TabBarProps {
11
+ tabs: readonly TabItem[];
12
+ activeId: string | null;
13
+ onSelect: (id: string) => void;
14
+ label: string;
15
+ onClose?: (id: string) => void;
16
+ closeLabel?: (tab: TabItem) => string;
17
+ dirtyLabel?: string;
18
+ }
19
+ export declare const TabBar: FunctionalComponent<TabBarProps>;
20
+ export interface SegmentedOption {
21
+ value: string;
22
+ label: string;
23
+ disabled?: boolean;
24
+ }
25
+ export interface SegmentedControlProps {
26
+ options: readonly SegmentedOption[];
27
+ value: string;
28
+ onChange: (value: string) => void;
29
+ label: string;
30
+ }
31
+ export declare const SegmentedControl: FunctionalComponent<SegmentedControlProps>;
32
+ export declare const Toolbar: FunctionalComponent<HTMLAttributes & {
33
+ label: string;
34
+ }>;
package/package.json CHANGED
@@ -1,17 +1,37 @@
1
1
  {
2
2
  "name": "@fastrd/ui-vue",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Vue primitives and accessible native controls for FastUI",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
- "repository": {"type":"git","url":"git+https://github.com/FastR-D/FastUI.git","directory":"packages/vue"},
8
- "publishConfig": {"access":"public"},
9
- "files": ["dist", "LICENSE", "README.md", "NOTICE.md"],
10
- "sideEffects": ["**/*.css"],
11
- "dependencies": {"@fastrd/ui-foundation":"0.1.1"},
12
- "peerDependencies": {"vue":"^3.5.0"},
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/FastR-D/FastUI.git",
10
+ "directory": "packages/vue"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "LICENSE",
18
+ "README.md",
19
+ "NOTICE.md"
20
+ ],
21
+ "sideEffects": [
22
+ "**/*.css"
23
+ ],
24
+ "dependencies": {
25
+ "@fastrd/ui-foundation": "0.2.0"
26
+ },
27
+ "peerDependencies": {
28
+ "vue": "^3.5.0"
29
+ },
13
30
  "exports": {
14
- ".": {"types":"./dist/index.d.ts","import":"./dist/index.js"},
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js"
34
+ },
15
35
  "./styles.css": "./dist/styles.css"
16
36
  }
17
37
  }