Skip to content

Commit

Permalink
Add shadcn Form, Input and Select
Browse files Browse the repository at this point in the history
  • Loading branch information
PabloLec committed May 8, 2024
1 parent 47ed482 commit 9e42105
Show file tree
Hide file tree
Showing 25 changed files with 517 additions and 1 deletion.
6 changes: 5 additions & 1 deletion test-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@
},
"dependencies": {
"@tanstack/vue-table": "^8.16.0",
"@vee-validate/zod": "^4.12.7",
"@vueuse/core": "^10.9.0",
"axios": "^1.6.8",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-vue-next": "^0.378.0",
"radix-vue": "^1.7.4",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"vue": "^3.4.21"
"vee-validate": "^4.12.7",
"vue": "^3.4.21",
"zod": "^3.23.7"
},
"devDependencies": {
"@types/node": "^20.12.11",
Expand Down
50 changes: 50 additions & 0 deletions test-frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions test-frontend/src/components/ui/form/FormControl.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 0 deletions test-frontend/src/components/ui/form/FormDescription.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 25 additions & 0 deletions test-frontend/src/components/ui/form/FormItem.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 0 deletions test-frontend/src/components/ui/form/FormLabel.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 16 additions & 0 deletions test-frontend/src/components/ui/form/FormMessage.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 6 additions & 0 deletions test-frontend/src/components/ui/form/index.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 30 additions & 0 deletions test-frontend/src/components/ui/form/useFormField.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 24 additions & 0 deletions test-frontend/src/components/ui/input/Input.vue
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions test-frontend/src/components/ui/input/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Input } from './Input.vue'
27 changes: 27 additions & 0 deletions test-frontend/src/components/ui/label/Label.vue
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions test-frontend/src/components/ui/label/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Label } from './Label.vue'
15 changes: 15 additions & 0 deletions test-frontend/src/components/ui/select/Select.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script setup lang="ts">
import type { SelectRootEmits, SelectRootProps } from 'radix-vue'
import { SelectRoot, useForwardPropsEmits } from 'radix-vue'
const props = defineProps<SelectRootProps>()
const emits = defineEmits<SelectRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>

<template>
<SelectRoot v-bind="forwarded">
<slot />
</SelectRoot>
</template>
Loading

0 comments on commit 9e42105

Please sign in to comment.