@aggc/ui 0.7.1 → 0.9.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.
Files changed (48) hide show
  1. package/dist/chunks/DataTable-CIiU5Jx3.js +8688 -0
  2. package/dist/components/DataTable.styles.d.ts +48 -0
  3. package/dist/components/DataTable.types.d.ts +38 -0
  4. package/dist/components/DataTable.vue.d.ts +72 -0
  5. package/dist/components/UiAvatar.styles.d.ts +53 -0
  6. package/dist/components/UiAvatar.vue.d.ts +13 -0
  7. package/dist/components/UiModal.styles.d.ts +31 -0
  8. package/dist/components/UiModal.vue.d.ts +30 -0
  9. package/dist/components/UiToast.styles.d.ts +41 -0
  10. package/dist/components/UiToast.vue.d.ts +13 -0
  11. package/dist/components/UiToastProvider.vue.d.ts +13 -0
  12. package/dist/components/UiTooltip.styles.d.ts +1 -0
  13. package/dist/components/UiTooltip.vue.d.ts +25 -0
  14. package/dist/components/index.d.ts +11 -0
  15. package/dist/components.js +30 -12
  16. package/dist/composables/useToast.d.ts +27 -0
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +82 -63
  19. package/dist/ui.css +995 -294
  20. package/package.json +3 -2
  21. package/src/components/DataTable.styles.ts +493 -0
  22. package/src/components/DataTable.test.ts +249 -0
  23. package/src/components/DataTable.types.ts +42 -0
  24. package/src/components/DataTable.vue +567 -0
  25. package/src/components/UiAvatar.styles.ts +81 -0
  26. package/src/components/UiAvatar.test.ts +43 -0
  27. package/src/components/UiAvatar.vue +41 -0
  28. package/src/components/UiModal.styles.ts +143 -0
  29. package/src/components/UiModal.test.ts +64 -0
  30. package/src/components/UiModal.vue +82 -0
  31. package/src/components/UiToast.styles.ts +143 -0
  32. package/src/components/UiToast.test.ts +47 -0
  33. package/src/components/UiToast.vue +65 -0
  34. package/src/components/UiToastProvider.vue +22 -0
  35. package/src/components/UiTooltip.styles.ts +25 -0
  36. package/src/components/UiTooltip.test.ts +37 -0
  37. package/src/components/UiTooltip.vue +37 -0
  38. package/src/components/index.ts +17 -0
  39. package/src/composables/useToast.ts +43 -0
  40. package/src/css/base.css +61 -1
  41. package/src/index.ts +1 -0
  42. package/src/stories/feedback/UiToast.stories.ts +72 -0
  43. package/src/stories/layout/DataTable.stories.ts +141 -0
  44. package/src/stories/layout/UiModal.stories.ts +89 -0
  45. package/src/stories/primitives/UiAvatar.stories.ts +83 -0
  46. package/src/stories/primitives/UiTooltip.stories.ts +46 -0
  47. package/src/stories/support/sources.ts +81 -0
  48. package/dist/chunks/UiSkeleton.vue_vue_type_script_setup_true_lang-DUse1KRc.js +0 -1201
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ TooltipProvider,
4
+ TooltipRoot,
5
+ TooltipTrigger,
6
+ TooltipContent,
7
+ } from "reka-ui";
8
+ import { tooltipContent } from "./UiTooltip.styles";
9
+
10
+ withDefaults(
11
+ defineProps<{
12
+ /** Tooltip text. When empty, renders the trigger bare (no tooltip). */
13
+ content: string;
14
+ side?: "top" | "right" | "bottom" | "left";
15
+ /** Open delay in ms. */
16
+ delay?: number;
17
+ }>(),
18
+ {
19
+ side: "top",
20
+ delay: 500,
21
+ },
22
+ );
23
+ </script>
24
+
25
+ <template>
26
+ <TooltipProvider v-if="content" :delay-duration="delay">
27
+ <TooltipRoot>
28
+ <TooltipTrigger as-child>
29
+ <slot />
30
+ </TooltipTrigger>
31
+ <TooltipContent :side="side" :side-offset="6" :class="tooltipContent">
32
+ {{ content }}
33
+ </TooltipContent>
34
+ </TooltipRoot>
35
+ </TooltipProvider>
36
+ <slot v-else />
37
+ </template>
@@ -2,10 +2,27 @@ export { default as PageSurface } from "./PageSurface.vue";
2
2
  export { default as ResultPanel } from "./ResultPanel.vue";
3
3
  export { default as SectionCard } from "./SectionCard.vue";
4
4
  export { default as StatusBadge } from "./StatusBadge.vue";
5
+ export { default as UiAvatar } from "./UiAvatar.vue";
5
6
  export { default as UiButton } from "./UiButton.vue";
6
7
  export { default as UiCheckbox } from "./UiCheckbox.vue";
7
8
  export { default as UiField } from "./UiField.vue";
8
9
  export { default as UiLoadingState } from "./UiLoadingState.vue";
10
+ export { default as UiModal } from "./UiModal.vue";
9
11
  export { default as UiSegmentedControl } from "./UiSegmentedControl.vue";
10
12
  export { default as UiSelect } from "./UiSelect.vue";
11
13
  export { default as UiSkeleton } from "./UiSkeleton.vue";
14
+ export { default as UiToast } from "./UiToast.vue";
15
+ export { default as UiToastProvider } from "./UiToastProvider.vue";
16
+ export { default as UiTooltip } from "./UiTooltip.vue";
17
+ export { default as DataTable } from "./DataTable.vue";
18
+ export type { DataTableColumn } from "./DataTable.types";
19
+ export {
20
+ DropdownMenuItem,
21
+ DropdownMenuSeparator,
22
+ DropdownMenuRoot,
23
+ DropdownMenuTrigger,
24
+ DropdownMenuContent,
25
+ } from "reka-ui";
26
+ export { actionMenu, menuBtnIcon } from "./DataTable.styles";
27
+ export { tooltipContent } from "./UiTooltip.styles";
28
+ export { TooltipRoot, TooltipTrigger, TooltipContent, TooltipProvider } from "reka-ui";
@@ -0,0 +1,43 @@
1
+ import { ref } from "vue";
2
+
3
+ export type ToastTone = "success" | "error" | "info" | "warning";
4
+
5
+ export interface ToastItem {
6
+ id: number;
7
+ tone: ToastTone;
8
+ message: string;
9
+ duration: number;
10
+ }
11
+
12
+ const toasts = ref<ToastItem[]>([]);
13
+ let nextId = 0;
14
+
15
+ function addToast(tone: ToastTone, message: string, duration = 4000) {
16
+ const id = nextId++;
17
+ toasts.value = [...toasts.value, { id, tone, message, duration }];
18
+
19
+ if (duration > 0) {
20
+ setTimeout(() => removeToast(id), duration);
21
+ }
22
+
23
+ return id;
24
+ }
25
+
26
+ function removeToast(id: number) {
27
+ toasts.value = toasts.value.filter((t) => t.id !== id);
28
+ }
29
+
30
+ export function useToast() {
31
+ return {
32
+ toasts,
33
+ success: (message: string, duration?: number) =>
34
+ addToast("success", message, duration),
35
+ error: (message: string, duration?: number) =>
36
+ addToast("error", message, duration),
37
+ info: (message: string, duration?: number) =>
38
+ addToast("info", message, duration),
39
+ warning: (message: string, duration?: number) =>
40
+ addToast("warning", message, duration),
41
+ dismiss: removeToast,
42
+ };
43
+ }
package/src/css/base.css CHANGED
@@ -83,9 +83,69 @@ body {
83
83
  }
84
84
  }
85
85
 
86
+ @keyframes fadeIn {
87
+ from { opacity: 0; }
88
+ to { opacity: 1; }
89
+ }
90
+
91
+ @keyframes modalIn {
92
+ from {
93
+ opacity: 0;
94
+ transform: translateY(8px) scale(0.97);
95
+ }
96
+ to {
97
+ opacity: 1;
98
+ transform: translateY(0);
99
+ }
100
+ }
101
+
102
+ @keyframes modalSlideUp {
103
+ from {
104
+ opacity: 0;
105
+ transform: translateY(100%);
106
+ }
107
+ to {
108
+ opacity: 1;
109
+ transform: translateY(0);
110
+ }
111
+ }
112
+
113
+ @keyframes toastSlideIn {
114
+ from {
115
+ opacity: 0;
116
+ transform: translateX(calc(100% + 1rem));
117
+ }
118
+ to {
119
+ opacity: 1;
120
+ transform: translateX(0);
121
+ }
122
+ }
123
+
124
+ @keyframes toastFadeOut {
125
+ from {
126
+ opacity: 1;
127
+ transform: translateX(0);
128
+ }
129
+ to {
130
+ opacity: 0;
131
+ transform: translateX(calc(100% + 1rem));
132
+ }
133
+ }
134
+
135
+ @keyframes toastSwipeOut {
136
+ from {
137
+ transform: translateX(var(--reka-toast-swipe-end-x));
138
+ }
139
+ to {
140
+ transform: translateX(calc(100% + 1rem));
141
+ }
142
+ }
143
+
86
144
  @media (prefers-reduced-motion: reduce) {
87
145
  .aggc-spin,
88
- [data-ui-loading-pulse] {
146
+ [data-ui-loading-pulse],
147
+ [data-state="open"],
148
+ [data-state="closed"] {
89
149
  animation: none !important;
90
150
  }
91
151
 
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./components/index";
2
+ export * from "./composables/useToast";
2
3
  export * from "./styles/index";
3
4
  export * from "./tokens/index";
@@ -0,0 +1,72 @@
1
+ import type { Meta, StoryObj } from "@storybook/vue3-vite";
2
+ import { ref } from "vue";
3
+ import { UiButton, UiToast, UiToastProvider } from "../../components";
4
+ import { useToast } from "../../composables/useToast";
5
+ import StoryThemeFrame from "../support/StoryThemeFrame.vue";
6
+ import {
7
+ storySurfaceClass,
8
+ storySurfaceCompactClass,
9
+ } from "../support/storyStyles";
10
+
11
+ const meta: Meta = {
12
+ title: "Feedback/UiToast",
13
+ id: "ui-toast",
14
+ component: UiToast,
15
+ tags: ["autodocs"],
16
+ parameters: {
17
+ docs: {
18
+ description: {
19
+ component:
20
+ "Notification toast with tone-based styling, auto-dismiss, and swipe-to-dismiss. Built on Reka UI Toast.",
21
+ },
22
+ },
23
+ },
24
+ };
25
+
26
+ export default meta;
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ export const Tones: Story = {
30
+ render: () => ({
31
+ components: { StoryThemeFrame, UiToast, UiToastProvider, UiButton },
32
+ setup() {
33
+ const previewTheme = ref<"light" | "dark">("dark");
34
+ const toast = useToast();
35
+ return { previewTheme, toast };
36
+ },
37
+ template: `
38
+ <StoryThemeFrame v-model:theme="previewTheme" preview-id="toast-tones">
39
+ <UiToastProvider>
40
+ <div :class="[storySurfaceClass, storySurfaceCompactClass]" style="display: flex; gap: 0.75rem; flex-wrap: wrap;">
41
+ <UiButton variant="outline" @click="toast.success('Workspace created successfully')">Success</UiButton>
42
+ <UiButton variant="outline" @click="toast.error('Failed to upload document')">Error</UiButton>
43
+ <UiButton variant="outline" @click="toast.info('API key copied to clipboard')">Info</UiButton>
44
+ <UiButton variant="outline" @click="toast.warning('Storage quota almost full')">Warning</UiButton>
45
+ </div>
46
+ </UiToastProvider>
47
+ </StoryThemeFrame>
48
+ `,
49
+ }),
50
+ };
51
+
52
+ export const Static: Story = {
53
+ render: () => ({
54
+ components: { StoryThemeFrame, UiToast, UiToastProvider },
55
+ setup() {
56
+ const previewTheme = ref<"light" | "dark">("dark");
57
+ return { previewTheme };
58
+ },
59
+ template: `
60
+ <StoryThemeFrame v-model:theme="previewTheme" preview-id="toast-static">
61
+ <UiToastProvider>
62
+ <div :class="[storySurfaceClass, storySurfaceCompactClass]" style="display: flex; flex-direction: column; gap: 0.75rem;">
63
+ <UiToast tone="success" message="Document uploaded and processed." :duration="0" />
64
+ <UiToast tone="error" message="Upload failed. Please try again." :duration="0" />
65
+ <UiToast tone="info" message="New workspace available." :duration="0" />
66
+ <UiToast tone="warning" message="Rate limit approaching." :duration="0" />
67
+ </div>
68
+ </UiToastProvider>
69
+ </StoryThemeFrame>
70
+ `,
71
+ }),
72
+ };
@@ -0,0 +1,141 @@
1
+ import type { Meta, StoryObj } from "@storybook/vue3-vite";
2
+ import { ref } from "vue";
3
+ import {
4
+ DataTable,
5
+ UiButton,
6
+ DropdownMenuItem,
7
+ DropdownMenuSeparator,
8
+ DropdownMenuRoot,
9
+ DropdownMenuTrigger,
10
+ DropdownMenuContent,
11
+ } from "../../components";
12
+ import type { DataTableColumn } from "../../components";
13
+ import StoryThemeFrame from "../support/StoryThemeFrame.vue";
14
+ import { storyCaptionClass, storySectionLabelClass, storySurfaceClass, storySurfaceWideClass } from "../support/storyStyles";
15
+ import { dataTableDefaultSource, dataTableLoadingSource, dataTableSortableSource } from "../support/sources";
16
+
17
+ interface Member {
18
+ id: string;
19
+ name: string;
20
+ email: string;
21
+ role: "ADMIN" | "MEMBER";
22
+ }
23
+
24
+ const columns: DataTableColumn[] = [
25
+ { key: "name", label: "Member", width: "1fr" },
26
+ { key: "email", label: "Email", width: "1fr" },
27
+ { key: "role", label: "Role", width: "120px" },
28
+ ];
29
+
30
+ const meta = {
31
+ title: "Layout/DataTable",
32
+ id: "data-table",
33
+ // Generic <T> component doesn't fit Meta's ConcreteComponent type; cast keeps
34
+ // autodocs working without forcing typed args on every story.
35
+ component: DataTable as never,
36
+ tags: ["autodocs"],
37
+ parameters: {
38
+ docs: {
39
+ description: {
40
+ component:
41
+ "Reusable data table with debounced search, Set-based selection (shift+click, drag-to-select, Ctrl/Cmd+A, Escape), responsive mobile cards, bulk actions, per-row actions, and loading skeletons.",
42
+ },
43
+ },
44
+ },
45
+ } satisfies Meta;
46
+
47
+ export default meta;
48
+ type Story = StoryObj;
49
+
50
+ export const Default: Story = {
51
+ parameters: { docs: { source: { code: dataTableDefaultSource } } },
52
+ render: () => ({
53
+ components: { DataTable, UiButton, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuContent, StoryThemeFrame },
54
+ setup() {
55
+ const previewTheme = ref<"light" | "dark">("dark");
56
+ const selected = ref<string[]>([]);
57
+ const items = ref<Member[]>([
58
+ { id: "1", name: "Alice Johnson", email: "alice@example.com", role: "ADMIN" },
59
+ { id: "2", name: "Bob Smith", email: "bob@example.com", role: "MEMBER" },
60
+ { id: "3", name: "Carla Mendes", email: "carla@example.com", role: "MEMBER" },
61
+ ]);
62
+ return { previewTheme, selected, items, columns, storyCaptionClass, storySectionLabelClass, storySurfaceClass, storySurfaceWideClass };
63
+ },
64
+ template: `
65
+ <StoryThemeFrame v-model:theme="previewTheme" preview-id="default">
66
+ <div :class="[storySurfaceClass, storySurfaceWideClass]">
67
+ <p :class="storySectionLabelClass">Searchable + selectable</p>
68
+ <DataTable v-model:selected="selected" :items="items" :columns="columns" searchable :search-fields="['name', 'email']" search-placeholder="Search by name or email...">
69
+ <template #bulk-actions="{ selectedItems }">
70
+ <UiButton size="sm" variant="outline">{{ selectedItems.length }} selected</UiButton>
71
+ </template>
72
+ <template #cell-name="{ item }"><strong>{{ item.name }}</strong></template>
73
+ <template #cell-email="{ item }">{{ item.email }}</template>
74
+ <template #cell-role="{ item }">{{ item.role === 'ADMIN' ? 'Admin' : 'Member' }}</template>
75
+ <template #actions="{ item }">
76
+ <DropdownMenuItem @select="() => {}">Change role</DropdownMenuItem>
77
+ <DropdownMenuSeparator />
78
+ <DropdownMenuItem @select="() => {}">Deactivate</DropdownMenuItem>
79
+ </template>
80
+ </DataTable>
81
+ <p :class="storyCaptionClass">Shift+click ranges, drag to paint, Ctrl/Cmd+A selects all visible, Escape clears.</p>
82
+ </div>
83
+ </StoryThemeFrame>
84
+ `,
85
+ }),
86
+ };
87
+
88
+ export const Loading: Story = {
89
+ parameters: { docs: { source: { code: dataTableLoadingSource } } },
90
+ render: () => ({
91
+ components: { DataTable, StoryThemeFrame },
92
+ setup() {
93
+ const previewTheme = ref<"light" | "dark">("dark");
94
+ return { previewTheme, columns, storyCaptionClass, storySectionLabelClass, storySurfaceClass, storySurfaceWideClass };
95
+ },
96
+ template: `
97
+ <StoryThemeFrame v-model:theme="previewTheme" preview-id="loading">
98
+ <div :class="[storySurfaceClass, storySurfaceWideClass]">
99
+ <p :class="storySectionLabelClass">Loading skeletons</p>
100
+ <DataTable :items="[]" :columns="columns" :is-loading="true" searchable />
101
+ <p :class="storyCaptionClass">Skeleton rows render while data loads, with aria-busy on the root.</p>
102
+ </div>
103
+ </StoryThemeFrame>
104
+ `,
105
+ }),
106
+ };
107
+
108
+ export const SortablePaginated: Story = {
109
+ parameters: { docs: { source: { code: dataTableSortableSource } } },
110
+ render: () => ({
111
+ components: { DataTable, StoryThemeFrame },
112
+ setup() {
113
+ const previewTheme = ref<"light" | "dark">("dark");
114
+ const items = ref<Member[]>([
115
+ { id: "1", name: "Alice Johnson", email: "alice@example.com", role: "ADMIN" },
116
+ { id: "2", name: "Bob Smith", email: "bob@example.com", role: "MEMBER" },
117
+ { id: "3", name: "Carla Mendes", email: "carla@example.com", role: "MEMBER" },
118
+ { id: "4", name: "Dan Lee", email: "dan@example.com", role: "MEMBER" },
119
+ ]);
120
+ const sortColumns: DataTableColumn[] = [
121
+ { key: "name", label: "Member", width: "1fr", sortable: true },
122
+ { key: "email", label: "Email", width: "1fr", sortable: true },
123
+ { key: "role", label: "Role", width: "120px", sortable: true },
124
+ ];
125
+ return { previewTheme, items, sortColumns, storyCaptionClass, storySectionLabelClass, storySurfaceClass, storySurfaceWideClass };
126
+ },
127
+ template: `
128
+ <StoryThemeFrame v-model:theme="previewTheme" preview-id="sortable">
129
+ <div :class="[storySurfaceClass, storySurfaceWideClass]">
130
+ <p :class="storySectionLabelClass">Sortable + paginated (display-only)</p>
131
+ <DataTable :items="items" :columns="sortColumns" :page-size="3" :selectable="false">
132
+ <template #cell-name="{ item }"><strong>{{ item.name }}</strong></template>
133
+ <template #cell-email="{ item }">{{ item.email }}</template>
134
+ <template #cell-role="{ item }">{{ item.role === 'ADMIN' ? 'Admin' : 'Member' }}</template>
135
+ </DataTable>
136
+ <p :class="storyCaptionClass">sortable columns toggle asc/desc/clear; pageSize adds client-side pagination. selectable=false hides the checkbox column.</p>
137
+ </div>
138
+ </StoryThemeFrame>
139
+ `,
140
+ }),
141
+ };
@@ -0,0 +1,89 @@
1
+ import type { Meta, StoryObj } from "@storybook/vue3-vite";
2
+ import { ref } from "vue";
3
+ import { UiButton, UiField, UiModal } from "../../components";
4
+ import StoryThemeFrame from "../support/StoryThemeFrame.vue";
5
+ import {
6
+ storySurfaceClass,
7
+ storySurfaceCompactClass,
8
+ } from "../support/storyStyles";
9
+
10
+ const meta: Meta = {
11
+ title: "Layout/UiModal",
12
+ id: "ui-modal",
13
+ component: UiModal,
14
+ tags: ["autodocs"],
15
+ parameters: {
16
+ docs: {
17
+ description: {
18
+ component:
19
+ "Accessible dialog overlay with focus trap, ESC close, and portal rendering. Built on Reka UI Dialog.",
20
+ },
21
+ },
22
+ },
23
+ };
24
+
25
+ export default meta;
26
+ type Story = StoryObj<typeof meta>;
27
+
28
+ export const Default: Story = {
29
+ render: () => ({
30
+ components: { StoryThemeFrame, UiModal, UiButton, UiField },
31
+ setup() {
32
+ const previewTheme = ref<"light" | "dark">("dark");
33
+ const open = ref(false);
34
+ return { previewTheme, open };
35
+ },
36
+ template: `
37
+ <StoryThemeFrame v-model:theme="previewTheme" preview-id="modal-default">
38
+ <div :class="[storySurfaceClass, storySurfaceCompactClass]">
39
+ <UiButton @click="open = true">Open Modal</UiButton>
40
+ <UiModal v-model:open="open" title="Create Workspace" description="Add a new workspace to your tenant.">
41
+ <div style="display: flex; flex-direction: column; gap: 1rem;">
42
+ <UiField label="Name" for-id="ws-name">
43
+ <input id="ws-name" type="text" placeholder="e.g., Legal Documents Q2" style="width: 100%; padding: 0.5rem 0.75rem; border-radius: 0.5rem; border: 1px solid var(--colors-border-default); background: var(--colors-bg-input); color: var(--colors-text-primary); font-size: 0.875rem;" />
44
+ </UiField>
45
+ </div>
46
+ <template #actions>
47
+ <UiButton variant="outline" @click="open = false">Cancel</UiButton>
48
+ <UiButton @click="open = false">Create</UiButton>
49
+ </template>
50
+ </UiModal>
51
+ </div>
52
+ </StoryThemeFrame>
53
+ `,
54
+ }),
55
+ };
56
+
57
+ export const Sizes: Story = {
58
+ render: () => ({
59
+ components: { StoryThemeFrame, UiModal, UiButton },
60
+ setup() {
61
+ const previewTheme = ref<"light" | "dark">("dark");
62
+ const openSm = ref(false);
63
+ const openMd = ref(false);
64
+ const openLg = ref(false);
65
+ return { previewTheme, openSm, openMd, openLg };
66
+ },
67
+ template: `
68
+ <StoryThemeFrame v-model:theme="previewTheme" preview-id="modal-sizes">
69
+ <div :class="[storySurfaceClass, storySurfaceCompactClass]" style="display: flex; gap: 0.75rem;">
70
+ <UiButton variant="outline" @click="openSm = true">Small</UiButton>
71
+ <UiButton variant="outline" @click="openMd = true">Medium</UiButton>
72
+ <UiButton variant="outline" @click="openLg = true">Large</UiButton>
73
+ <UiModal v-model:open="openSm" size="sm" title="Small Modal">
74
+ <p>This is a small modal (400px max width).</p>
75
+ </UiModal>
76
+ <UiModal v-model:open="openMd" size="md" title="Medium Modal">
77
+ <p>This is a medium modal (560px max width).</p>
78
+ </UiModal>
79
+ <UiModal v-model:open="openLg" size="lg" title="Large Modal">
80
+ <p>This is a large modal (720px max width).</p>
81
+ <template #actions>
82
+ <UiButton variant="outline" @click="openLg = false">Close</UiButton>
83
+ </template>
84
+ </UiModal>
85
+ </div>
86
+ </StoryThemeFrame>
87
+ `,
88
+ }),
89
+ };
@@ -0,0 +1,83 @@
1
+ import type { Meta, StoryObj } from "@storybook/vue3-vite";
2
+ import { ref } from "vue";
3
+ import { UiAvatar } from "../../components";
4
+ import StoryThemeFrame from "../support/StoryThemeFrame.vue";
5
+ import {
6
+ storySurfaceClass,
7
+ storySurfaceCompactClass,
8
+ } from "../support/storyStyles";
9
+
10
+ const meta: Meta = {
11
+ title: "Primitives/UiAvatar",
12
+ id: "ui-avatar",
13
+ component: UiAvatar,
14
+ tags: ["autodocs"],
15
+ parameters: {
16
+ docs: {
17
+ description: {
18
+ component:
19
+ "User avatar with image support and initials fallback. Built on Reka UI Avatar.",
20
+ },
21
+ },
22
+ },
23
+ };
24
+
25
+ export default meta;
26
+ type Story = StoryObj<typeof meta>;
27
+
28
+ export const Sizes: Story = {
29
+ render: () => ({
30
+ components: { StoryThemeFrame, UiAvatar },
31
+ setup() {
32
+ const previewTheme = ref<"light" | "dark">("dark");
33
+ return { previewTheme };
34
+ },
35
+ template: `
36
+ <StoryThemeFrame v-model:theme="previewTheme" preview-id="avatar-sizes">
37
+ <div :class="[storySurfaceClass, storySurfaceCompactClass]" style="display: flex; align-items: center; gap: 1rem;">
38
+ <UiAvatar name="Alice Johnson" size="sm" />
39
+ <UiAvatar name="Alice Johnson" size="md" />
40
+ <UiAvatar name="Alice Johnson" size="lg" />
41
+ <UiAvatar name="Alice Johnson" size="xl" />
42
+ </div>
43
+ </StoryThemeFrame>
44
+ `,
45
+ }),
46
+ };
47
+
48
+ export const Shapes: Story = {
49
+ render: () => ({
50
+ components: { StoryThemeFrame, UiAvatar },
51
+ setup() {
52
+ const previewTheme = ref<"light" | "dark">("dark");
53
+ return { previewTheme };
54
+ },
55
+ template: `
56
+ <StoryThemeFrame v-model:theme="previewTheme" preview-id="avatar-shapes">
57
+ <div :class="[storySurfaceClass, storySurfaceCompactClass]" style="display: flex; align-items: center; gap: 1rem;">
58
+ <UiAvatar name="Alice Johnson" size="lg" shape="circle" />
59
+ <UiAvatar name="Alice Johnson" size="lg" shape="square" />
60
+ </div>
61
+ </StoryThemeFrame>
62
+ `,
63
+ }),
64
+ };
65
+
66
+ export const WithImage: Story = {
67
+ render: () => ({
68
+ components: { StoryThemeFrame, UiAvatar },
69
+ setup() {
70
+ const previewTheme = ref<"light" | "dark">("dark");
71
+ return { previewTheme };
72
+ },
73
+ template: `
74
+ <StoryThemeFrame v-model:theme="previewTheme" preview-id="avatar-image">
75
+ <div :class="[storySurfaceClass, storySurfaceCompactClass]" style="display: flex; align-items: center; gap: 1rem;">
76
+ <UiAvatar name="Colm Tuite" src="https://images.unsplash.com/photo-1492633423870-43d1cd2775eb?&w=128&h=128&dpr=2&q=80" size="lg" />
77
+ <UiAvatar name="Fallback User" src="https://broken-url.invalid/photo.jpg" size="lg" />
78
+ <UiAvatar name="No Image" size="lg" />
79
+ </div>
80
+ </StoryThemeFrame>
81
+ `,
82
+ }),
83
+ };
@@ -0,0 +1,46 @@
1
+ import type { Meta, StoryObj } from "@storybook/vue3-vite";
2
+ import { ref } from "vue";
3
+ import { UiTooltip, UiButton } from "../../components";
4
+ import StoryThemeFrame from "../support/StoryThemeFrame.vue";
5
+ import { storyCaptionClass, storySectionLabelClass, storySurfaceClass } from "../support/storyStyles";
6
+ import { uiTooltipDefaultSource } from "../support/sources";
7
+
8
+ const meta = {
9
+ title: "Primitives/UiTooltip",
10
+ id: "ui-tooltip",
11
+ component: UiTooltip as never,
12
+ tags: ["autodocs"],
13
+ parameters: {
14
+ docs: {
15
+ description: {
16
+ component:
17
+ "Accessible tooltip built on Reka UI. Renders the trigger in place and floats the content on hover/focus with positioning, portal, and keyboard support.",
18
+ },
19
+ },
20
+ },
21
+ } satisfies Meta;
22
+
23
+ export default meta;
24
+ type Story = StoryObj;
25
+
26
+ export const Default: Story = {
27
+ parameters: { docs: { source: { code: uiTooltipDefaultSource } } },
28
+ render: () => ({
29
+ components: { UiTooltip, UiButton, StoryThemeFrame },
30
+ setup() {
31
+ const previewTheme = ref<"light" | "dark">("dark");
32
+ return { previewTheme, storyCaptionClass, storySectionLabelClass, storySurfaceClass };
33
+ },
34
+ template: `
35
+ <StoryThemeFrame v-model:theme="previewTheme" preview-id="default">
36
+ <div :class="storySurfaceClass" style="min-height:160px;display:flex;align-items:center;justify-content:center;gap:4">
37
+ <p :class="storySectionLabelClass">Hover the button</p>
38
+ <UiTooltip content="Saves changes to the workspace">
39
+ <UiButton size="sm">Save</UiButton>
40
+ </UiTooltip>
41
+ <p :class="storyCaptionClass">Reka UI handles positioning, portal, and focus.</p>
42
+ </div>
43
+ </StoryThemeFrame>
44
+ `,
45
+ }),
46
+ };