@sviy/ui 0.0.0-stage → 0.0.9

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 (103) hide show
  1. package/README.md +142 -2
  2. package/index.ts +61 -0
  3. package/package.json +44 -4
  4. package/src/components/ui/alert/Alert.vue +21 -0
  5. package/src/components/ui/alert/AlertAction.vue +17 -0
  6. package/src/components/ui/alert/AlertDescription.vue +17 -0
  7. package/src/components/ui/alert/AlertTitle.vue +17 -0
  8. package/src/components/ui/alert/index.ts +21 -0
  9. package/src/components/ui/avatar/Avatar.vue +22 -0
  10. package/src/components/ui/avatar/AvatarBadge.vue +23 -0
  11. package/src/components/ui/avatar/AvatarFallback.vue +21 -0
  12. package/src/components/ui/avatar/AvatarGroup.vue +20 -0
  13. package/src/components/ui/avatar/AvatarGroupCount.vue +20 -0
  14. package/src/components/ui/avatar/AvatarImage.vue +16 -0
  15. package/src/components/ui/avatar/index.ts +27 -0
  16. package/src/components/ui/badge/Badge.vue +27 -0
  17. package/src/components/ui/badge/index.ts +24 -0
  18. package/src/components/ui/button/Button.vue +31 -0
  19. package/src/components/ui/button/index.ts +35 -0
  20. package/src/components/ui/card/Card.vue +21 -0
  21. package/src/components/ui/card/CardAction.vue +17 -0
  22. package/src/components/ui/card/CardContent.vue +17 -0
  23. package/src/components/ui/card/CardDescription.vue +17 -0
  24. package/src/components/ui/card/CardFooter.vue +17 -0
  25. package/src/components/ui/card/CardHeader.vue +17 -0
  26. package/src/components/ui/card/CardTitle.vue +17 -0
  27. package/src/components/ui/card/index.ts +7 -0
  28. package/src/components/ui/checkbox/Checkbox.vue +34 -0
  29. package/src/components/ui/checkbox/index.ts +1 -0
  30. package/src/components/ui/dialog/Dialog.vue +19 -0
  31. package/src/components/ui/dialog/DialogClose.vue +15 -0
  32. package/src/components/ui/dialog/DialogContent.vue +53 -0
  33. package/src/components/ui/dialog/DialogDescription.vue +23 -0
  34. package/src/components/ui/dialog/DialogFooter.vue +27 -0
  35. package/src/components/ui/dialog/DialogHeader.vue +17 -0
  36. package/src/components/ui/dialog/DialogOverlay.vue +21 -0
  37. package/src/components/ui/dialog/DialogScrollContent.vue +60 -0
  38. package/src/components/ui/dialog/DialogTitle.vue +23 -0
  39. package/src/components/ui/dialog/DialogTrigger.vue +15 -0
  40. package/src/components/ui/dialog/index.ts +10 -0
  41. package/src/components/ui/dropdown-menu/DropdownMenu.vue +19 -0
  42. package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +43 -0
  43. package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +40 -0
  44. package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +15 -0
  45. package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +31 -0
  46. package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -0
  47. package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -0
  48. package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +44 -0
  49. package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
  50. package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +17 -0
  51. package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +18 -0
  52. package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +27 -0
  53. package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +32 -0
  54. package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -0
  55. package/src/components/ui/dropdown-menu/index.ts +16 -0
  56. package/src/components/ui/input/Input.vue +31 -0
  57. package/src/components/ui/input/index.ts +1 -0
  58. package/src/components/ui/label/Label.vue +26 -0
  59. package/src/components/ui/label/index.ts +1 -0
  60. package/src/components/ui/progress/Progress.vue +38 -0
  61. package/src/components/ui/progress/index.ts +1 -0
  62. package/src/components/ui/select/Select.vue +19 -0
  63. package/src/components/ui/select/SelectContent.vue +58 -0
  64. package/src/components/ui/select/SelectGroup.vue +21 -0
  65. package/src/components/ui/select/SelectItem.vue +45 -0
  66. package/src/components/ui/select/SelectItemText.vue +15 -0
  67. package/src/components/ui/select/SelectLabel.vue +17 -0
  68. package/src/components/ui/select/SelectScrollDownButton.vue +27 -0
  69. package/src/components/ui/select/SelectScrollUpButton.vue +27 -0
  70. package/src/components/ui/select/SelectSeparator.vue +19 -0
  71. package/src/components/ui/select/SelectTrigger.vue +34 -0
  72. package/src/components/ui/select/SelectValue.vue +15 -0
  73. package/src/components/ui/select/index.ts +11 -0
  74. package/src/components/ui/separator/Separator.vue +29 -0
  75. package/src/components/ui/separator/index.ts +1 -0
  76. package/src/components/ui/sonner/Sonner.vue +62 -0
  77. package/src/components/ui/sonner/index.ts +1 -0
  78. package/src/components/ui/switch/Switch.vue +44 -0
  79. package/src/components/ui/switch/index.ts +1 -0
  80. package/src/components/ui/table/Table.vue +16 -0
  81. package/src/components/ui/table/TableBody.vue +17 -0
  82. package/src/components/ui/table/TableCaption.vue +17 -0
  83. package/src/components/ui/table/TableCell.vue +17 -0
  84. package/src/components/ui/table/TableEmpty.vue +34 -0
  85. package/src/components/ui/table/TableFooter.vue +17 -0
  86. package/src/components/ui/table/TableHead.vue +17 -0
  87. package/src/components/ui/table/TableHeader.vue +17 -0
  88. package/src/components/ui/table/TableRow.vue +17 -0
  89. package/src/components/ui/table/index.ts +9 -0
  90. package/src/components/ui/tabs/Tabs.vue +25 -0
  91. package/src/components/ui/tabs/TabsContent.vue +21 -0
  92. package/src/components/ui/tabs/TabsList.vue +29 -0
  93. package/src/components/ui/tabs/TabsTrigger.vue +29 -0
  94. package/src/components/ui/tabs/index.ts +24 -0
  95. package/src/components/ui/textarea/Textarea.vue +28 -0
  96. package/src/components/ui/textarea/index.ts +1 -0
  97. package/src/components/ui/tooltip/Tooltip.vue +19 -0
  98. package/src/components/ui/tooltip/TooltipContent.vue +34 -0
  99. package/src/components/ui/tooltip/TooltipProvider.vue +14 -0
  100. package/src/components/ui/tooltip/TooltipTrigger.vue +15 -0
  101. package/src/components/ui/tooltip/index.ts +4 -0
  102. package/src/lib/utils.ts +1 -0
  103. package/theme.css +223 -0
package/README.md CHANGED
@@ -1,3 +1,143 @@
1
- # Temporary Holding Version
1
+ # @sviy/ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ House UI kit. Reka UI primitives, styled with Tailwind v4, generated by the
4
+ `shadcn-vue` CLI and owned by us. No upstream, no build step.
5
+
6
+ Ships **raw `.vue`/`.ts` source**. The app's bundler compiles it. Installed from
7
+ npm; nothing is ever copied into an app.
8
+
9
+ ---
10
+
11
+ ## Consuming it
12
+
13
+ ```bash
14
+ npm install @sviy/ui vue tailwindcss @tailwindcss/vite @lucide/vue
15
+ npm install -D typescript
16
+ ```
17
+
18
+ `vue`, `tailwindcss`, `@lucide/vue` are peers — the app owns those versions.
19
+ `typescript` is required in the **app**: the kit is typed `.vue` source and
20
+ `@vue/compiler-sfc` cannot resolve imported types without it. Omitting it fails
21
+ the build with `Failed to load TypeScript…`.
22
+
23
+ **`vite.config.ts`** — `optimizeDeps.exclude` is mandatory:
24
+
25
+ ```ts
26
+ export default defineConfig({
27
+ plugins: [vue(), tailwindcss()],
28
+ optimizeDeps: { exclude: ['@sviy/ui'] }, // raw source: esbuild must not pre-bundle it
29
+ })
30
+ ```
31
+
32
+ **CSS entry** — two lines, in this order:
33
+
34
+ ```css
35
+ @import "tailwindcss";
36
+ @import "@sviy/ui/theme.css";
37
+ ```
38
+
39
+ Do **not** add an `@source` directive. `theme.css` carries its own
40
+ `@source "./src"`, resolved against the installed copy.
41
+
42
+ **Imports** — always from the package root. Never from `@sviy/ui/src/…`:
43
+
44
+ ```ts
45
+ import { Button, Card, Dialog, Table, toast, Toaster, icons } from '@sviy/ui'
46
+ ```
47
+
48
+ **Dark mode** is class-based, not `prefers-color-scheme`. The app toggles `.dark`
49
+ on `<html>`:
50
+
51
+ ```ts
52
+ document.documentElement.classList.toggle('dark', dark.value)
53
+ ```
54
+
55
+ Mount `<Toaster />` once near the app root. Wrap anything using `Tooltip` in
56
+ `TooltipProvider`.
57
+
58
+ ---
59
+
60
+ ## Surface
61
+
62
+ `alert` · `avatar` · `badge` · `button` · `card` · `checkbox` · `dialog` ·
63
+ `dropdown-menu` · `input` · `label` · `progress` · `select` · `separator` ·
64
+ `sonner` · `switch` · `table` · `tabs` · `textarea` · `tooltip`
65
+
66
+ Plus `cn`, `toast`, `icons`.
67
+
68
+ - **Icons** are only under the `icons` namespace: `<icons.PlusIcon class="size-4" />`.
69
+ `export * from '@lucide/vue'` is **not** re-exported flat — `Badge` and `Table`
70
+ exist in both, and ESM silently drops names supplied by two star-exports, which
71
+ would break `import { Badge }`. Import `@lucide/vue` directly if you prefer flat.
72
+ - **`Table` is primitives only.** Sorting/filtering are app policy — write a
73
+ `computed`.
74
+
75
+ ---
76
+
77
+ ## Adding a component
78
+
79
+ Run the CLI **in place**, from the kit repo root:
80
+
81
+ ```bash
82
+ npx --yes shadcn-vue@latest add <component> -y
83
+ ```
84
+
85
+ Then:
86
+
87
+ 1. **Rewrite `@/…` imports to relative paths.** A component importing a sibling
88
+ family (e.g. `DialogContent.vue` → `Button`) is generated as
89
+ `@/components/ui/button`, which cannot resolve in a published package. Use
90
+ `../button`. Step 3 enforces this — `tsconfig.json` maps no `@/*` path.
91
+ 2. **Export the family** from `index.ts` (hand-maintained).
92
+ 3. **`npm run typecheck`** — `vue-tsc`. The kit's only gate; an app's JS build
93
+ never checks these SFCs.
94
+ 4. **Release** (below). Unpublished means invisible.
95
+
96
+ ### Rules
97
+
98
+ - **Bare `data-*` variants do not exist in Tailwind v4.** `data-open:` compiles to
99
+ the literal selector `[data-open]`, but Reka writes `data-state` /
100
+ `data-orientation`. The names the components need are mapped once in
101
+ `theme.css` under "Reka attribute variants": `data-open`, `data-closed`,
102
+ `data-checked`, `data-unchecked`, `data-horizontal`, `data-vertical`.
103
+ Any generated bare variant **outside that list** needs its own
104
+ `@custom-variant` there. **This failure is silent** — the build succeeds and the
105
+ CSS never matches. Check what Reka really emits with:
106
+ `grep -rho '"data-[a-z-]*"' node_modules/reka-ui/dist`
107
+ - **`dev.css` is a generator target, not published.** The CLI rewrites it on every
108
+ run. Leave it alone.
109
+ - **Check dependencies the CLI adds.** A new one may belong in `peerDependencies`.
110
+ - **Fix hardcoded colour/font in a new component in `theme.css` §3**, never in an app.
111
+
112
+ ---
113
+
114
+ ## Releasing
115
+
116
+ ```bash
117
+ npm version patch
118
+ npm publish --access public
119
+ ```
120
+
121
+ `npm version` edits `package.json`, commits and tags in one step — let it do the
122
+ edit so the three can never disagree. It aborts on a dirty tree: commit first.
123
+ `--access public` is required; scoped packages default to private.
124
+
125
+ **A version bump is mandatory.** The registry is immutable — one publish per
126
+ version, re-publishing is rejected. Apps resolve by version, so a committed but
127
+ unversioned change is uninstallable.
128
+
129
+ Consumers: `npm install @sviy/ui@latest`.
130
+
131
+ ---
132
+
133
+ ## Brand
134
+
135
+ All colour lives in `theme.css`, in three sections. Nothing else defines it.
136
+
137
+ 1. `:root` — house tokens (`--primary`, `--background`, `--radius`, …)
138
+ 2. `.dark` — dark counterparts, **same token names**
139
+ 3. Third-party corrections
140
+
141
+ Rebranding edits §1 and §2 only — never a component, never an app. The typeface
142
+ is IBM Plex Sans, self-hosted via `@fontsource` (no runtime CDN request), set once
143
+ on `--font-sans`.
package/index.ts ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @sviy/ui — the single entry point for apps.
3
+ *
4
+ * Apps import everything from here and never touch the component folders, so
5
+ * the components can be upgraded without every app being edited.
6
+ *
7
+ * import { Button, Dialog, Table, cn } from '@sviy/ui';
8
+ *
9
+ * Each family is its own module, so only what an app actually imports ends up
10
+ * in its bundle.
11
+ */
12
+
13
+ export * from './src/lib/utils'
14
+
15
+ /*
16
+ * The Toaster component is in the kit, so the function that drives it should be
17
+ * too — otherwise every app has to depend on `vue-sonner` directly just to push
18
+ * a notification.
19
+ */
20
+ export { toast } from 'vue-sonner'
21
+
22
+ /*
23
+ * Icons, under a namespace.
24
+ *
25
+ * The obvious form, `export * from '@lucide/vue'`, breaks the kit: `Badge` and
26
+ * `Table` exist both as kit components and as lucide icons, and ESM silently
27
+ * drops any name that two star-exports provide. The failure then surfaces in
28
+ * the *app* as `"Badge" is not exported by "@sviy/ui"`, with nothing pointing
29
+ * back here. It is also a moving target — the day lucide ships a `Card` or
30
+ * `Button` icon, the kit quietly loses that component.
31
+ *
32
+ * A namespace cannot collide with anything, and it costs nothing: the built
33
+ * bundle is byte-identical to not exporting icons at all, because only the
34
+ * icons an app actually references survive tree-shaking.
35
+ *
36
+ * import { icons } from '@sviy/ui'
37
+ * <icons.PlusIcon />
38
+ *
39
+ * Apps that prefer the flat form can still import from '@lucide/vue' directly.
40
+ */
41
+ export * as icons from '@lucide/vue'
42
+
43
+ export * from './src/components/ui/alert'
44
+ export * from './src/components/ui/avatar'
45
+ export * from './src/components/ui/badge'
46
+ export * from './src/components/ui/button'
47
+ export * from './src/components/ui/card'
48
+ export * from './src/components/ui/checkbox'
49
+ export * from './src/components/ui/dialog'
50
+ export * from './src/components/ui/dropdown-menu'
51
+ export * from './src/components/ui/input'
52
+ export * from './src/components/ui/label'
53
+ export * from './src/components/ui/progress'
54
+ export * from './src/components/ui/select'
55
+ export * from './src/components/ui/separator'
56
+ export * from './src/components/ui/sonner'
57
+ export * from './src/components/ui/switch'
58
+ export * from './src/components/ui/table'
59
+ export * from './src/components/ui/tabs'
60
+ export * from './src/components/ui/textarea'
61
+ export * from './src/components/ui/tooltip'
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "@sviy/ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.9",
4
+ "description": "House UI kit: Reka UI primitives styled with Tailwind CSS v4.",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "scripts": {
8
+ "typecheck": "vue-tsc --noEmit"
9
+ },
10
+ "sideEffects": [
11
+ "**/*.css"
12
+ ],
13
+ "files": [
14
+ "src",
15
+ "theme.css",
16
+ "index.ts",
17
+ "README.md"
18
+ ],
19
+ "exports": {
20
+ ".": "./index.ts",
21
+ "./theme.css": "./theme.css",
22
+ "./lib/utils": "./src/lib/utils.ts",
23
+ "./components/*": "./src/components/*"
24
+ },
25
+ "dependencies": {
26
+ "@fontsource/ibm-plex-sans": "^5.3.0",
27
+ "@vueuse/core": "^15.0.0",
28
+ "class-variance-authority": "^0.7.1",
29
+ "cn": "^0.5.1",
30
+ "reka-ui": "^2.11.0",
31
+ "tw-animate-css": "^1.4.0",
32
+ "vue-sonner": "^2.0.9"
33
+ },
34
+ "peerDependencies": {
35
+ "@lucide/vue": "^1.66.0",
36
+ "tailwindcss": "^4.0.0",
37
+ "vue": "^3.5.0"
38
+ },
39
+ "devDependencies": {
40
+ "@lucide/vue": "^1.66.0",
41
+ "tailwindcss": "^4.3.3",
42
+ "typescript": "~6.0.3",
43
+ "vue": "^3.5.44",
44
+ "vue-tsc": "^3.3.13"
45
+ }
46
+ }
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from 'vue'
3
+ import type { AlertVariants } from '.'
4
+ import { cn } from 'cn'
5
+ import { alertVariants } from '.'
6
+
7
+ const props = defineProps<{
8
+ class?: HTMLAttributes['class']
9
+ variant?: AlertVariants['variant']
10
+ }>()
11
+ </script>
12
+
13
+ <template>
14
+ <div
15
+ data-slot="alert"
16
+ :class="cn(alertVariants({ variant }), props.class)"
17
+ role="alert"
18
+ >
19
+ <slot />
20
+ </div>
21
+ </template>
@@ -0,0 +1,17 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from 'vue'
3
+ import { cn } from 'cn'
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes['class']
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <div
12
+ data-slot="alert-action"
13
+ :class="cn('absolute top-2.5 right-3', props.class)"
14
+ >
15
+ <slot />
16
+ </div>
17
+ </template>
@@ -0,0 +1,17 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from 'vue'
3
+ import { cn } from 'cn'
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes['class']
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <div
12
+ data-slot="alert-description"
13
+ :class="cn('text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground', props.class)"
14
+ >
15
+ <slot />
16
+ </div>
17
+ </template>
@@ -0,0 +1,17 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from 'vue'
3
+ import { cn } from 'cn'
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes['class']
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <div
12
+ data-slot="alert-title"
13
+ :class="cn('font-medium group-has-[>svg]/alert:col-start-2 cn-font-heading [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground', props.class)"
14
+ >
15
+ <slot />
16
+ </div>
17
+ </template>
@@ -0,0 +1,21 @@
1
+ import type { VariantProps } from 'class-variance-authority'
2
+ import { cva } from 'class-variance-authority'
3
+
4
+ export { default as Alert } from './Alert.vue'
5
+ export { default as AlertAction } from './AlertAction.vue'
6
+ export { default as AlertDescription } from './AlertDescription.vue'
7
+ export { default as AlertTitle } from './AlertTitle.vue'
8
+
9
+ export const alertVariants = cva('grid gap-0.5 rounded-lg border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*=size-])]:size-4 group/alert relative w-full', {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-card text-card-foreground',
13
+ destructive: 'text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current',
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: 'default',
18
+ },
19
+ })
20
+
21
+ export type AlertVariants = VariantProps<typeof alertVariants>
@@ -0,0 +1,22 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from 'vue'
3
+ import type { AvatarVariants } from '.'
4
+ import { cn } from 'cn'
5
+ import { AvatarRoot } from 'reka-ui'
6
+ import { avatarVariants } from '.'
7
+
8
+ const props = defineProps<{
9
+ class?: HTMLAttributes['class']
10
+ size?: AvatarVariants['size']
11
+ }>()
12
+ </script>
13
+
14
+ <template>
15
+ <AvatarRoot
16
+ data-slot="avatar"
17
+ :data-size="size ?? 'default'"
18
+ :class="cn(avatarVariants({ size }), props.class)"
19
+ >
20
+ <slot />
21
+ </AvatarRoot>
22
+ </template>
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from 'vue'
3
+ import { cn } from 'cn'
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes['class']
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <span
12
+ data-slot="avatar-badge"
13
+ :class="cn(
14
+ 'bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none',
15
+ 'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',
16
+ 'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',
17
+ 'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',
18
+ props.class,
19
+ )"
20
+ >
21
+ <slot />
22
+ </span>
23
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import type { AvatarFallbackProps } from 'reka-ui'
3
+ import type { HTMLAttributes } from 'vue'
4
+ import { reactiveOmit } from '@vueuse/core'
5
+ import { cn } from 'cn'
6
+ import { AvatarFallback } from 'reka-ui'
7
+
8
+ const props = defineProps<AvatarFallbackProps & { class?: HTMLAttributes['class'] }>()
9
+
10
+ const delegatedProps = reactiveOmit(props, 'class')
11
+ </script>
12
+
13
+ <template>
14
+ <AvatarFallback
15
+ data-slot="avatar-fallback"
16
+ v-bind="delegatedProps"
17
+ :class="cn('bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs', props.class)"
18
+ >
19
+ <slot />
20
+ </AvatarFallback>
21
+ </template>
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from 'vue'
3
+ import { cn } from 'cn'
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes['class']
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <div
12
+ data-slot="avatar-group"
13
+ :class="cn(
14
+ 'group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',
15
+ props.class,
16
+ )"
17
+ >
18
+ <slot />
19
+ </div>
20
+ </template>
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from 'vue'
3
+ import { cn } from 'cn'
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes['class']
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <div
12
+ data-slot="avatar-group-count"
13
+ :class="cn(
14
+ 'bg-muted text-muted-foreground size-8 rounded-full text-sm group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 relative flex shrink-0 items-center justify-center ring-2 ring-background',
15
+ props.class,
16
+ )"
17
+ >
18
+ <slot />
19
+ </div>
20
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import type { AvatarImageProps } from 'reka-ui'
3
+ import { AvatarImage } from 'reka-ui'
4
+
5
+ const props = defineProps<AvatarImageProps>()
6
+ </script>
7
+
8
+ <template>
9
+ <AvatarImage
10
+ data-slot="avatar-image"
11
+ v-bind="props"
12
+ class="rounded-full aspect-square size-full object-cover"
13
+ >
14
+ <slot />
15
+ </AvatarImage>
16
+ </template>
@@ -0,0 +1,27 @@
1
+ import type { VariantProps } from 'class-variance-authority'
2
+ import { cva } from 'class-variance-authority'
3
+
4
+ export { default as Avatar } from './Avatar.vue'
5
+ export { default as AvatarBadge } from './AvatarBadge.vue'
6
+ export { default as AvatarFallback } from './AvatarFallback.vue'
7
+ export { default as AvatarGroup } from './AvatarGroup.vue'
8
+ export { default as AvatarGroupCount } from './AvatarGroupCount.vue'
9
+ export { default as AvatarImage } from './AvatarImage.vue'
10
+
11
+ export const avatarVariants = cva(
12
+ 'size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:border-border after:mix-blend-darken dark:after:mix-blend-lighten',
13
+ {
14
+ variants: {
15
+ size: {
16
+ sm: '',
17
+ default: '',
18
+ lg: '',
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ size: 'default',
23
+ },
24
+ },
25
+ )
26
+
27
+ export type AvatarVariants = VariantProps<typeof avatarVariants>
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ import type { PrimitiveProps } from 'reka-ui'
3
+ import type { HTMLAttributes } from 'vue'
4
+ import type { BadgeVariants } from '.'
5
+ import { reactiveOmit } from '@vueuse/core'
6
+ import { cn } from 'cn'
7
+ import { Primitive } from 'reka-ui'
8
+ import { badgeVariants } from '.'
9
+
10
+ const props = defineProps<PrimitiveProps & {
11
+ variant?: BadgeVariants['variant']
12
+ class?: HTMLAttributes['class']
13
+ }>()
14
+
15
+ const delegatedProps = reactiveOmit(props, 'class')
16
+ </script>
17
+
18
+ <template>
19
+ <Primitive
20
+ data-slot="badge"
21
+ :data-variant="variant"
22
+ :class="cn(badgeVariants({ variant }), props.class)"
23
+ v-bind="delegatedProps"
24
+ >
25
+ <slot />
26
+ </Primitive>
27
+ </template>
@@ -0,0 +1,24 @@
1
+ import type { VariantProps } from 'class-variance-authority'
2
+ import { cva } from 'class-variance-authority'
3
+
4
+ export { default as Badge } from './Badge.vue'
5
+
6
+ export const badgeVariants = cva(
7
+ 'h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
12
+ secondary: 'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',
13
+ destructive: 'bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20',
14
+ outline: 'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',
15
+ ghost: 'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',
16
+ link: 'text-primary underline-offset-4 hover:underline',
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ variant: 'default',
21
+ },
22
+ },
23
+ )
24
+ export type BadgeVariants = VariantProps<typeof badgeVariants>
@@ -0,0 +1,31 @@
1
+ <script setup lang="ts">
2
+ import type { PrimitiveProps } from 'reka-ui'
3
+ import type { HTMLAttributes } from 'vue'
4
+ import type { ButtonVariants } from '.'
5
+ import { cn } from 'cn'
6
+ import { Primitive } from 'reka-ui'
7
+ import { buttonVariants } from '.'
8
+
9
+ interface Props extends PrimitiveProps {
10
+ variant?: ButtonVariants['variant']
11
+ size?: ButtonVariants['size']
12
+ class?: HTMLAttributes['class']
13
+ }
14
+
15
+ const props = withDefaults(defineProps<Props>(), {
16
+ as: 'button',
17
+ })
18
+ </script>
19
+
20
+ <template>
21
+ <Primitive
22
+ data-slot="button"
23
+ :data-variant="variant"
24
+ :data-size="size"
25
+ :as="as"
26
+ :as-child="asChild"
27
+ :class="cn(buttonVariants({ variant, size }), props.class)"
28
+ >
29
+ <slot />
30
+ </Primitive>
31
+ </template>
@@ -0,0 +1,35 @@
1
+ import type { VariantProps } from 'class-variance-authority'
2
+ import { cva } from 'class-variance-authority'
3
+
4
+ export { default as Button } from './Button.vue'
5
+
6
+ export const buttonVariants = cva(
7
+ 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 aria-invalid:ring-3 active:not-aria-[haspopup]:translate-y-px [&_svg:not([class*=size-])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'bg-primary text-primary-foreground hover:bg-primary/80',
12
+ outline: 'border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground shadow-xs',
13
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',
14
+ ghost: 'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground',
15
+ destructive: 'bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30',
16
+ link: 'text-primary underline-offset-4 hover:underline',
17
+ },
18
+ size: {
19
+ 'default': 'h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
20
+ 'xs': 'h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*=size-])]:size-3',
21
+ 'sm': 'h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5',
22
+ 'lg': 'h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
23
+ 'icon': 'size-9',
24
+ 'icon-xs': 'size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*=size-])]:size-3',
25
+ 'icon-sm': 'size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md',
26
+ 'icon-lg': 'size-10',
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: 'default',
31
+ size: 'default',
32
+ },
33
+ },
34
+ )
35
+ export type ButtonVariants = VariantProps<typeof buttonVariants>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from 'vue'
3
+ import { cn } from 'cn'
4
+
5
+ const props = withDefaults(defineProps<{
6
+ class?: HTMLAttributes['class']
7
+ size?: 'default' | 'sm'
8
+ }>(), {
9
+ size: 'default',
10
+ })
11
+ </script>
12
+
13
+ <template>
14
+ <div
15
+ data-slot="card"
16
+ :data-size="size"
17
+ :class="cn('ring-foreground/10 bg-card text-card-foreground gap-6 overflow-hidden rounded-xl py-6 text-sm shadow-xs ring-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col', props.class)"
18
+ >
19
+ <slot />
20
+ </div>
21
+ </template>