feat: migrate Textarea to shadcn-vue
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
f3002c7eb1
commit
382d76c2d7
7 changed files with 51 additions and 37 deletions
|
|
@ -3,3 +3,4 @@ export { default as NychLoadingIcon } from './NychLoadingIcon.vue'
|
||||||
export { Button, buttonVariants } from './ui/button'
|
export { Button, buttonVariants } from './ui/button'
|
||||||
export type { ButtonVariants } from './ui/button'
|
export type { ButtonVariants } from './ui/button'
|
||||||
export { Input } from './ui/input'
|
export { Input } from './ui/input'
|
||||||
|
export { Textarea } from './ui/textarea'
|
||||||
|
|
|
||||||
28
packages/library/src/components/ui/textarea/Textarea.vue
Normal file
28
packages/library/src/components/ui/textarea/Textarea.vue
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<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"
|
||||||
|
data-slot="textarea"
|
||||||
|
:class="cn('border-input dark:bg-input/30 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 disabled:bg-input/50 dark:disabled:bg-input/80 rounded-lg border bg-transparent px-2.5 py-2 text-base transition-colors focus-visible:ring-3 aria-invalid:ring-3 md:text-sm flex field-sizing-content min-h-16 w-full outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
1
packages/library/src/components/ui/textarea/index.ts
Normal file
1
packages/library/src/components/ui/textarea/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as Textarea } from "./Textarea.vue"
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
import {
|
import {
|
||||||
Textarea as PrimeTextarea,
|
|
||||||
Checkbox as PrimeCheckbox,
|
Checkbox as PrimeCheckbox,
|
||||||
CheckboxGroup as PrimeCheckboxGroup,
|
CheckboxGroup as PrimeCheckboxGroup,
|
||||||
RadioButton as PrimeRadioButton,
|
RadioButton as PrimeRadioButton,
|
||||||
|
|
@ -13,9 +12,9 @@ import {
|
||||||
Tag as PrimeTag,
|
Tag as PrimeTag,
|
||||||
} from 'primevue'
|
} from 'primevue'
|
||||||
import { ThemeLibrary, type Themeable, ThemeEngine, type LibComponent, type Key } from '../engine'
|
import { ThemeLibrary, type Themeable, ThemeEngine, type LibComponent, type Key } from '../engine'
|
||||||
import { NychLoadingIcon, Button, buttonVariants, Input } from '../components'
|
import { NychLoadingIcon, Button, buttonVariants, Input, Textarea } from '../components'
|
||||||
|
|
||||||
export { Button, buttonVariants, Input }
|
export { Button, buttonVariants, Input, Textarea }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a Themeable that themes a PrimeVue component through its passthrough (`pt`) API.
|
* Build a Themeable that themes a PrimeVue component through its passthrough (`pt`) API.
|
||||||
|
|
@ -59,7 +58,6 @@ const loadingIconThemeable: Themeable<typeof NychLoadingIcon> = {
|
||||||
const nychthemeron: ThemeLibrary = {
|
const nychthemeron: ThemeLibrary = {
|
||||||
components: {
|
components: {
|
||||||
LoadingIcon: loadingIconThemeable,
|
LoadingIcon: loadingIconThemeable,
|
||||||
Textarea: ptThemeable(PrimeTextarea, 'textarea'),
|
|
||||||
Checkbox: ptThemeable(PrimeCheckbox, 'checkbox', ['box', 'input', 'icon']),
|
Checkbox: ptThemeable(PrimeCheckbox, 'checkbox', ['box', 'input', 'icon']),
|
||||||
CheckboxGroup: ptThemeable(PrimeCheckboxGroup, 'checkbox-group'),
|
CheckboxGroup: ptThemeable(PrimeCheckboxGroup, 'checkbox-group'),
|
||||||
RadioButton: ptThemeable(PrimeRadioButton, 'radio', ['box', 'input', 'icon']),
|
RadioButton: ptThemeable(PrimeRadioButton, 'radio', ['box', 'input', 'icon']),
|
||||||
|
|
@ -109,7 +107,6 @@ const nychthemeron: ThemeLibrary = {
|
||||||
const _engine = new ThemeEngine(nychthemeron)
|
const _engine = new ThemeEngine(nychthemeron)
|
||||||
|
|
||||||
export const LoadingIcon = _engine.getComponent<typeof NychLoadingIcon>('LoadingIcon')
|
export const LoadingIcon = _engine.getComponent<typeof NychLoadingIcon>('LoadingIcon')
|
||||||
export const Textarea: typeof PrimeTextarea = _engine.getComponent<typeof PrimeTextarea>('Textarea')
|
|
||||||
export const Checkbox: typeof PrimeCheckbox = _engine.getComponent<typeof PrimeCheckbox>('Checkbox')
|
export const Checkbox: typeof PrimeCheckbox = _engine.getComponent<typeof PrimeCheckbox>('Checkbox')
|
||||||
export const CheckboxGroup: typeof PrimeCheckboxGroup =
|
export const CheckboxGroup: typeof PrimeCheckboxGroup =
|
||||||
_engine.getComponent<typeof PrimeCheckboxGroup>('CheckboxGroup')
|
_engine.getComponent<typeof PrimeCheckboxGroup>('CheckboxGroup')
|
||||||
|
|
@ -129,7 +126,7 @@ export const createNychthemeron = (): {
|
||||||
Button: typeof Button
|
Button: typeof Button
|
||||||
LoadingIcon: typeof NychLoadingIcon
|
LoadingIcon: typeof NychLoadingIcon
|
||||||
Input: typeof Input
|
Input: typeof Input
|
||||||
Textarea: typeof PrimeTextarea
|
Textarea: typeof Textarea
|
||||||
Checkbox: typeof PrimeCheckbox
|
Checkbox: typeof PrimeCheckbox
|
||||||
CheckboxGroup: typeof PrimeCheckboxGroup
|
CheckboxGroup: typeof PrimeCheckboxGroup
|
||||||
RadioButton: typeof PrimeRadioButton
|
RadioButton: typeof PrimeRadioButton
|
||||||
|
|
|
||||||
18
packages/library/tests/Textarea.spec.ts
Normal file
18
packages/library/tests/Textarea.spec.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { Textarea } from '../src/lib'
|
||||||
|
|
||||||
|
describe('Textarea', () => {
|
||||||
|
it('renders a native textarea element', () => {
|
||||||
|
const wrapper = mount(Textarea)
|
||||||
|
expect(wrapper.element.tagName).toBe('TEXTAREA')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('supports v-model', async () => {
|
||||||
|
const wrapper = mount(Textarea, { props: { modelValue: 'An omen' } })
|
||||||
|
expect((wrapper.element as HTMLTextAreaElement).value).toBe('An omen')
|
||||||
|
|
||||||
|
await wrapper.setValue('A crow')
|
||||||
|
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['A crow'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -3,7 +3,6 @@ import type { App } from 'vue'
|
||||||
import {
|
import {
|
||||||
createNychthemeron,
|
createNychthemeron,
|
||||||
LoadingIcon,
|
LoadingIcon,
|
||||||
Textarea,
|
|
||||||
Checkbox,
|
Checkbox,
|
||||||
RadioButton,
|
RadioButton,
|
||||||
CheckboxGroup,
|
CheckboxGroup,
|
||||||
|
|
@ -70,7 +69,6 @@ describe('LoadingIcon', () => {
|
||||||
|
|
||||||
describe('themed pt components', () => {
|
describe('themed pt components', () => {
|
||||||
const cases = [
|
const cases = [
|
||||||
{ name: 'Textarea', component: Textarea, base: 'nych-textarea' },
|
|
||||||
{ name: 'Checkbox', component: Checkbox, base: 'nych-checkbox' },
|
{ name: 'Checkbox', component: Checkbox, base: 'nych-checkbox' },
|
||||||
{ name: 'CheckboxGroup', component: CheckboxGroup, base: 'nych-checkbox-group' },
|
{ name: 'CheckboxGroup', component: CheckboxGroup, base: 'nych-checkbox-group' },
|
||||||
{ name: 'RadioButton', component: RadioButton, base: 'nych-radio' },
|
{ name: 'RadioButton', component: RadioButton, base: 'nych-radio' },
|
||||||
|
|
|
||||||
|
|
@ -10,24 +10,6 @@ const meta = {
|
||||||
layout: 'centered',
|
layout: 'centered',
|
||||||
},
|
},
|
||||||
tags: ['autodocs'],
|
tags: ['autodocs'],
|
||||||
argTypes: {
|
|
||||||
placeholder: {
|
|
||||||
control: { type: 'text' },
|
|
||||||
description: 'Placeholder text',
|
|
||||||
},
|
|
||||||
rows: {
|
|
||||||
control: { type: 'number' },
|
|
||||||
description: 'Visible rows',
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
control: { type: 'boolean' },
|
|
||||||
description: 'Disable the textarea',
|
|
||||||
},
|
|
||||||
invalid: {
|
|
||||||
control: { type: 'boolean' },
|
|
||||||
description: 'Mark the textarea as invalid',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof NychTextarea>
|
} satisfies Meta<typeof NychTextarea>
|
||||||
|
|
||||||
export default meta
|
export default meta
|
||||||
|
|
@ -57,17 +39,6 @@ export const Filled: Story = {
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Invalid: Story = {
|
|
||||||
render: () => ({
|
|
||||||
components: { NychTextarea },
|
|
||||||
setup() {
|
|
||||||
const value = ref('')
|
|
||||||
return { value }
|
|
||||||
},
|
|
||||||
template: `<NychTextarea v-model="value" rows="4" invalid placeholder="Required" style="width: 22rem;" />`,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Disabled: Story = {
|
export const Disabled: Story = {
|
||||||
render: () => ({
|
render: () => ({
|
||||||
components: { NychTextarea },
|
components: { NychTextarea },
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue