adding and displaying of entries works"
;
This commit is contained in:
90
\
Normal file
90
\
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Table, TableCaption, TableHeader, TableRow, TableHead, TableCell } from '@/components/ui/table';
|
||||||
|
import { Button } from './components/ui/button';
|
||||||
|
import { Plus } from 'lucide-vue-next';
|
||||||
|
import { entries } from './data/entries';
|
||||||
|
import { Drawer, DrawerHeader, DrawerTitle, DrawerTrigger, DrawerContent, DrawerFooter } from './components/ui/drawer';
|
||||||
|
import { Form, FormItem, FormLabel, FormField, FormControl} from '@/components/ui/form';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Textarea } from './components/ui/textarea';
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod';
|
||||||
|
import { useForm } from 'vee-validate';
|
||||||
|
import * as z from 'zod'
|
||||||
|
import moment, { Moment } from 'moment';
|
||||||
|
|
||||||
|
const createEntrySchema = toTypedSchema(z.object({
|
||||||
|
name: z.string()
|
||||||
|
text: z.string()
|
||||||
|
}))
|
||||||
|
|
||||||
|
function getDifferenceToToday(date: Moment) {
|
||||||
|
return Math.abs(date.diff(moment(), 'days'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEntry(values) {
|
||||||
|
console.log(values)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main class="flex justify-center">
|
||||||
|
<div class="wrapper sm:w-3/4 w-11/12">
|
||||||
|
<Table>
|
||||||
|
<TableCaption>List of Entries</TableCaption>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>Last Accident</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
|
||||||
|
<TableRow v-for="entry in entries">
|
||||||
|
<TableCell> {{ entry.name }} </TableCell>
|
||||||
|
<TableCell> {{ getDifferenceToToday(entry.last_reset) }} </TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<Drawer>
|
||||||
|
<DrawerTrigger as-child>
|
||||||
|
<div>
|
||||||
|
<Button variant="outline" size="icon" class="w-20 h-20 fixed right-10 bottom-10">
|
||||||
|
<Plus />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DrawerTrigger>
|
||||||
|
|
||||||
|
<DrawerContent>
|
||||||
|
<DrawerHeader>
|
||||||
|
<DrawerTitle>Create new Entry</DrawerTitle>
|
||||||
|
</DrawerHeader>
|
||||||
|
<div id="content" class="p-5">
|
||||||
|
<Form @submit="createEntry">
|
||||||
|
<FormField v-slot="{ componentField }" name="name">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Name" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription />
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel></FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea placeholder="Text" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription />
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
<DrawerFooter>
|
||||||
|
<Button>Create</Button>
|
||||||
|
</DrawerFooter>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -11,13 +11,19 @@
|
|||||||
"build:sw": "workbox generateSW workbox-config.js"
|
"build:sw": "workbox generateSW workbox-config.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@vee-validate/zod": "^4.12.5",
|
||||||
|
"@vueuse/core": "^10.9.0",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"lucide-vue-next": "^0.344.0",
|
"lucide-vue-next": "^0.344.0",
|
||||||
|
"moment": "^2.30.1",
|
||||||
"radix-vue": "^1.4.9",
|
"radix-vue": "^1.4.9",
|
||||||
"tailwind-merge": "^2.2.1",
|
"tailwind-merge": "^2.2.1",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vue": "^3.4.19"
|
"vaul-vue": "^0.1.0",
|
||||||
|
"vee-validate": "^4.12.5",
|
||||||
|
"vue": "^3.4.19",
|
||||||
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.11.24",
|
"@types/node": "^20.11.24",
|
||||||
|
|||||||
92
pnpm-lock.yaml
generated
92
pnpm-lock.yaml
generated
@@ -5,6 +5,12 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@vee-validate/zod':
|
||||||
|
specifier: ^4.12.5
|
||||||
|
version: 4.12.5(vue@3.4.19)
|
||||||
|
'@vueuse/core':
|
||||||
|
specifier: ^10.9.0
|
||||||
|
version: 10.9.0(vue@3.4.19)
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.7.0
|
specifier: ^0.7.0
|
||||||
version: 0.7.0
|
version: 0.7.0
|
||||||
@@ -14,6 +20,9 @@ dependencies:
|
|||||||
lucide-vue-next:
|
lucide-vue-next:
|
||||||
specifier: ^0.344.0
|
specifier: ^0.344.0
|
||||||
version: 0.344.0(vue@3.4.19)
|
version: 0.344.0(vue@3.4.19)
|
||||||
|
moment:
|
||||||
|
specifier: ^2.30.1
|
||||||
|
version: 2.30.1
|
||||||
radix-vue:
|
radix-vue:
|
||||||
specifier: ^1.4.9
|
specifier: ^1.4.9
|
||||||
version: 1.4.9(vue@3.4.19)
|
version: 1.4.9(vue@3.4.19)
|
||||||
@@ -23,9 +32,18 @@ dependencies:
|
|||||||
tailwindcss-animate:
|
tailwindcss-animate:
|
||||||
specifier: ^1.0.7
|
specifier: ^1.0.7
|
||||||
version: 1.0.7(tailwindcss@3.4.1)
|
version: 1.0.7(tailwindcss@3.4.1)
|
||||||
|
vaul-vue:
|
||||||
|
specifier: ^0.1.0
|
||||||
|
version: 0.1.0(typescript@5.3.3)
|
||||||
|
vee-validate:
|
||||||
|
specifier: ^4.12.5
|
||||||
|
version: 4.12.5(vue@3.4.19)
|
||||||
vue:
|
vue:
|
||||||
specifier: ^3.4.19
|
specifier: ^3.4.19
|
||||||
version: 3.4.19(typescript@5.3.3)
|
version: 3.4.19(typescript@5.3.3)
|
||||||
|
zod:
|
||||||
|
specifier: ^3.22.4
|
||||||
|
version: 3.22.4
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/node':
|
'@types/node':
|
||||||
@@ -1792,6 +1810,20 @@ packages:
|
|||||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/web-bluetooth@0.0.20:
|
||||||
|
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@vee-validate/zod@4.12.5(vue@3.4.19):
|
||||||
|
resolution: {integrity: sha512-hUjvXaa4HHvlZeosucViIDOUikQmyKaXXuL6P8LR1ETOUrBV6ntTsafJGvRYtwhXosoLYuolUD6Km737okK4Gg==}
|
||||||
|
dependencies:
|
||||||
|
type-fest: 4.11.1
|
||||||
|
vee-validate: 4.12.5(vue@3.4.19)
|
||||||
|
zod: 3.22.4
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- vue
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@vitejs/plugin-vue@5.0.4(vite@5.1.4)(vue@3.4.19):
|
/@vitejs/plugin-vue@5.0.4(vite@5.1.4)(vue@3.4.19):
|
||||||
resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==}
|
resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==}
|
||||||
engines: {node: ^18.0.0 || >=20.0.0}
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
@@ -1856,6 +1888,10 @@ packages:
|
|||||||
'@vue/compiler-dom': 3.4.19
|
'@vue/compiler-dom': 3.4.19
|
||||||
'@vue/shared': 3.4.19
|
'@vue/shared': 3.4.19
|
||||||
|
|
||||||
|
/@vue/devtools-api@6.6.1:
|
||||||
|
resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@vue/language-core@1.8.27(typescript@5.3.3):
|
/@vue/language-core@1.8.27(typescript@5.3.3):
|
||||||
resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==}
|
resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1912,6 +1948,31 @@ packages:
|
|||||||
'@vue/compiler-core': 3.4.19
|
'@vue/compiler-core': 3.4.19
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@vueuse/core@10.9.0(vue@3.4.19):
|
||||||
|
resolution: {integrity: sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==}
|
||||||
|
dependencies:
|
||||||
|
'@types/web-bluetooth': 0.0.20
|
||||||
|
'@vueuse/metadata': 10.9.0
|
||||||
|
'@vueuse/shared': 10.9.0(vue@3.4.19)
|
||||||
|
vue-demi: 0.14.7(vue@3.4.19)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@vue/composition-api'
|
||||||
|
- vue
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@vueuse/metadata@10.9.0:
|
||||||
|
resolution: {integrity: sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@vueuse/shared@10.9.0(vue@3.4.19):
|
||||||
|
resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==}
|
||||||
|
dependencies:
|
||||||
|
vue-demi: 0.14.7(vue@3.4.19)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@vue/composition-api'
|
||||||
|
- vue
|
||||||
|
dev: false
|
||||||
|
|
||||||
/acorn@8.11.3:
|
/acorn@8.11.3:
|
||||||
resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
|
resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
@@ -3294,6 +3355,10 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/moment@2.30.1:
|
||||||
|
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ms@2.1.2:
|
/ms@2.1.2:
|
||||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -4205,6 +4270,11 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/type-fest@4.11.1:
|
||||||
|
resolution: {integrity: sha512-MFMf6VkEVZAETidGGSYW2B1MjXbGX+sWIywn2QPEaJ3j08V+MwVRHMXtf2noB8ENJaD0LIun9wh5Z6OPNf1QzQ==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/typed-array-buffer@1.0.2:
|
/typed-array-buffer@1.0.2:
|
||||||
resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==}
|
resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -4327,6 +4397,27 @@ packages:
|
|||||||
/util-deprecate@1.0.2:
|
/util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
|
/vaul-vue@0.1.0(typescript@5.3.3):
|
||||||
|
resolution: {integrity: sha512-3PYWMbN3cSdsciv3fzewskxZFnX61PYq1uNsbvizXDo/8sN4SMrWkYDqWaPdTD3GTEm6wpx7j5flRLg7A5ZXbQ==}
|
||||||
|
dependencies:
|
||||||
|
'@vueuse/core': 10.9.0(vue@3.4.19)
|
||||||
|
radix-vue: 1.4.9(vue@3.4.19)
|
||||||
|
vue: 3.4.19(typescript@5.3.3)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@vue/composition-api'
|
||||||
|
- typescript
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/vee-validate@4.12.5(vue@3.4.19):
|
||||||
|
resolution: {integrity: sha512-rvaDfLPSLwTk+mf016XWE4drB8yXzOsKXiKHTb9gNXNLTtQSZ0Ww26O0/xbIFQe+n3+u8Wv1Y8uO/aLDX4fxOg==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.3.11
|
||||||
|
dependencies:
|
||||||
|
'@vue/devtools-api': 6.6.1
|
||||||
|
type-fest: 4.11.1
|
||||||
|
vue: 3.4.19(typescript@5.3.3)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/vite-plugin-pwa@0.19.0(vite@5.1.4)(workbox-build@7.0.0)(workbox-window@7.0.0):
|
/vite-plugin-pwa@0.19.0(vite@5.1.4)(workbox-build@7.0.0)(workbox-window@7.0.0):
|
||||||
resolution: {integrity: sha512-Unfb4Jk/ka4HELtpMLIPCmGcW4LFT+CL7Ri1/Of1544CVKXS2ftP91kUkNzkzeI1sGpOdVGuxprVLB9NjMoCAA==}
|
resolution: {integrity: sha512-Unfb4Jk/ka4HELtpMLIPCmGcW4LFT+CL7Ri1/Of1544CVKXS2ftP91kUkNzkzeI1sGpOdVGuxprVLB9NjMoCAA==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
@@ -4677,4 +4768,3 @@ packages:
|
|||||||
|
|
||||||
/zod@3.22.4:
|
/zod@3.22.4:
|
||||||
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
||||||
dev: true
|
|
||||||
|
|||||||
101
src/App.vue
101
src/App.vue
@@ -1,10 +1,103 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {Button} from '@/components/ui/button'
|
import { Table, TableCaption, TableHeader, TableRow, TableHead, TableCell } from '@/components/ui/table';
|
||||||
|
import { Button } from './components/ui/button';
|
||||||
|
import { Plus } from 'lucide-vue-next';
|
||||||
|
import { entries, save } from './data/entries';
|
||||||
|
import { Drawer, DrawerHeader, DrawerTitle, DrawerTrigger, DrawerContent, DrawerFooter, DrawerClose } from './components/ui/drawer';
|
||||||
|
import { Form, FormItem, FormLabel, FormField, FormControl } from '@/components/ui/form';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Textarea } from './components/ui/textarea';
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod';
|
||||||
|
import * as z from 'zod'
|
||||||
|
import moment, { Moment } from 'moment';
|
||||||
|
|
||||||
|
const createEntryZodSchema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
text: z.string()
|
||||||
|
})
|
||||||
|
|
||||||
|
type CreateEntrySchema = z.infer<typeof createEntryZodSchema>
|
||||||
|
|
||||||
|
const createEntrySchema = toTypedSchema(createEntryZodSchema)
|
||||||
|
|
||||||
|
function getDifferenceToToday(date: Moment) {
|
||||||
|
return Math.abs(date.diff(moment(), 'days'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEntry(value: CreateEntrySchema) {
|
||||||
|
entries.value.push({
|
||||||
|
last_reset: moment(),
|
||||||
|
...value
|
||||||
|
})
|
||||||
|
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Button>This is a cool button</Button>
|
<main class="flex justify-center">
|
||||||
|
<div class="wrapper sm:w-3/4 w-11/12">
|
||||||
|
<Table>
|
||||||
|
<TableCaption>List of Entries</TableCaption>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>Last Accident</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
|
||||||
|
<TableRow v-for="entry in entries">
|
||||||
|
<TableCell> {{ entry.name }} </TableCell>
|
||||||
|
<TableCell> {{ getDifferenceToToday(entry.last_reset) }} </TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<Drawer>
|
||||||
|
<DrawerTrigger as-child>
|
||||||
|
<div>
|
||||||
|
<Button variant="outline" size="icon" class="w-20 h-20 fixed right-10 bottom-10">
|
||||||
|
<Plus />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DrawerTrigger>
|
||||||
|
|
||||||
|
<DrawerContent>
|
||||||
|
<DrawerHeader>
|
||||||
|
<DrawerTitle>Create new Entry</DrawerTitle>
|
||||||
|
</DrawerHeader>
|
||||||
|
<div id="content" class="p-5">
|
||||||
|
<Form :validation-schema="createEntrySchema" @submit="createEntry">
|
||||||
|
<FormField v-slot="{ componentField }" name="name">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Name" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription />
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
<FormField v-slot="{ componentField }" name="text">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel></FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea placeholder="Text" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription />
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
<DrawerFooter>
|
||||||
|
<DrawerClose>
|
||||||
|
<Button type="submit">Create</Button>
|
||||||
|
</DrawerClose>
|
||||||
|
</DrawerFooter>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
</style>
|
|
||||||
|
|||||||
19
src/components/ui/drawer/Drawer.vue
Normal file
19
src/components/ui/drawer/Drawer.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { DrawerRootEmits, DrawerRootProps } from 'vaul-vue'
|
||||||
|
import { DrawerRoot } from 'vaul-vue'
|
||||||
|
import { useForwardPropsEmits } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<DrawerRootProps>(), {
|
||||||
|
shouldScaleBackground: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits<DrawerRootEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DrawerRoot v-bind="forwarded">
|
||||||
|
<slot />
|
||||||
|
</DrawerRoot>
|
||||||
|
</template>
|
||||||
28
src/components/ui/drawer/DrawerContent.vue
Normal file
28
src/components/ui/drawer/DrawerContent.vue
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { DrawerContent, DrawerPortal } from 'vaul-vue'
|
||||||
|
import type { DialogContentEmits, DialogContentProps } from 'radix-vue'
|
||||||
|
import { useForwardPropsEmits } from 'radix-vue'
|
||||||
|
import type { HtmlHTMLAttributes } from 'vue'
|
||||||
|
import DrawerOverlay from './DrawerOverlay.vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<DialogContentProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||||
|
const emits = defineEmits<DialogContentEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DrawerPortal>
|
||||||
|
<DrawerOverlay />
|
||||||
|
<DrawerContent
|
||||||
|
v-bind="forwarded" :class="cn(
|
||||||
|
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<div class="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||||
|
<slot />
|
||||||
|
</DrawerContent>
|
||||||
|
</DrawerPortal>
|
||||||
|
</template>
|
||||||
20
src/components/ui/drawer/DrawerDescription.vue
Normal file
20
src/components/ui/drawer/DrawerDescription.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { DrawerDescriptionProps } from 'vaul-vue'
|
||||||
|
import { DrawerDescription } from 'vaul-vue'
|
||||||
|
import { type HtmlHTMLAttributes, computed } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<DrawerDescriptionProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DrawerDescription v-bind="delegatedProps" :class="cn('text-sm text-muted-foreground', props.class)">
|
||||||
|
<slot />
|
||||||
|
</DrawerDescription>
|
||||||
|
</template>
|
||||||
14
src/components/ui/drawer/DrawerFooter.vue
Normal file
14
src/components/ui/drawer/DrawerFooter.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HtmlHTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HtmlHTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('mt-auto flex flex-col gap-2 p-4', props.class)">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
14
src/components/ui/drawer/DrawerHeader.vue
Normal file
14
src/components/ui/drawer/DrawerHeader.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HtmlHTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HtmlHTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('grid gap-1.5 p-4 text-center sm:text-left', props.class)">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
18
src/components/ui/drawer/DrawerOverlay.vue
Normal file
18
src/components/ui/drawer/DrawerOverlay.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { DrawerOverlay } from 'vaul-vue'
|
||||||
|
import type { DialogOverlayProps } from 'radix-vue'
|
||||||
|
import { type HtmlHTMLAttributes, computed } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<DialogOverlayProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DrawerOverlay v-bind="delegatedProps" :class="cn('fixed inset-0 z-50 bg-black/80', props.class)" />
|
||||||
|
</template>
|
||||||
20
src/components/ui/drawer/DrawerTitle.vue
Normal file
20
src/components/ui/drawer/DrawerTitle.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { DrawerTitleProps } from 'vaul-vue'
|
||||||
|
import { DrawerTitle } from 'vaul-vue'
|
||||||
|
import { type HtmlHTMLAttributes, computed } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<DrawerTitleProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DrawerTitle v-bind="delegatedProps" :class="cn('text-lg font-semibold leading-none tracking-tight', props.class)">
|
||||||
|
<slot />
|
||||||
|
</DrawerTitle>
|
||||||
|
</template>
|
||||||
8
src/components/ui/drawer/index.ts
Normal file
8
src/components/ui/drawer/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export { DrawerPortal, DrawerTrigger, DrawerClose } from 'vaul-vue'
|
||||||
|
export { default as Drawer } from './Drawer.vue'
|
||||||
|
export { default as DrawerOverlay } from './DrawerOverlay.vue'
|
||||||
|
export { default as DrawerContent } from './DrawerContent.vue'
|
||||||
|
export { default as DrawerHeader } from './DrawerHeader.vue'
|
||||||
|
export { default as DrawerFooter } from './DrawerFooter.vue'
|
||||||
|
export { default as DrawerTitle } from './DrawerTitle.vue'
|
||||||
|
export { default as DrawerDescription } from './DrawerDescription.vue'
|
||||||
16
src/components/ui/form/FormControl.vue
Normal file
16
src/components/ui/form/FormControl.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { Slot } from 'radix-vue'
|
||||||
|
import { useFormField } from './useFormField'
|
||||||
|
|
||||||
|
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Slot
|
||||||
|
:id="formItemId"
|
||||||
|
:aria-describedby="!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`"
|
||||||
|
:aria-invalid="!!error"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</Slot>
|
||||||
|
</template>
|
||||||
20
src/components/ui/form/FormDescription.vue
Normal file
20
src/components/ui/form/FormDescription.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { useFormField } from './useFormField'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { formDescriptionId } = useFormField()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<p
|
||||||
|
:id="formDescriptionId"
|
||||||
|
:class="cn('text-sm text-muted-foreground', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
25
src/components/ui/form/FormItem.vue
Normal file
25
src/components/ui/form/FormItem.vue
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes, InjectionKey } from 'vue'
|
||||||
|
|
||||||
|
export const FORM_ITEM_INJECTION_KEY
|
||||||
|
= Symbol() as InjectionKey<string>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { provide } from 'vue'
|
||||||
|
import { useId } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const id = useId()
|
||||||
|
provide(FORM_ITEM_INJECTION_KEY, id)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('space-y-2', props.class)">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
23
src/components/ui/form/FormLabel.vue
Normal file
23
src/components/ui/form/FormLabel.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import type { LabelProps } from 'radix-vue'
|
||||||
|
import { useFormField } from './useFormField'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
|
||||||
|
const props = defineProps<LabelProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const { error, formItemId } = useFormField()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Label
|
||||||
|
:class="cn(
|
||||||
|
error && 'text-destructive',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
:for="formItemId"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</Label>
|
||||||
|
</template>
|
||||||
16
src/components/ui/form/FormMessage.vue
Normal file
16
src/components/ui/form/FormMessage.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ErrorMessage } from 'vee-validate'
|
||||||
|
import { toValue } from 'vue'
|
||||||
|
import { useFormField } from './useFormField'
|
||||||
|
|
||||||
|
const { name, formMessageId } = useFormField()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ErrorMessage
|
||||||
|
:id="formMessageId"
|
||||||
|
as="p"
|
||||||
|
:name="toValue(name)"
|
||||||
|
class="text-sm font-medium text-destructive"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
6
src/components/ui/form/index.ts
Normal file
6
src/components/ui/form/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export { Form, Field as FormField } from 'vee-validate'
|
||||||
|
export { default as FormItem } from './FormItem.vue'
|
||||||
|
export { default as FormLabel } from './FormLabel.vue'
|
||||||
|
export { default as FormControl } from './FormControl.vue'
|
||||||
|
export { default as FormMessage } from './FormMessage.vue'
|
||||||
|
export { default as FormDescription } from './FormDescription.vue'
|
||||||
30
src/components/ui/form/useFormField.ts
Normal file
30
src/components/ui/form/useFormField.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { FieldContextKey, useFieldError, useIsFieldDirty, useIsFieldTouched, useIsFieldValid } from 'vee-validate'
|
||||||
|
import { inject } from 'vue'
|
||||||
|
import { FORM_ITEM_INJECTION_KEY } from './FormItem.vue'
|
||||||
|
|
||||||
|
export function useFormField() {
|
||||||
|
const fieldContext = inject(FieldContextKey)
|
||||||
|
const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY)
|
||||||
|
|
||||||
|
const fieldState = {
|
||||||
|
valid: useIsFieldValid(),
|
||||||
|
isDirty: useIsFieldDirty(),
|
||||||
|
isTouched: useIsFieldTouched(),
|
||||||
|
error: useFieldError(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fieldContext)
|
||||||
|
throw new Error('useFormField should be used within <FormField>')
|
||||||
|
|
||||||
|
const { name } = fieldContext
|
||||||
|
const id = fieldItemContext
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
formItemId: `${id}-form-item`,
|
||||||
|
formDescriptionId: `${id}-form-item-description`,
|
||||||
|
formMessageId: `${id}-form-item-message`,
|
||||||
|
...fieldState,
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/components/ui/input/Input.vue
Normal file
24
src/components/ui/input/Input.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
defaultValue?: string | number
|
||||||
|
modelValue?: string | number
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emits = defineEmits<{
|
||||||
|
(e: 'update:modelValue', payload: string | number): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||||
|
passive: true,
|
||||||
|
defaultValue: props.defaultValue,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<input v-model="modelValue" :class="cn('flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)">
|
||||||
|
</template>
|
||||||
1
src/components/ui/input/index.ts
Normal file
1
src/components/ui/input/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default as Input } from './Input.vue'
|
||||||
27
src/components/ui/label/Label.vue
Normal file
27
src/components/ui/label/Label.vue
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { Label, type LabelProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<LabelProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Label
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</Label>
|
||||||
|
</template>
|
||||||
1
src/components/ui/label/index.ts
Normal file
1
src/components/ui/label/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default as Label } from './Label.vue'
|
||||||
16
src/components/ui/table/Table.vue
Normal file
16
src/components/ui/table/Table.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="relative w-full overflow-auto">
|
||||||
|
<table :class="cn('w-full caption-bottom text-sm', props.class)">
|
||||||
|
<slot />
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
14
src/components/ui/table/TableBody.vue
Normal file
14
src/components/ui/table/TableBody.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tbody :class="cn('[&_tr:last-child]:border-0', props.class)">
|
||||||
|
<slot />
|
||||||
|
</tbody>
|
||||||
|
</template>
|
||||||
14
src/components/ui/table/TableCaption.vue
Normal file
14
src/components/ui/table/TableCaption.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<caption :class="cn('mt-4 text-sm text-muted-foreground', props.class)">
|
||||||
|
<slot />
|
||||||
|
</caption>
|
||||||
|
</template>
|
||||||
21
src/components/ui/table/TableCell.vue
Normal file
21
src/components/ui/table/TableCell.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<td
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'p-4 align-middle [&:has([role=checkbox])]:pr-0',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
37
src/components/ui/table/TableEmpty.vue
Normal file
37
src/components/ui/table/TableEmpty.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import TableRow from './TableRow.vue'
|
||||||
|
import TableCell from './TableCell.vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
colspan?: number
|
||||||
|
}>(), {
|
||||||
|
colspan: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'p-4 whitespace-nowrap align-middle text-sm text-foreground',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-center py-10">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</template>
|
||||||
14
src/components/ui/table/TableFooter.vue
Normal file
14
src/components/ui/table/TableFooter.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tfoot :class="cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', props.class)">
|
||||||
|
<slot />
|
||||||
|
</tfoot>
|
||||||
|
</template>
|
||||||
14
src/components/ui/table/TableHead.vue
Normal file
14
src/components/ui/table/TableHead.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<th :class="cn('h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0', props.class)">
|
||||||
|
<slot />
|
||||||
|
</th>
|
||||||
|
</template>
|
||||||
14
src/components/ui/table/TableHeader.vue
Normal file
14
src/components/ui/table/TableHeader.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<thead :class="cn('[&_tr]:border-b', props.class)">
|
||||||
|
<slot />
|
||||||
|
</thead>
|
||||||
|
</template>
|
||||||
14
src/components/ui/table/TableRow.vue
Normal file
14
src/components/ui/table/TableRow.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tr :class="cn('border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', props.class)">
|
||||||
|
<slot />
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
8
src/components/ui/table/index.ts
Normal file
8
src/components/ui/table/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export { default as Table } from './Table.vue'
|
||||||
|
export { default as TableBody } from './TableBody.vue'
|
||||||
|
export { default as TableCell } from './TableCell.vue'
|
||||||
|
export { default as TableHead } from './TableHead.vue'
|
||||||
|
export { default as TableHeader } from './TableHeader.vue'
|
||||||
|
export { default as TableRow } from './TableRow.vue'
|
||||||
|
export { default as TableCaption } from './TableCaption.vue'
|
||||||
|
export { default as TableEmpty } from './TableEmpty.vue'
|
||||||
24
src/components/ui/textarea/Textarea.vue
Normal file
24
src/components/ui/textarea/Textarea.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
defaultValue?: string | number
|
||||||
|
modelValue?: string | number
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emits = defineEmits<{
|
||||||
|
(e: 'update:modelValue', payload: string | number): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||||
|
passive: true,
|
||||||
|
defaultValue: props.defaultValue,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<textarea v-model="modelValue" :class="cn('flex min-h-20 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)" />
|
||||||
|
</template>
|
||||||
1
src/components/ui/textarea/index.ts
Normal file
1
src/components/ui/textarea/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default as Textarea } from './Textarea.vue'
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import moment, { Moment } from "moment";
|
||||||
|
import { Ref, ref } from "vue";
|
||||||
|
|
||||||
|
const localStorageKey = 'entries'
|
||||||
|
|
||||||
|
export const entries: Ref<Entry[]> = ref(parseFromPossibleString(localStorage.getItem(localStorageKey)))
|
||||||
|
|
||||||
|
export interface Entry {
|
||||||
|
name: string
|
||||||
|
text: string | undefined
|
||||||
|
last_reset: Moment
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseFromPossibleString(input: string | null): Entry[] {
|
||||||
|
if (input === null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries: Entry[] = []
|
||||||
|
const rawObjects: any[] = JSON.parse(input)
|
||||||
|
|
||||||
|
for (const rawObject of rawObjects) {
|
||||||
|
const { name, text, last_reset } = rawObject
|
||||||
|
|
||||||
|
if (name && text && last_reset) {
|
||||||
|
entries.push({ name, text, last_reset: moment(last_reset) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
export function save() {
|
||||||
|
localStorage.setItem(localStorageKey, JSON.stringify(entries.value))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user