Skip to content

Commit 9ea64be

Browse files
author
Lasim
committed
feat(frontend): add field components with props and templates
1 parent 2258555 commit 9ea64be

File tree

13 files changed

+295
-7
lines changed

13 files changed

+295
-7
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import type { FieldVariants } from "."
4+
import { cn } from "@/lib/utils"
5+
import { fieldVariants } from "."
6+
7+
const props = defineProps<{
8+
class?: HTMLAttributes["class"]
9+
orientation?: FieldVariants["orientation"]
10+
}>()
11+
</script>
12+
13+
<template>
14+
<div
15+
role="group"
16+
data-slot="field"
17+
:data-orientation="orientation"
18+
:class="cn(
19+
fieldVariants({ orientation }),
20+
props.class,
21+
)"
22+
>
23+
<slot />
24+
</div>
25+
</template>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import { cn } from "@/lib/utils"
4+
5+
const props = defineProps<{
6+
class?: HTMLAttributes["class"]
7+
}>()
8+
</script>
9+
10+
<template>
11+
<div
12+
data-slot="field-content"
13+
:class="cn(
14+
'group/field-content flex flex-1 flex-col gap-1.5 leading-snug',
15+
props.class,
16+
)"
17+
>
18+
<slot />
19+
</div>
20+
</template>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import { cn } from "@/lib/utils"
4+
5+
const props = defineProps<{
6+
class?: HTMLAttributes["class"]
7+
}>()
8+
</script>
9+
10+
<template>
11+
<p
12+
data-slot="field-description"
13+
:class="cn(
14+
'text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',
15+
'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',
16+
'[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
17+
props.class,
18+
)"
19+
>
20+
<slot />
21+
</p>
22+
</template>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import { computed } from "vue"
4+
import { cn } from "@/lib/utils"
5+
6+
const props = defineProps<{
7+
class?: HTMLAttributes["class"]
8+
errors?: Array<{ message?: string } | undefined>
9+
}>()
10+
11+
const content = computed(() => {
12+
if (!props.errors || props.errors.length === 0)
13+
return null
14+
15+
if (props.errors.length === 1 && props.errors[0]?.message) {
16+
return props.errors[0].message
17+
}
18+
19+
return props.errors.some(e => e?.message)
20+
? props.errors
21+
: null
22+
})
23+
</script>
24+
25+
<template>
26+
<div
27+
v-if="$slots.default || content"
28+
role="alert"
29+
data-slot="field-error"
30+
:class="cn('text-destructive text-sm font-normal', props.class)"
31+
>
32+
<slot v-if="$slots.default" />
33+
34+
<template v-else-if="typeof content === 'string'">
35+
{{ content }}
36+
</template>
37+
38+
<ul v-else-if="Array.isArray(content)" class="ml-4 flex list-disc flex-col gap-1">
39+
<li v-for="(error, index) in content" :key="index">
40+
{{ error?.message }}
41+
</li>
42+
</ul>
43+
</div>
44+
</template>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import { cn } from "@/lib/utils"
4+
5+
const props = defineProps<{
6+
class?: HTMLAttributes["class"]
7+
}>()
8+
</script>
9+
10+
<template>
11+
<div
12+
data-slot="field-group"
13+
:class="cn(
14+
'group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4',
15+
props.class,
16+
)"
17+
>
18+
<slot />
19+
</div>
20+
</template>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import { cn } from "@/lib/utils"
4+
import { Label } from '@/components/ui/label'
5+
6+
const props = defineProps<{
7+
class?: HTMLAttributes["class"]
8+
}>()
9+
</script>
10+
11+
<template>
12+
<Label
13+
data-slot="field-label"
14+
:class="cn(
15+
'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50',
16+
'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&_>[data-slot=field]]:p-4',
17+
'has-[[data-state=checked]]:bg-primary/5 has-[[data-state=checked]]:border-primary dark:has-[[data-state=checked]]:bg-primary/10',
18+
props.class,
19+
)"
20+
>
21+
<slot />
22+
</Label>
23+
</template>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import { cn } from "@/lib/utils"
4+
5+
const props = defineProps<{
6+
class?: HTMLAttributes["class"]
7+
variant?: "legend" | "label"
8+
}>()
9+
</script>
10+
11+
<template>
12+
<legend
13+
data-slot="field-legend"
14+
:data-variant="variant"
15+
:class="cn(
16+
'mb-3 font-medium',
17+
'data-[variant=legend]:text-base',
18+
'data-[variant=label]:text-sm',
19+
props.class,
20+
)"
21+
>
22+
<slot />
23+
</legend>
24+
</template>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import { cn } from "@/lib/utils"
4+
import { Separator } from '@/components/ui/separator'
5+
6+
const props = defineProps<{
7+
class?: HTMLAttributes["class"]
8+
}>()
9+
</script>
10+
11+
<template>
12+
<div
13+
data-slot="field-separator"
14+
:data-content="!!$slots.default"
15+
:class="cn(
16+
'relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2',
17+
props.class,
18+
)"
19+
>
20+
<Separator class="absolute inset-0 top-1/2" />
21+
<span
22+
v-if="$slots.default"
23+
class="bg-background text-muted-foreground relative mx-auto block w-fit px-2"
24+
data-slot="field-separator-content"
25+
>
26+
<slot />
27+
</span>
28+
</div>
29+
</template>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import { cn } from "@/lib/utils"
4+
5+
const props = defineProps<{
6+
class?: HTMLAttributes["class"]
7+
}>()
8+
</script>
9+
10+
<template>
11+
<fieldset
12+
data-slot="field-set"
13+
:class="cn(
14+
'flex flex-col gap-6',
15+
'has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
16+
props.class,
17+
)"
18+
>
19+
<slot />
20+
</fieldset>
21+
</template>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import { cn } from "@/lib/utils"
4+
5+
const props = defineProps<{
6+
class?: HTMLAttributes["class"]
7+
}>()
8+
</script>
9+
10+
<template>
11+
<div
12+
data-slot="field-label"
13+
:class="cn(
14+
'flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50',
15+
props.class,
16+
)"
17+
>
18+
<slot />
19+
</div>
20+
</template>

0 commit comments

Comments
 (0)