feat: migrate InputText to shadcn-vue Input
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
3a8dd109bb
commit
f3002c7eb1
11 changed files with 113 additions and 105 deletions
1
bun.lock
1
bun.lock
|
|
@ -39,6 +39,7 @@
|
|||
"dependencies": {
|
||||
"@lucide/vue": "^1.24.0",
|
||||
"@tailwindcss/vite": "^4.3.2",
|
||||
"@vueuse/core": "^14.3.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"reka-ui": "^2.10.1",
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
"dependencies": {
|
||||
"@lucide/vue": "^1.24.0",
|
||||
"@tailwindcss/vite": "^4.3.2",
|
||||
"@vueuse/core": "^14.3.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"reka-ui": "^2.10.1",
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
export { default as NychLoadingIcon } from './NychLoadingIcon.vue'
|
||||
export { Button, buttonVariants } from './ui/button'
|
||||
export type { ButtonVariants } from './ui/button'
|
||||
export { Input } from './ui/input'
|
||||
|
|
|
|||
31
packages/library/src/components/ui/input/Input.vue
Normal file
31
packages/library/src/components/ui/input/Input.vue
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<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"
|
||||
data-slot="input"
|
||||
:class="cn(
|
||||
'dark:bg-input/30 border-input 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 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
</template>
|
||||
1
packages/library/src/components/ui/input/index.ts
Normal file
1
packages/library/src/components/ui/input/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as Input } from "./Input.vue"
|
||||
|
|
@ -10,7 +10,7 @@ export {
|
|||
createNychthemeron,
|
||||
Button,
|
||||
LoadingIcon,
|
||||
InputText,
|
||||
Input,
|
||||
Textarea,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import type { App } from 'vue'
|
||||
import {
|
||||
InputText as PrimeInputText,
|
||||
Textarea as PrimeTextarea,
|
||||
Checkbox as PrimeCheckbox,
|
||||
CheckboxGroup as PrimeCheckboxGroup,
|
||||
|
|
@ -14,9 +13,9 @@ import {
|
|||
Tag as PrimeTag,
|
||||
} from 'primevue'
|
||||
import { ThemeLibrary, type Themeable, ThemeEngine, type LibComponent, type Key } from '../engine'
|
||||
import { NychLoadingIcon, Button, buttonVariants } from '../components'
|
||||
import { NychLoadingIcon, Button, buttonVariants, Input } from '../components'
|
||||
|
||||
export { Button, buttonVariants }
|
||||
export { Button, buttonVariants, Input }
|
||||
|
||||
/**
|
||||
* Build a Themeable that themes a PrimeVue component through its passthrough (`pt`) API.
|
||||
|
|
@ -60,7 +59,6 @@ const loadingIconThemeable: Themeable<typeof NychLoadingIcon> = {
|
|||
const nychthemeron: ThemeLibrary = {
|
||||
components: {
|
||||
LoadingIcon: loadingIconThemeable,
|
||||
InputText: ptThemeable(PrimeInputText, 'input'),
|
||||
Textarea: ptThemeable(PrimeTextarea, 'textarea'),
|
||||
Checkbox: ptThemeable(PrimeCheckbox, 'checkbox', ['box', 'input', 'icon']),
|
||||
CheckboxGroup: ptThemeable(PrimeCheckboxGroup, 'checkbox-group'),
|
||||
|
|
@ -111,8 +109,6 @@ const nychthemeron: ThemeLibrary = {
|
|||
const _engine = new ThemeEngine(nychthemeron)
|
||||
|
||||
export const LoadingIcon = _engine.getComponent<typeof NychLoadingIcon>('LoadingIcon')
|
||||
export const InputText: typeof PrimeInputText =
|
||||
_engine.getComponent<typeof PrimeInputText>('InputText')
|
||||
export const Textarea: typeof PrimeTextarea = _engine.getComponent<typeof PrimeTextarea>('Textarea')
|
||||
export const Checkbox: typeof PrimeCheckbox = _engine.getComponent<typeof PrimeCheckbox>('Checkbox')
|
||||
export const CheckboxGroup: typeof PrimeCheckboxGroup =
|
||||
|
|
@ -132,7 +128,7 @@ export const Tag: typeof PrimeTag = _engine.getComponent<typeof PrimeTag>('Tag')
|
|||
export const createNychthemeron = (): {
|
||||
Button: typeof Button
|
||||
LoadingIcon: typeof NychLoadingIcon
|
||||
InputText: typeof PrimeInputText
|
||||
Input: typeof Input
|
||||
Textarea: typeof PrimeTextarea
|
||||
Checkbox: typeof PrimeCheckbox
|
||||
CheckboxGroup: typeof PrimeCheckboxGroup
|
||||
|
|
@ -148,7 +144,7 @@ export const createNychthemeron = (): {
|
|||
} => ({
|
||||
Button,
|
||||
LoadingIcon,
|
||||
InputText,
|
||||
Input,
|
||||
Textarea,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
|
|
@ -163,7 +159,7 @@ export const createNychthemeron = (): {
|
|||
install(app: App) {
|
||||
app.component('NychButton', Button)
|
||||
app.component('NychLoadingIcon', LoadingIcon)
|
||||
app.component('NychInputText', InputText)
|
||||
app.component('NychInput', Input)
|
||||
app.component('NychTextarea', Textarea)
|
||||
app.component('NychCheckbox', Checkbox)
|
||||
app.component('NychCheckboxGroup', CheckboxGroup)
|
||||
|
|
|
|||
23
packages/library/tests/Input.spec.ts
Normal file
23
packages/library/tests/Input.spec.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { Input } from '../src/lib'
|
||||
|
||||
describe('Input', () => {
|
||||
it('renders a native input element', () => {
|
||||
const wrapper = mount(Input)
|
||||
expect(wrapper.element.tagName).toBe('INPUT')
|
||||
})
|
||||
|
||||
it('supports v-model', async () => {
|
||||
const wrapper = mount(Input, { props: { modelValue: 'Styx' } })
|
||||
expect((wrapper.element as HTMLInputElement).value).toBe('Styx')
|
||||
|
||||
await wrapper.setValue('Lethe')
|
||||
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['Lethe'])
|
||||
})
|
||||
|
||||
it('applies the disabled attribute', () => {
|
||||
const wrapper = mount(Input, { attrs: { disabled: true } })
|
||||
expect(wrapper.attributes('disabled')).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
|
@ -3,7 +3,6 @@ import type { App } from 'vue'
|
|||
import {
|
||||
createNychthemeron,
|
||||
LoadingIcon,
|
||||
InputText,
|
||||
Textarea,
|
||||
Checkbox,
|
||||
RadioButton,
|
||||
|
|
@ -71,7 +70,6 @@ describe('LoadingIcon', () => {
|
|||
|
||||
describe('themed pt components', () => {
|
||||
const cases = [
|
||||
{ name: 'InputText', component: InputText, base: 'nych-input' },
|
||||
{ name: 'Textarea', component: Textarea, base: 'nych-textarea' },
|
||||
{ name: 'Checkbox', component: Checkbox, base: 'nych-checkbox' },
|
||||
{ name: 'CheckboxGroup', component: CheckboxGroup, base: 'nych-checkbox-group' },
|
||||
|
|
|
|||
49
packages/playground/stories/Input.stories.ts
Normal file
49
packages/playground/stories/Input.stories.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { Input as NychInput } from '@nychthemeron/library'
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Input',
|
||||
component: NychInput,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof NychInput>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => ({
|
||||
components: { NychInput },
|
||||
setup() {
|
||||
const value = ref('')
|
||||
return { value }
|
||||
},
|
||||
template: `<NychInput v-model="value" placeholder="Speak, traveller…" style="width: 18rem;" />`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const Filled: Story = {
|
||||
render: () => ({
|
||||
components: { NychInput },
|
||||
setup() {
|
||||
const value = ref('Charon')
|
||||
return { value }
|
||||
},
|
||||
template: `<NychInput v-model="value" style="width: 18rem;" />`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const Disabled: Story = {
|
||||
render: () => ({
|
||||
components: { NychInput },
|
||||
setup() {
|
||||
const value = ref('Sealed')
|
||||
return { value }
|
||||
},
|
||||
template: `<NychInput v-model="value" disabled style="width: 18rem;" />`,
|
||||
}),
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { InputText as NychInputText } from '@nychthemeron/library'
|
||||
|
||||
const meta = {
|
||||
title: 'Components/InputText',
|
||||
component: NychInputText,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
size: {
|
||||
control: { type: 'select' },
|
||||
options: ['small', undefined, 'large'],
|
||||
description: 'Input size',
|
||||
},
|
||||
placeholder: {
|
||||
control: { type: 'text' },
|
||||
description: 'Placeholder text',
|
||||
},
|
||||
disabled: {
|
||||
control: { type: 'boolean' },
|
||||
description: 'Disable the input',
|
||||
},
|
||||
invalid: {
|
||||
control: { type: 'boolean' },
|
||||
description: 'Mark the input as invalid',
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof NychInputText>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const modelTemplate = (attrs: string) => ({
|
||||
components: { NychInputText },
|
||||
setup() {
|
||||
const value = ref('')
|
||||
return { value }
|
||||
},
|
||||
template: `<NychInputText v-model="value" ${attrs} />`,
|
||||
})
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => modelTemplate('placeholder="Speak, traveller…"'),
|
||||
}
|
||||
|
||||
export const Filled: Story = {
|
||||
render: () => ({
|
||||
components: { NychInputText },
|
||||
setup() {
|
||||
const value = ref('Charon')
|
||||
return { value }
|
||||
},
|
||||
template: `<NychInputText v-model="value" />`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const Invalid: Story = {
|
||||
render: () => modelTemplate('placeholder="Required" invalid'),
|
||||
}
|
||||
|
||||
export const Disabled: Story = {
|
||||
render: () => ({
|
||||
components: { NychInputText },
|
||||
setup() {
|
||||
const value = ref('Sealed')
|
||||
return { value }
|
||||
},
|
||||
template: `<NychInputText v-model="value" disabled />`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const Sizes: Story = {
|
||||
render: () => ({
|
||||
components: { NychInputText },
|
||||
setup() {
|
||||
const a = ref('')
|
||||
const b = ref('')
|
||||
const c = ref('')
|
||||
return { a, b, c }
|
||||
},
|
||||
template: `
|
||||
<div style="display: flex; flex-direction: column; gap: 1rem; width: 16rem;">
|
||||
<NychInputText v-model="a" size="small" placeholder="Small" />
|
||||
<NychInputText v-model="b" placeholder="Normal" />
|
||||
<NychInputText v-model="c" size="large" placeholder="Large" />
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
Loading…
Reference in a new issue