@reinvented/design 0.1.0 → 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/DESIGN_GUIDE.md +148 -0
- package/README.md +39 -162
- package/docs/components/index.md +50 -0
- package/docs/conventions.md +74 -0
- package/docs/layouts/index.md +32 -0
- package/docs/patterns/index.md +39 -0
- package/docs/rules.md +43 -0
- package/docs/visual-polish.md +141 -0
- package/package.json +40 -61
- package/src/components/ui/avatar/Avatar.vue +14 -0
- package/src/components/ui/avatar/index.ts +1 -0
- package/src/components/ui/badge/Badge.vue +27 -0
- package/src/components/ui/badge/index.ts +1 -0
- package/src/components/ui/button/Button.vue +66 -0
- package/src/components/ui/button/index.ts +1 -0
- package/src/components/ui/card/Card.vue +13 -0
- package/src/components/ui/card/CardContent.vue +7 -0
- package/src/components/ui/card/CardDescription.vue +7 -0
- package/src/components/ui/card/CardFooter.vue +7 -0
- package/src/components/ui/card/CardHeader.vue +9 -0
- package/src/components/ui/card/CardTitle.vue +7 -0
- package/src/components/ui/card/index.ts +6 -0
- package/src/components/ui/input/Input.vue +23 -0
- package/src/components/ui/input/index.ts +1 -0
- package/src/components/ui/lib/utils.ts +2 -0
- package/src/components/ui/scroll-area/ScrollArea.vue +13 -0
- package/src/components/ui/scroll-area/index.ts +1 -0
- package/src/components/ui/separator/Separator.vue +16 -0
- package/src/components/ui/separator/index.ts +1 -0
- package/src/components/ui/skeleton/Skeleton.vue +9 -0
- package/src/components/ui/skeleton/index.ts +1 -0
- package/src/env.d.ts +7 -0
- package/src/index.ts +209 -0
- package/src/lib/utils.ts +7 -0
- package/src/patterns/DetailView.vue +46 -0
- package/src/patterns/EmptyState.vue +27 -0
- package/src/patterns/FormView.vue +34 -0
- package/src/patterns/ListView.vue +45 -0
- package/src/styles/index.css +4 -0
- package/src/styles/tokens.css +144 -0
- package/tailwind.config.js +108 -0
- package/tsconfig.json +7 -0
- package/dist/index.css +0 -1890
- package/dist/index.d.ts +0 -406
- package/dist/index.js +0 -1721
- package/dist/index.js.map +0 -1
- package/tailwind.config.ts +0 -174
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Visual Polish Guide
|
|
2
|
+
|
|
3
|
+
Standards for animations, transitions, loading patterns, and interaction polish.
|
|
4
|
+
|
|
5
|
+
## Transition Durations
|
|
6
|
+
|
|
7
|
+
| Duration | Use |
|
|
8
|
+
|----------|-----|
|
|
9
|
+
| `duration-150` (150ms) | Hover states, color changes, opacity |
|
|
10
|
+
| `duration-200` (200ms) | Modals open/close, tooltips, popovers |
|
|
11
|
+
| `duration-300` (300ms) | Page transitions, slide-ins, expanded sections |
|
|
12
|
+
|
|
13
|
+
**Easing**: Use `ease-out` for entrances, `ease-in` for exits, `ease-in-out` for state changes.
|
|
14
|
+
|
|
15
|
+
## Modal Animations
|
|
16
|
+
|
|
17
|
+
```vue
|
|
18
|
+
<!-- Dialog with fade + scale -->
|
|
19
|
+
<DialogContent class="
|
|
20
|
+
data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95
|
|
21
|
+
data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95
|
|
22
|
+
duration-200
|
|
23
|
+
">
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## List Item Animations
|
|
27
|
+
|
|
28
|
+
```vue
|
|
29
|
+
<TransitionGroup name="list" tag="div">
|
|
30
|
+
<Card v-for="item in items" :key="item.id">
|
|
31
|
+
<!-- content -->
|
|
32
|
+
</Card>
|
|
33
|
+
</TransitionGroup>
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
.list-enter-active { transition: all 0.3s ease-out; }
|
|
37
|
+
.list-leave-active { transition: all 0.2s ease-in; }
|
|
38
|
+
.list-enter-from { opacity: 0; transform: translateY(8px); }
|
|
39
|
+
.list-leave-to { opacity: 0; transform: translateX(-16px); }
|
|
40
|
+
.list-move { transition: transform 0.3s ease; }
|
|
41
|
+
</style>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Button Loading State
|
|
45
|
+
|
|
46
|
+
```vue
|
|
47
|
+
<script setup>
|
|
48
|
+
import { ref } from 'vue'
|
|
49
|
+
import { Button } from '@reinvented/design'
|
|
50
|
+
import { Loader2 } from 'lucide-vue-next'
|
|
51
|
+
|
|
52
|
+
const saving = ref(false)
|
|
53
|
+
async function handleSave() {
|
|
54
|
+
saving.value = true
|
|
55
|
+
try {
|
|
56
|
+
await saveMutation()
|
|
57
|
+
} finally {
|
|
58
|
+
saving.value = false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<Button :disabled="saving" @click="handleSave">
|
|
65
|
+
<Loader2 v-if="saving" class="w-4 h-4 mr-2 animate-spin" />
|
|
66
|
+
{{ saving ? 'Saving...' : 'Save' }}
|
|
67
|
+
</Button>
|
|
68
|
+
</template>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Skeleton Loading
|
|
72
|
+
|
|
73
|
+
Skeletons must match the shape of the actual content:
|
|
74
|
+
|
|
75
|
+
```vue
|
|
76
|
+
<!-- Skeleton for a card list -->
|
|
77
|
+
<div class="space-y-4">
|
|
78
|
+
<div v-for="i in 3" :key="i" class="p-4 border rounded-lg">
|
|
79
|
+
<div class="flex items-center gap-3">
|
|
80
|
+
<Skeleton class="w-10 h-10 rounded-full" />
|
|
81
|
+
<div class="flex-1 space-y-2">
|
|
82
|
+
<Skeleton class="h-4 w-3/4" />
|
|
83
|
+
<Skeleton class="h-3 w-1/2" />
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Toast Notifications
|
|
91
|
+
|
|
92
|
+
```vue
|
|
93
|
+
<script setup>
|
|
94
|
+
import { toast } from '@reinvented/design'
|
|
95
|
+
|
|
96
|
+
// Success
|
|
97
|
+
toast.success('Task created')
|
|
98
|
+
|
|
99
|
+
// Error with action
|
|
100
|
+
toast.error('Failed to save', {
|
|
101
|
+
action: { label: 'Retry', onClick: () => handleSave() }
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// Promise-based (auto shows loading → success/error)
|
|
105
|
+
toast.promise(saveMutation(), {
|
|
106
|
+
loading: 'Saving...',
|
|
107
|
+
success: 'Saved!',
|
|
108
|
+
error: 'Failed to save'
|
|
109
|
+
})
|
|
110
|
+
</script>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Focus Management
|
|
114
|
+
|
|
115
|
+
- **Modal open**: Focus first interactive element inside the modal
|
|
116
|
+
- **Modal close**: Return focus to the trigger element
|
|
117
|
+
- **After delete**: Focus the next item in the list (or empty state)
|
|
118
|
+
- **After create**: Focus the newly created item (or close modal and show toast)
|
|
119
|
+
- **Tab order**: Logical reading order, top-to-bottom, left-to-right
|
|
120
|
+
|
|
121
|
+
## Hover Effects
|
|
122
|
+
|
|
123
|
+
```vue
|
|
124
|
+
<!-- Card with hover lift -->
|
|
125
|
+
<Card class="transition-shadow duration-150 hover:shadow-md cursor-pointer">
|
|
126
|
+
|
|
127
|
+
<!-- Button with subtle scale on press -->
|
|
128
|
+
<Button class="active:scale-[0.98] transition-transform duration-150">
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Optimistic Updates
|
|
132
|
+
|
|
133
|
+
Use for:
|
|
134
|
+
- Toggles (like/unlike, pin/unpin)
|
|
135
|
+
- Status changes (mark complete)
|
|
136
|
+
- Reordering (drag and drop)
|
|
137
|
+
|
|
138
|
+
Do NOT use for:
|
|
139
|
+
- Creation (need server-generated ID)
|
|
140
|
+
- Deletion (hard to undo if server rejects)
|
|
141
|
+
- Complex mutations (multiple side effects)
|
package/package.json
CHANGED
|
@@ -1,74 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reinvented/design",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Reinvented platform design system — premium UI components and tokens",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"author": "Reinvented",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "https://github.com/reinvented/ri-flex"
|
|
10
|
-
},
|
|
3
|
+
"version": "0.2.0",
|
|
11
4
|
"type": "module",
|
|
12
|
-
"
|
|
13
|
-
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts",
|
|
8
|
+
"./styles": "./src/styles/index.css",
|
|
9
|
+
"./tokens": "./src/styles/tokens.css",
|
|
10
|
+
"./src/styles/*": "./src/styles/*",
|
|
11
|
+
"./src/*": "./src/*",
|
|
12
|
+
"./tailwind.config.js": "./tailwind.config.js",
|
|
13
|
+
"./patterns/*": "./src/patterns/*"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
16
|
+
"src/",
|
|
17
|
+
"docs/",
|
|
18
|
+
"tailwind.config.js",
|
|
19
|
+
"tsconfig.json",
|
|
20
|
+
"DESIGN_GUIDE.md"
|
|
19
21
|
],
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
".": {
|
|
24
|
-
"import": "./dist/index.js",
|
|
25
|
-
"types": "./dist/index.d.ts"
|
|
26
|
-
},
|
|
27
|
-
"./css": "./dist/index.css",
|
|
28
|
-
"./tailwind": "./tailwind.config.ts"
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "vue-tsc --noEmit",
|
|
24
|
+
"lint": "eslint src"
|
|
29
25
|
},
|
|
30
26
|
"peerDependencies": {
|
|
31
|
-
"
|
|
32
|
-
"react-dom": "^18 || ^19"
|
|
27
|
+
"vue": "^3.5.0"
|
|
33
28
|
},
|
|
34
29
|
"dependencies": {
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"@
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"@
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"@
|
|
49
|
-
"class-variance-authority": "^0.7.1",
|
|
50
|
-
"clsx": "^2.1.1",
|
|
51
|
-
"cmdk": "^1.0.4",
|
|
52
|
-
"lucide-react": "^0.469.0",
|
|
53
|
-
"tailwind-merge": "^2.6.0"
|
|
30
|
+
"class-variance-authority": "^0.7.0",
|
|
31
|
+
"clsx": "^2.1.0",
|
|
32
|
+
"tailwind-merge": "^2.6.0",
|
|
33
|
+
"lucide-vue-next": "^0.460.0",
|
|
34
|
+
"radix-vue": "^1.9.0",
|
|
35
|
+
"@vueuse/core": "^11.0.0",
|
|
36
|
+
"embla-carousel-vue": "^8.5.0",
|
|
37
|
+
"vaul-vue": "^0.2.0",
|
|
38
|
+
"vee-validate": "^4.14.0",
|
|
39
|
+
"@vee-validate/zod": "^4.14.0",
|
|
40
|
+
"zod": "^3.23.0",
|
|
41
|
+
"vue-sonner": "^1.2.0",
|
|
42
|
+
"v-calendar": "^3.1.0",
|
|
43
|
+
"@tanstack/vue-table": "^8.21.0"
|
|
54
44
|
},
|
|
55
45
|
"devDependencies": {
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"postcss
|
|
62
|
-
"tailwindcss": "^3.4.17",
|
|
63
|
-
"tsup": "^8.3.6",
|
|
64
|
-
"typescript": "^5.7.3",
|
|
65
|
-
"@reinvented/tsconfig": "0.0.0"
|
|
66
|
-
},
|
|
67
|
-
"scripts": {
|
|
68
|
-
"build": "tsup && postcss src/index.css -o dist/index.css",
|
|
69
|
-
"dev": "tsup --watch",
|
|
70
|
-
"lint": "eslint .",
|
|
71
|
-
"typecheck": "tsc --noEmit",
|
|
72
|
-
"clean": "rm -rf dist"
|
|
46
|
+
"vue": "^3.5.0",
|
|
47
|
+
"typescript": "^5.6.0",
|
|
48
|
+
"vue-tsc": "^2.1.0",
|
|
49
|
+
"tailwindcss": "^3.4.0",
|
|
50
|
+
"autoprefixer": "^10.4.0",
|
|
51
|
+
"postcss": "^8.4.0"
|
|
73
52
|
}
|
|
74
|
-
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '../lib/utils'
|
|
4
|
+
interface Props { class?: HTMLAttributes['class']; src?: string; alt?: string; fallback?: string }
|
|
5
|
+
const props = defineProps<Props>()
|
|
6
|
+
</script>
|
|
7
|
+
<template>
|
|
8
|
+
<span :class="cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', props.class)">
|
|
9
|
+
<img v-if="src" :src="src" :alt="alt" class="aspect-square h-full w-full object-cover" />
|
|
10
|
+
<span v-else class="flex h-full w-full items-center justify-center rounded-full bg-muted text-sm font-medium">
|
|
11
|
+
{{ fallback || '?' }}
|
|
12
|
+
</span>
|
|
13
|
+
</span>
|
|
14
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Avatar } from './Avatar.vue'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type HTMLAttributes, computed } from 'vue'
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
4
|
+
import { cn } from '../lib/utils'
|
|
5
|
+
|
|
6
|
+
const badgeVariants = cva(
|
|
7
|
+
'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
|
|
12
|
+
secondary: 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
13
|
+
destructive: 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
|
|
14
|
+
outline: 'text-foreground',
|
|
15
|
+
success: 'border-transparent bg-success text-success-foreground shadow',
|
|
16
|
+
warning: 'border-transparent bg-warning text-warning-foreground shadow',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: { variant: 'default' },
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
type BadgeVariants = VariantProps<typeof badgeVariants>
|
|
24
|
+
interface Props { variant?: BadgeVariants['variant']; class?: HTMLAttributes['class'] }
|
|
25
|
+
const props = withDefaults(defineProps<Props>(), { variant: 'default' })
|
|
26
|
+
</script>
|
|
27
|
+
<template><div :class="cn(badgeVariants({ variant }), props.class)"><slot /></div></template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Badge } from './Badge.vue'
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type HTMLAttributes, computed } from 'vue'
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
4
|
+
import { cn } from '../lib/utils'
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
|
12
|
+
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
|
13
|
+
outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
|
|
14
|
+
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
|
15
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
16
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
17
|
+
success: 'bg-success text-success-foreground shadow-sm hover:bg-success/90',
|
|
18
|
+
},
|
|
19
|
+
size: {
|
|
20
|
+
default: 'h-9 px-4 py-2',
|
|
21
|
+
xs: 'h-7 rounded px-2 text-xs',
|
|
22
|
+
sm: 'h-8 rounded-md px-3 text-xs',
|
|
23
|
+
lg: 'h-10 rounded-md px-8',
|
|
24
|
+
xl: 'h-12 rounded-lg px-10 text-base',
|
|
25
|
+
icon: 'h-9 w-9',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: {
|
|
29
|
+
variant: 'default',
|
|
30
|
+
size: 'default',
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
export type ButtonVariants = VariantProps<typeof buttonVariants>
|
|
36
|
+
|
|
37
|
+
interface Props {
|
|
38
|
+
variant?: ButtonVariants['variant']
|
|
39
|
+
size?: ButtonVariants['size']
|
|
40
|
+
as?: string
|
|
41
|
+
class?: HTMLAttributes['class']
|
|
42
|
+
disabled?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
46
|
+
as: 'button',
|
|
47
|
+
variant: 'default',
|
|
48
|
+
size: 'default',
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const delegatedProps = computed(() => {
|
|
52
|
+
const { class: _, ...rest } = props
|
|
53
|
+
return rest
|
|
54
|
+
})
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<template>
|
|
58
|
+
<component
|
|
59
|
+
:is="as"
|
|
60
|
+
:class="cn(buttonVariants({ variant, size }), props.class)"
|
|
61
|
+
:disabled="disabled"
|
|
62
|
+
v-bind="delegatedProps"
|
|
63
|
+
>
|
|
64
|
+
<slot />
|
|
65
|
+
</component>
|
|
66
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Button } from './Button.vue'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type HTMLAttributes, computed } from 'vue'
|
|
3
|
+
import { cn } from '../lib/utils'
|
|
4
|
+
|
|
5
|
+
interface Props { class?: HTMLAttributes['class'] }
|
|
6
|
+
const props = defineProps<Props>()
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<div :class="cn('rounded-xl border bg-card text-card-foreground shadow', props.class)">
|
|
11
|
+
<slot />
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '../lib/utils'
|
|
4
|
+
interface Props { class?: HTMLAttributes['class'] }
|
|
5
|
+
const props = defineProps<Props>()
|
|
6
|
+
</script>
|
|
7
|
+
<template><div :class="cn('p-6 pt-0', props.class)"><slot /></div></template>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '../lib/utils'
|
|
4
|
+
interface Props { class?: HTMLAttributes['class'] }
|
|
5
|
+
const props = defineProps<Props>()
|
|
6
|
+
</script>
|
|
7
|
+
<template><p :class="cn('text-sm text-muted-foreground', props.class)"><slot /></p></template>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '../lib/utils'
|
|
4
|
+
interface Props { class?: HTMLAttributes['class'] }
|
|
5
|
+
const props = defineProps<Props>()
|
|
6
|
+
</script>
|
|
7
|
+
<template><div :class="cn('flex items-center p-6 pt-0', props.class)"><slot /></div></template>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '../lib/utils'
|
|
4
|
+
interface Props { class?: HTMLAttributes['class'] }
|
|
5
|
+
const props = defineProps<Props>()
|
|
6
|
+
</script>
|
|
7
|
+
<template>
|
|
8
|
+
<div :class="cn('flex flex-col space-y-1.5 p-6', props.class)"><slot /></div>
|
|
9
|
+
</template>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '../lib/utils'
|
|
4
|
+
interface Props { class?: HTMLAttributes['class'] }
|
|
5
|
+
const props = defineProps<Props>()
|
|
6
|
+
</script>
|
|
7
|
+
<template><h3 :class="cn('font-semibold leading-none tracking-tight', props.class)"><slot /></h3></template>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as Card } from './Card.vue'
|
|
2
|
+
export { default as CardHeader } from './CardHeader.vue'
|
|
3
|
+
export { default as CardTitle } from './CardTitle.vue'
|
|
4
|
+
export { default as CardDescription } from './CardDescription.vue'
|
|
5
|
+
export { default as CardContent } from './CardContent.vue'
|
|
6
|
+
export { default as CardFooter } from './CardFooter.vue'
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '../lib/utils'
|
|
4
|
+
interface Props {
|
|
5
|
+
class?: HTMLAttributes['class']
|
|
6
|
+
type?: string
|
|
7
|
+
placeholder?: string
|
|
8
|
+
modelValue?: string | number
|
|
9
|
+
disabled?: boolean
|
|
10
|
+
}
|
|
11
|
+
const props = withDefaults(defineProps<Props>(), { type: 'text' })
|
|
12
|
+
const emit = defineEmits<{ 'update:modelValue': [value: string] }>()
|
|
13
|
+
</script>
|
|
14
|
+
<template>
|
|
15
|
+
<input
|
|
16
|
+
:type="type"
|
|
17
|
+
:value="modelValue"
|
|
18
|
+
:placeholder="placeholder"
|
|
19
|
+
:disabled="disabled"
|
|
20
|
+
:class="cn('flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50', props.class)"
|
|
21
|
+
@input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
|
22
|
+
/>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Input } from './Input.vue'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '../lib/utils'
|
|
4
|
+
interface Props { class?: HTMLAttributes['class']; orientation?: 'horizontal' | 'vertical' }
|
|
5
|
+
const props = withDefaults(defineProps<Props>(), { orientation: 'vertical' })
|
|
6
|
+
</script>
|
|
7
|
+
<template>
|
|
8
|
+
<div :class="cn('relative overflow-hidden', props.class)" :style="{ overflow: 'auto' }">
|
|
9
|
+
<div class="h-full w-full rounded-[inherit]">
|
|
10
|
+
<slot />
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as ScrollArea } from './ScrollArea.vue'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '../lib/utils'
|
|
4
|
+
interface Props { class?: HTMLAttributes['class']; orientation?: 'horizontal' | 'vertical' }
|
|
5
|
+
const props = withDefaults(defineProps<Props>(), { orientation: 'horizontal' })
|
|
6
|
+
</script>
|
|
7
|
+
<template>
|
|
8
|
+
<div
|
|
9
|
+
role="separator"
|
|
10
|
+
:class="cn(
|
|
11
|
+
'shrink-0 bg-border',
|
|
12
|
+
orientation === 'horizontal' ? 'h-px w-full' : 'h-full w-px',
|
|
13
|
+
props.class
|
|
14
|
+
)"
|
|
15
|
+
/>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Separator } from './Separator.vue'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '../lib/utils'
|
|
4
|
+
interface Props { class?: HTMLAttributes['class'] }
|
|
5
|
+
const props = defineProps<Props>()
|
|
6
|
+
</script>
|
|
7
|
+
<template>
|
|
8
|
+
<div :class="cn('animate-pulse rounded-md bg-primary/10', props.class)" />
|
|
9
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Skeleton } from './Skeleton.vue'
|