mirror of https://github.com/halo-dev/halo-admin
refactor: remove form-related components (#762)
#### What type of PR is this? /kind improvement /milestone 2.1.x #### What this PR does / why we need it: 移除与表单相关的组件,比如 `VInput`、`VSelect` 等,以后均使用 FormKit 提供的表单元素 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/2905 #### Does this PR introduce a user-facing change? ```release-note None ```pull/767/head
parent
7f9de2ffd3
commit
7e0daaedac
|
@ -2,17 +2,12 @@ export * from "./components/avatar";
|
|||
export * from "./components/alert";
|
||||
export * from "./components/button";
|
||||
export * from "./components/card";
|
||||
export * from "./components/checkbox";
|
||||
export * from "./components/header";
|
||||
export * from "./components/input";
|
||||
export * from "./components/menu";
|
||||
export * from "./components/modal";
|
||||
export * from "./components/radio";
|
||||
export * from "./components/select";
|
||||
export * from "./components/space";
|
||||
export * from "./components/tabs";
|
||||
export * from "./components/tag";
|
||||
export * from "./components/textarea";
|
||||
export * from "./components/switch";
|
||||
export * from "./components/dialog";
|
||||
export * from "./components/pagination";
|
||||
|
|
|
@ -1,35 +1,10 @@
|
|||
<script lang="ts" setup>
|
||||
import { VCard } from "./index";
|
||||
import { VInput } from "../../components/input";
|
||||
import { VSpace } from "../../components/space";
|
||||
import { VButton } from "../../components/button";
|
||||
import { IconSettings } from "../../icons/icons";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story title="Card">
|
||||
<template #default>
|
||||
<div class="p-3">
|
||||
<VCard title="登录">
|
||||
<VSpace class="w-full" direction="column">
|
||||
<VInput placeholder="用户名"></VInput>
|
||||
<VInput placeholder="密码"></VInput>
|
||||
</VSpace>
|
||||
|
||||
<template #actions>
|
||||
<div style="padding: 12px 16px">
|
||||
<IconSettings />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<VSpace>
|
||||
<VButton type="secondary">登录</VButton>
|
||||
<VButton type="default">取消</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VCard>
|
||||
</div>
|
||||
<!-- https://lofiui.co/-->
|
||||
<div class="p-3">
|
||||
<VCard title="Page Views">
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
<template>
|
||||
<Story :init-state="initState" title="CheckBox">
|
||||
<template #default="{ state }">
|
||||
<VCheckboxGroup
|
||||
v-model="state.value"
|
||||
:options="checkboxData"
|
||||
name="fruit"
|
||||
/>
|
||||
Checked: {{ state.value.join(", ") }}
|
||||
</template>
|
||||
</Story>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { VCheckboxGroup } from "./index";
|
||||
|
||||
function initState() {
|
||||
return {
|
||||
value: ["apple"],
|
||||
};
|
||||
}
|
||||
|
||||
const checkboxData = [
|
||||
{
|
||||
value: "apple",
|
||||
label: "Apple",
|
||||
},
|
||||
{
|
||||
value: "banana",
|
||||
label: "Banana",
|
||||
},
|
||||
{
|
||||
value: "orange",
|
||||
label: "Orange",
|
||||
},
|
||||
];
|
||||
</script>
|
|
@ -1,58 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
checked?: boolean;
|
||||
value?: string | number | boolean | undefined;
|
||||
label?: string | undefined;
|
||||
name?: string | undefined;
|
||||
}>(),
|
||||
{
|
||||
checked: false,
|
||||
value: undefined,
|
||||
label: undefined,
|
||||
name: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
const id = ["checkbox", props.name, props.value]
|
||||
.filter((item) => !!item)
|
||||
.join("-");
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:checked", value: boolean): void;
|
||||
(event: "change", value: Event): void;
|
||||
}>();
|
||||
|
||||
function handleChange(e: Event) {
|
||||
const { checked } = e.target as HTMLInputElement;
|
||||
emit("update:checked", checked);
|
||||
emit("change", e);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
:class="{ 'checkbox-wrapper-checked': checked }"
|
||||
class="checkbox-wrapper"
|
||||
>
|
||||
<div class="checkbox-inner">
|
||||
<input
|
||||
:id="id"
|
||||
:checked="checked"
|
||||
:value="value"
|
||||
type="checkbox"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</div>
|
||||
<label v-if="label" :for="id" class="checkbox-label">
|
||||
{{ label }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.checkbox-wrapper {
|
||||
@apply flex
|
||||
items-center
|
||||
box-border
|
||||
flex-grow-0;
|
||||
}
|
||||
</style>
|
|
@ -1,54 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { VCheckbox } from "./index";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: string[];
|
||||
options: Array<Record<string, string>>;
|
||||
valueKey?: string;
|
||||
labelKey?: string;
|
||||
name?: string;
|
||||
}>(),
|
||||
{
|
||||
modelValue: () => [],
|
||||
valueKey: "value",
|
||||
labelKey: "label",
|
||||
name: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:modelValue", value: string[]): void;
|
||||
(event: "change", value: string[]): void;
|
||||
}>();
|
||||
|
||||
function handleChange(e: Event) {
|
||||
const { value, checked } = e.target as HTMLInputElement;
|
||||
const checkedValues = [...props.modelValue];
|
||||
|
||||
if (checked) {
|
||||
checkedValues.push(value);
|
||||
} else {
|
||||
checkedValues.splice(checkedValues.indexOf(value), 1);
|
||||
}
|
||||
emit("update:modelValue", checkedValues);
|
||||
emit("change", checkedValues);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="checkbox-group-wrapper">
|
||||
<VCheckbox
|
||||
v-for="(option, index) in options"
|
||||
:key="index"
|
||||
:checked="modelValue.includes(option[valueKey])"
|
||||
:label="option[labelKey]"
|
||||
:name="name"
|
||||
:value="option[valueKey]"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.checkbox-group-wrapper {
|
||||
}
|
||||
</style>
|
|
@ -1,61 +0,0 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { VCheckbox } from "../index";
|
||||
import { mount } from "@vue/test-utils";
|
||||
|
||||
describe("CheckBox", () => {
|
||||
it("should render", () => {
|
||||
expect(VCheckbox).toBeDefined();
|
||||
expect(mount(VCheckbox).html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should work with v-model:checked", async function () {
|
||||
const wrapper = mount({
|
||||
data() {
|
||||
return {
|
||||
checked: false,
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<v-checkbox v-model:checked="checked"/>
|
||||
`,
|
||||
components: {
|
||||
VCheckbox,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.find("input").element.checked).toBe(false);
|
||||
expect(wrapper.findComponent(VCheckbox).classes()).not.toContain(
|
||||
"checkbox-wrapper-checked"
|
||||
);
|
||||
|
||||
// change checked value
|
||||
await wrapper.setData({ checked: true });
|
||||
expect(wrapper.find("input").element.checked).toBe(true);
|
||||
expect(wrapper.findComponent(VCheckbox).classes()).toContain(
|
||||
"checkbox-wrapper-checked"
|
||||
);
|
||||
|
||||
// click on checkbox
|
||||
await wrapper.find("input").setValue(false);
|
||||
expect(wrapper.vm.checked).toBe(false);
|
||||
expect(wrapper.find("input").element.checked).toBe(false);
|
||||
expect(wrapper.findComponent(VCheckbox).classes()).not.toContain(
|
||||
"checkbox-wrapper-checked"
|
||||
);
|
||||
});
|
||||
|
||||
it("should work with label prop", async function () {
|
||||
const wrapper = mount(VCheckbox, {
|
||||
props: {
|
||||
checked: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).not.toContain("label");
|
||||
|
||||
await wrapper.setProps({ label: "label" });
|
||||
|
||||
expect(wrapper.html()).toContain("label");
|
||||
expect(wrapper.find("label").text()).toBe("label");
|
||||
});
|
||||
});
|
|
@ -1,124 +0,0 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { VCheckbox, VCheckboxGroup } from "../index";
|
||||
import { mount } from "@vue/test-utils";
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: "foo",
|
||||
label: "foo",
|
||||
},
|
||||
{
|
||||
value: "bar",
|
||||
label: "bar",
|
||||
},
|
||||
];
|
||||
|
||||
describe("CheckBoxGroup", () => {
|
||||
it("should render", () => {
|
||||
expect(VCheckboxGroup).toBeDefined();
|
||||
expect(
|
||||
mount(VCheckboxGroup, {
|
||||
props: {
|
||||
options,
|
||||
},
|
||||
}).html()
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should work with options prop", function () {
|
||||
const wrapper = mount({
|
||||
data() {
|
||||
return {
|
||||
options: options,
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<v-checkbox-group :options="options" />
|
||||
`,
|
||||
components: {
|
||||
VCheckboxGroup,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.findAllComponents(VCheckbox).length).toBe(2);
|
||||
expect(wrapper.findAllComponents(VCheckbox)[0].vm.$props.value).toBe("foo");
|
||||
expect(wrapper.findAllComponents(VCheckbox)[0].vm.$props.label).toBe("foo");
|
||||
expect(wrapper.findAllComponents(VCheckbox)[1].vm.$props.value).toBe("bar");
|
||||
expect(wrapper.findAllComponents(VCheckbox)[1].vm.$props.label).toBe("bar");
|
||||
});
|
||||
|
||||
it("should work with v-model", async function () {
|
||||
const wrapper = mount({
|
||||
data() {
|
||||
return {
|
||||
value: ["foo"],
|
||||
options: options,
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<v-checkbox-group v-model="value" :options="options" />
|
||||
`,
|
||||
components: {
|
||||
VCheckboxGroup,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.findAllComponents(VCheckbox)[0].classes()).toContain(
|
||||
"checkbox-wrapper-checked"
|
||||
);
|
||||
expect(
|
||||
wrapper.findAllComponents(VCheckbox)[0].find("input").element.checked
|
||||
).toBe(true);
|
||||
|
||||
// mock click event
|
||||
await wrapper.findAllComponents(VCheckbox)[1].find("input").setValue(true);
|
||||
|
||||
expect(wrapper.findAllComponents(VCheckbox)[1].classes()).toContain(
|
||||
"checkbox-wrapper-checked"
|
||||
);
|
||||
expect(
|
||||
wrapper.findAllComponents(VCheckbox)[1].find("input").element.checked
|
||||
).toBe(true);
|
||||
expect(wrapper.vm.value).toEqual(["foo", "bar"]);
|
||||
});
|
||||
|
||||
it("should work with valueKey and labelKey props", async function () {
|
||||
const wrapper = mount({
|
||||
data() {
|
||||
return {
|
||||
value: ["foo"],
|
||||
options: [
|
||||
{
|
||||
id: "foo",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
id: "bar",
|
||||
name: "bar",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<v-checkbox-group v-model="value" :options="options" value-key="id" label-key="name" />
|
||||
`,
|
||||
components: {
|
||||
VCheckboxGroup,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
wrapper.findAllComponents(VCheckbox)[0].find("input").attributes("value")
|
||||
).toBe("foo");
|
||||
expect(
|
||||
wrapper.findAllComponents(VCheckbox)[0].find(".checkbox-label").text()
|
||||
).toBe("foo");
|
||||
|
||||
await wrapper.findAllComponents(VCheckbox)[1].find("input").setValue(true);
|
||||
expect(wrapper.findAllComponents(VCheckbox)[1].classes()).toContain(
|
||||
"checkbox-wrapper-checked"
|
||||
);
|
||||
|
||||
expect(wrapper.vm.value).toEqual(["foo", "bar"]);
|
||||
});
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`CheckBox > should render 1`] = `
|
||||
"<div class=\\"checkbox-wrapper\\">
|
||||
<div class=\\"checkbox-inner\\"><input id=\\"checkbox\\" type=\\"checkbox\\"></div>
|
||||
<!--v-if-->
|
||||
</div>"
|
||||
`;
|
|
@ -1,12 +0,0 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`CheckBoxGroup > should render 1`] = `
|
||||
"<div class=\\"checkbox-group-wrapper\\">
|
||||
<div class=\\"checkbox-wrapper\\">
|
||||
<div class=\\"checkbox-inner\\"><input id=\\"checkbox-foo\\" type=\\"checkbox\\" value=\\"foo\\"></div><label for=\\"checkbox-foo\\" class=\\"checkbox-label\\">foo</label>
|
||||
</div>
|
||||
<div class=\\"checkbox-wrapper\\">
|
||||
<div class=\\"checkbox-inner\\"><input id=\\"checkbox-bar\\" type=\\"checkbox\\" value=\\"bar\\"></div><label for=\\"checkbox-bar\\" class=\\"checkbox-label\\">bar</label>
|
||||
</div>
|
||||
</div>"
|
||||
`;
|
|
@ -1,2 +0,0 @@
|
|||
export { default as VCheckbox } from "./CheckBox.vue";
|
||||
export { default as VCheckboxGroup } from "./CheckBoxGroup.vue";
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, mergeProps } from "vue";
|
||||
import { computed } from "vue";
|
||||
import type { RouteLocationRaw } from "vue-router";
|
||||
|
||||
const props = withDefaults(
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
<template>
|
||||
<Story :init-state="initState" title="Input">
|
||||
<template #default="{ state }">
|
||||
<div class="w-1/2">
|
||||
<VInput
|
||||
v-model="state.value"
|
||||
:disabled="state.disabled"
|
||||
:placeholder="state.placeholder"
|
||||
:size="state.size"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #controls="{ state }">
|
||||
<HstText v-model="state.value" title="Value" />
|
||||
<HstText v-model="state.placeholder" title="Placeholder" />
|
||||
<HstCheckbox v-model="state.disabled" title="Disabled" />
|
||||
</template>
|
||||
</Story>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { VInput } from "../../components/input";
|
||||
|
||||
function initState() {
|
||||
return {
|
||||
value: "Hello Halo",
|
||||
disabled: false,
|
||||
placeholder: "",
|
||||
size: "md",
|
||||
};
|
||||
}
|
||||
</script>
|
|
@ -1,128 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import type { Size } from "./interface";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: string;
|
||||
size?: Size;
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
modelValue: undefined,
|
||||
size: "md",
|
||||
disabled: false,
|
||||
placeholder: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:modelValue", value: string): void;
|
||||
}>();
|
||||
|
||||
const classes = computed(() => {
|
||||
return [`input-${props.size}`];
|
||||
});
|
||||
|
||||
function handleInput(e: Event) {
|
||||
const { value } = e.target as HTMLInputElement;
|
||||
emit("update:modelValue", value);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="input-wrapper">
|
||||
<div v-if="$slots.prefix" class="input-prefix">
|
||||
<slot name="prefix" />
|
||||
</div>
|
||||
<input
|
||||
:class="classes"
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
:value="modelValue"
|
||||
type="text"
|
||||
@input="handleInput"
|
||||
/>
|
||||
<div v-if="$slots.suffix" class="input-suffix">
|
||||
<slot name="suffix" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.input-wrapper {
|
||||
@apply box-border
|
||||
relative
|
||||
w-full
|
||||
inline-flex;
|
||||
input {
|
||||
@apply outline-0
|
||||
bg-white
|
||||
antialiased
|
||||
resize-none
|
||||
w-full
|
||||
text-black
|
||||
block
|
||||
transition-all
|
||||
appearance-none
|
||||
rounded-base;
|
||||
border: 1px solid #ced4da;
|
||||
|
||||
&:active {
|
||||
@apply border-primary;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@apply border-primary;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@apply opacity-50
|
||||
cursor-not-allowed;
|
||||
}
|
||||
|
||||
&.input-lg {
|
||||
@apply h-11
|
||||
px-4
|
||||
text-lg;
|
||||
}
|
||||
|
||||
&.input-md {
|
||||
@apply h-9
|
||||
px-3
|
||||
text-sm;
|
||||
}
|
||||
|
||||
&.input-sm {
|
||||
@apply h-7
|
||||
px-3
|
||||
text-xs;
|
||||
}
|
||||
|
||||
&.input-xs {
|
||||
@apply h-6
|
||||
px-2
|
||||
text-xs;
|
||||
}
|
||||
}
|
||||
|
||||
.input-prefix {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input-suffix {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translateY(-50%);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,67 +0,0 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { VInput } from "../index";
|
||||
import { mount } from "@vue/test-utils";
|
||||
|
||||
describe("Input", () => {
|
||||
it("should render", () => {
|
||||
expect(VInput).toBeDefined();
|
||||
expect(mount(VInput).html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should work with size prop", function () {
|
||||
["lg", "md", "sm", "xs"].forEach((size) => {
|
||||
const input = mount(VInput, { props: { size } });
|
||||
|
||||
expect(input.html()).toMatchSnapshot();
|
||||
expect(input.find("input").classes()).toContain(`input-${size}`);
|
||||
input.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
it("should work with disabled prop", async function () {
|
||||
const input = mount(VInput);
|
||||
expect(input.find("input").attributes()["disabled"]).toBeUndefined();
|
||||
|
||||
// set disabled prop
|
||||
await input.setProps({ disabled: true });
|
||||
expect(input.find("input").attributes()["disabled"]).toBeDefined();
|
||||
});
|
||||
|
||||
it("should work with placeholder prop", async function () {
|
||||
const input = mount(VInput);
|
||||
expect(input.find("input").attributes()["placeholder"]).toBeUndefined();
|
||||
|
||||
// set placeholder prop
|
||||
const placeholderText = "Please enter your name";
|
||||
await input.setProps({ placeholder: placeholderText });
|
||||
expect(input.find("input").attributes()["placeholder"]).toBe(
|
||||
placeholderText
|
||||
);
|
||||
});
|
||||
|
||||
it("should work with v-model", async function () {
|
||||
const wrapper = mount({
|
||||
data() {
|
||||
return {
|
||||
value: "Ryan",
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<v-input v-model="value"/>
|
||||
`,
|
||||
components: { VInput },
|
||||
});
|
||||
|
||||
expect(wrapper.find("input").element.value).toBe("Ryan");
|
||||
|
||||
// change value
|
||||
await wrapper.setData({
|
||||
value: "Ryan Wang",
|
||||
});
|
||||
expect(wrapper.find("input").element.value).toBe("Ryan Wang");
|
||||
|
||||
// change modelValue by input element value
|
||||
await wrapper.find("input").setValue("ryanwang");
|
||||
expect(wrapper.vm.$data.value).toBe("ryanwang");
|
||||
});
|
||||
});
|
|
@ -1,36 +0,0 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`Input > should render 1`] = `
|
||||
"<div class=\\"input-wrapper\\">
|
||||
<!--v-if--><input class=\\"input-md\\" type=\\"text\\">
|
||||
<!--v-if-->
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`Input > should work with size prop 1`] = `
|
||||
"<div class=\\"input-wrapper\\">
|
||||
<!--v-if--><input class=\\"input-lg\\" type=\\"text\\">
|
||||
<!--v-if-->
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`Input > should work with size prop 2`] = `
|
||||
"<div class=\\"input-wrapper\\">
|
||||
<!--v-if--><input class=\\"input-md\\" type=\\"text\\">
|
||||
<!--v-if-->
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`Input > should work with size prop 3`] = `
|
||||
"<div class=\\"input-wrapper\\">
|
||||
<!--v-if--><input class=\\"input-sm\\" type=\\"text\\">
|
||||
<!--v-if-->
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`Input > should work with size prop 4`] = `
|
||||
"<div class=\\"input-wrapper\\">
|
||||
<!--v-if--><input class=\\"input-xs\\" type=\\"text\\">
|
||||
<!--v-if-->
|
||||
</div>"
|
||||
`;
|
|
@ -1 +0,0 @@
|
|||
export { default as VInput } from "./Input.vue";
|
|
@ -1 +0,0 @@
|
|||
export type Size = "lg" | "md" | "sm" | "xs";
|
|
@ -1,40 +0,0 @@
|
|||
<template>
|
||||
<Story :init-state="initState" title="Radio">
|
||||
<template #default="{ state }">
|
||||
<VRadioGroup v-model="state.value" :options="radioData"></VRadioGroup>
|
||||
|
||||
<VRadio
|
||||
v-for="(option, index) in radioData"
|
||||
:key="index"
|
||||
v-model="state.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
name="fruit"
|
||||
></VRadio>
|
||||
</template>
|
||||
</Story>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { VRadio, VRadioGroup } from "../../components/radio";
|
||||
|
||||
function initState() {
|
||||
return {
|
||||
value: "apple",
|
||||
};
|
||||
}
|
||||
|
||||
const radioData = [
|
||||
{
|
||||
value: "banana",
|
||||
label: "Banana",
|
||||
},
|
||||
{
|
||||
value: "apple",
|
||||
label: "Apple",
|
||||
},
|
||||
{
|
||||
value: "orange",
|
||||
label: "Orange",
|
||||
},
|
||||
];
|
||||
</script>
|
|
@ -1,64 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: string | number | boolean;
|
||||
value?: string | number | boolean;
|
||||
label?: string;
|
||||
name?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:modelValue", value: string | number | boolean): void;
|
||||
(event: "change", value: string | number | boolean): void;
|
||||
}>();
|
||||
|
||||
const id = ["radio", props.name, props.value]
|
||||
.filter((item) => !!item)
|
||||
.join("-");
|
||||
|
||||
const checked = computed(() => props.modelValue === props.value);
|
||||
|
||||
function handleChange(e: Event) {
|
||||
const { value } = e.target as HTMLInputElement;
|
||||
emit("update:modelValue", value);
|
||||
emit("change", value);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div :class="{ 'radio-wrapper-checked': checked }" class="radio-wrapper">
|
||||
<div class="radio-inner">
|
||||
<input
|
||||
:id="id"
|
||||
:checked="checked"
|
||||
:name="name"
|
||||
:value="value"
|
||||
type="radio"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</div>
|
||||
<label v-if="label" :for="id" class="radio-label">
|
||||
{{ label }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.radio-wrapper {
|
||||
@apply flex
|
||||
items-center
|
||||
box-border
|
||||
flex-grow-0;
|
||||
|
||||
.radio-inner {
|
||||
@apply self-center
|
||||
relative;
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
@apply flex
|
||||
self-center
|
||||
items-start
|
||||
ml-3;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,44 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { VRadio } from "./index";
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: string | number | boolean;
|
||||
options?: Array<Record<string, string | number | boolean>>;
|
||||
valueKey?: string;
|
||||
labelKey?: string;
|
||||
name?: string;
|
||||
}>(),
|
||||
{
|
||||
modelValue: undefined,
|
||||
options: undefined,
|
||||
valueKey: "value",
|
||||
labelKey: "label",
|
||||
name: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:modelValue", value: string | number | boolean): void;
|
||||
(event: "change", value: string | number | boolean): void;
|
||||
}>();
|
||||
|
||||
function handleChange(value: string | number | boolean) {
|
||||
emit("update:modelValue", value);
|
||||
emit("change", value);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="radio-group-wrapper">
|
||||
<VRadio
|
||||
v-for="(option, index) in options"
|
||||
:key="index"
|
||||
:label="option[labelKey] + ''"
|
||||
:model-value="modelValue"
|
||||
:name="name"
|
||||
:value="option[valueKey]"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss"></style>
|
|
@ -1,98 +0,0 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { VRadio } from "../index";
|
||||
import { mount } from "@vue/test-utils";
|
||||
|
||||
describe("Radio", () => {
|
||||
it("should render", () => {
|
||||
expect(VRadio).toBeDefined();
|
||||
expect(mount(VRadio).html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should work with v-model", async function () {
|
||||
const wrapper = mount({
|
||||
data() {
|
||||
return {
|
||||
value: "",
|
||||
};
|
||||
},
|
||||
template: "<v-radio v-model='value' name='test' value='bar' />",
|
||||
components: {
|
||||
VRadio,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.findComponent(VRadio).classes()).not.toContain(
|
||||
"radio-wrapper-checked"
|
||||
);
|
||||
|
||||
await wrapper.setData({ value: "bar" });
|
||||
|
||||
expect(wrapper.findComponent(VRadio).classes()).toContain(
|
||||
"radio-wrapper-checked"
|
||||
);
|
||||
});
|
||||
|
||||
it("should work with label prop", async function () {
|
||||
const wrapper = mount(VRadio, {
|
||||
props: {
|
||||
value: "foo",
|
||||
},
|
||||
});
|
||||
expect(wrapper.html()).not.toContain("label");
|
||||
|
||||
await wrapper.setProps({ label: "foo" });
|
||||
|
||||
expect(wrapper.html()).toContain("label");
|
||||
expect(wrapper.find("label").text()).toBe("foo");
|
||||
});
|
||||
|
||||
it("should work with multiple radio", async function () {
|
||||
const wrapper = mount({
|
||||
data() {
|
||||
return {
|
||||
value: "foo",
|
||||
};
|
||||
},
|
||||
template:
|
||||
"<v-radio v-model='value' name='test' value='foo' label='foo' />" +
|
||||
"<v-radio v-model='value' name='test' value='bar' label='bar' />",
|
||||
components: {
|
||||
VRadio,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.findAllComponents(VRadio).length).toBe(2);
|
||||
expect(wrapper.findAllComponents(VRadio)[0].classes()).toContain(
|
||||
"radio-wrapper-checked"
|
||||
);
|
||||
|
||||
// set value to bar
|
||||
await wrapper.setData({ value: "bar" });
|
||||
|
||||
expect(wrapper.findAllComponents(VRadio)[0].classes()).not.toContain(
|
||||
"radio-wrapper-checked"
|
||||
);
|
||||
expect(wrapper.findAllComponents(VRadio)[1].classes()).toContain(
|
||||
"radio-wrapper-checked"
|
||||
);
|
||||
|
||||
// click on the first radio
|
||||
await wrapper
|
||||
.findAllComponents(VRadio)[0]
|
||||
.find('input[type="radio"]')
|
||||
.trigger("change");
|
||||
|
||||
expect(wrapper.findAllComponents(VRadio)[0].classes()).toContain(
|
||||
"radio-wrapper-checked"
|
||||
);
|
||||
|
||||
// click on the second radio
|
||||
await wrapper
|
||||
.findAllComponents(VRadio)[1]
|
||||
.find('input[type="radio"]')
|
||||
.trigger("change");
|
||||
expect(wrapper.findAllComponents(VRadio)[1].classes()).toContain(
|
||||
"radio-wrapper-checked"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,135 +0,0 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { VRadio, VRadioGroup } from "../index";
|
||||
import { mount } from "@vue/test-utils";
|
||||
|
||||
describe("RadioGroup", () => {
|
||||
it("should render", function () {
|
||||
expect(VRadioGroup).toBeDefined();
|
||||
expect(
|
||||
mount(VRadioGroup, {
|
||||
props: {
|
||||
options: [
|
||||
{
|
||||
value: "foo",
|
||||
label: "foo",
|
||||
},
|
||||
{
|
||||
value: "bar",
|
||||
label: "bar",
|
||||
},
|
||||
],
|
||||
},
|
||||
}).html()
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should work with options prop", function () {
|
||||
const wrapper = mount({
|
||||
data() {
|
||||
return {
|
||||
options: [
|
||||
{
|
||||
value: "foo",
|
||||
label: "foo",
|
||||
},
|
||||
{
|
||||
value: "bar",
|
||||
label: "bar",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
template: '<v-radio-group :options="options" />',
|
||||
components: {
|
||||
VRadioGroup,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.findAllComponents(VRadio).length).toBe(2);
|
||||
expect(wrapper.findAllComponents(VRadio)[0].vm.$props.value).toBe("foo");
|
||||
expect(wrapper.findAllComponents(VRadio)[0].vm.$props.label).toBe("foo");
|
||||
expect(wrapper.findAllComponents(VRadio)[1].vm.$props.value).toBe("bar");
|
||||
expect(wrapper.findAllComponents(VRadio)[1].vm.$props.label).toBe("bar");
|
||||
});
|
||||
|
||||
it("should work with v-model", async function () {
|
||||
const wrapper = mount({
|
||||
data() {
|
||||
return {
|
||||
value: "foo",
|
||||
options: [
|
||||
{
|
||||
value: "foo",
|
||||
label: "foo",
|
||||
},
|
||||
{
|
||||
value: "bar",
|
||||
label: "bar",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
template: '<v-radio-group v-model="value" :options="options" />',
|
||||
components: {
|
||||
VRadioGroup,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.findAllComponents(VRadio)[0].classes()).toContain(
|
||||
"radio-wrapper-checked"
|
||||
);
|
||||
|
||||
await wrapper
|
||||
.findAllComponents(VRadio)[1]
|
||||
.find('input[type="radio"]')
|
||||
.trigger("change");
|
||||
|
||||
expect(wrapper.findAllComponents(VRadio)[0].classes()).not.toContain(
|
||||
"radio-wrapper-checked"
|
||||
);
|
||||
expect(wrapper.findAllComponents(VRadio)[1].classes()).toContain(
|
||||
"radio-wrapper-checked"
|
||||
);
|
||||
});
|
||||
|
||||
it("should work with valueKey and labelKey props", async function () {
|
||||
const wrapper = mount({
|
||||
data() {
|
||||
return {
|
||||
value: "foo",
|
||||
options: [
|
||||
{
|
||||
id: "foo",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
id: "bar",
|
||||
name: "bar",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
template:
|
||||
'<v-radio-group v-model="value" :options="options" value-key="id" label-key="name" />',
|
||||
components: {
|
||||
VRadioGroup,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
wrapper.findAllComponents(VRadio)[0].find("input").attributes("value")
|
||||
).toBe("foo");
|
||||
expect(
|
||||
wrapper.findAllComponents(VRadio)[0].find(".radio-label").text()
|
||||
).toBe("foo");
|
||||
|
||||
await wrapper
|
||||
.findAllComponents(VRadio)[1]
|
||||
.find('input[type="radio"]')
|
||||
.trigger("change");
|
||||
|
||||
expect(wrapper.findAllComponents(VRadio)[1].classes()).toContain(
|
||||
"radio-wrapper-checked"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`Radio > should render 1`] = `
|
||||
"<div class=\\"radio-wrapper-checked radio-wrapper\\">
|
||||
<div class=\\"radio-inner\\"><input id=\\"radio\\" type=\\"radio\\" value=\\"false\\"></div>
|
||||
<!--v-if-->
|
||||
</div>"
|
||||
`;
|
|
@ -1,12 +0,0 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`RadioGroup > should render 1`] = `
|
||||
"<div class=\\"radio-group-wrapper\\">
|
||||
<div class=\\"radio-wrapper\\">
|
||||
<div class=\\"radio-inner\\"><input id=\\"radio-foo\\" type=\\"radio\\" value=\\"foo\\"></div><label for=\\"radio-foo\\" class=\\"radio-label\\">foo</label>
|
||||
</div>
|
||||
<div class=\\"radio-wrapper\\">
|
||||
<div class=\\"radio-inner\\"><input id=\\"radio-bar\\" type=\\"radio\\" value=\\"bar\\"></div><label for=\\"radio-bar\\" class=\\"radio-label\\">bar</label>
|
||||
</div>
|
||||
</div>"
|
||||
`;
|
|
@ -1,2 +0,0 @@
|
|||
export { default as VRadio } from "./Radio.vue";
|
||||
export { default as VRadioGroup } from "./RadioGroup.vue";
|
|
@ -1,11 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
value?: string | number | boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<option :value="value">
|
||||
<slot />
|
||||
</option>
|
||||
</template>
|
|
@ -1,49 +0,0 @@
|
|||
<template>
|
||||
<Story :init-state="initState" title="Select">
|
||||
<template #default="{ state }">
|
||||
<div class="w-1/2">
|
||||
<VSelect
|
||||
v-model="state.value"
|
||||
:disabled="state.disabled"
|
||||
:placeholder="state.placeholder"
|
||||
>
|
||||
<VOption
|
||||
v-for="(option, index) in selectData"
|
||||
:key="index"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</VOption>
|
||||
</VSelect>
|
||||
</div>
|
||||
</template>
|
||||
<template #controls="{ state }">
|
||||
<HstText v-model="state.value" title="Value" />
|
||||
<HstText v-model="state.placeholder" title="Placeholder" />
|
||||
<HstCheckbox v-model="state.disabled" title="Disabled" />
|
||||
</template>
|
||||
</Story>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { VOption, VSelect } from "../../components/select";
|
||||
|
||||
function initState() {
|
||||
return {
|
||||
value: undefined,
|
||||
disabled: false,
|
||||
placeholder: "",
|
||||
size: "md",
|
||||
};
|
||||
}
|
||||
|
||||
const selectData = [
|
||||
{
|
||||
value: "apple",
|
||||
label: "Apple",
|
||||
},
|
||||
{
|
||||
value: "banana",
|
||||
label: "Banana",
|
||||
},
|
||||
];
|
||||
</script>
|
|
@ -1,105 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import type { Size } from "./interface";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: string;
|
||||
size?: Size;
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
modelValue: undefined,
|
||||
size: "md",
|
||||
disabled: false,
|
||||
placeholder: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:modelValue", value: string): void;
|
||||
}>();
|
||||
|
||||
const classes = computed(() => {
|
||||
return [`select-${props.size}`];
|
||||
});
|
||||
|
||||
function handleChange(e: Event) {
|
||||
const { value } = e.target as HTMLSelectElement;
|
||||
emit("update:modelValue", value);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="select-wrapper">
|
||||
<select
|
||||
:class="classes"
|
||||
:disabled="disabled"
|
||||
:value="modelValue"
|
||||
@change="handleChange"
|
||||
>
|
||||
<option v-if="placeholder" key="placeholder" disabled hidden value="">
|
||||
{{ placeholder }}
|
||||
</option>
|
||||
<slot />
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.select-wrapper {
|
||||
@apply box-border
|
||||
relative
|
||||
w-full
|
||||
inline-flex;
|
||||
|
||||
select {
|
||||
@apply outline-0
|
||||
bg-white
|
||||
antialiased
|
||||
w-full
|
||||
text-black
|
||||
block
|
||||
transition-all
|
||||
appearance-none
|
||||
rounded-base;
|
||||
border: 1px solid #ced4da;
|
||||
|
||||
&:active {
|
||||
@apply border-primary;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@apply border-primary;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@apply opacity-50
|
||||
cursor-not-allowed;
|
||||
}
|
||||
|
||||
&.select-lg {
|
||||
@apply h-11
|
||||
px-4
|
||||
text-lg;
|
||||
}
|
||||
|
||||
&.select-md {
|
||||
@apply h-9
|
||||
px-3
|
||||
text-sm;
|
||||
}
|
||||
|
||||
&.select-sm {
|
||||
@apply h-7
|
||||
px-3
|
||||
text-xs;
|
||||
}
|
||||
|
||||
&.select-xs {
|
||||
@apply h-6
|
||||
px-2
|
||||
text-xs;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,20 +0,0 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { VSelect } from "../index";
|
||||
import { mount } from "@vue/test-utils";
|
||||
|
||||
describe("Select", () => {
|
||||
it("should render", () => {
|
||||
expect(VSelect).toBeDefined();
|
||||
expect(mount(VSelect).html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should work with size prop", function () {
|
||||
["lg", "md", "sm", "xs"].forEach((size) => {
|
||||
const select = mount(VSelect, { props: { size } });
|
||||
|
||||
expect(select.html()).toMatchSnapshot();
|
||||
expect(select.find("select").classes()).toContain(`select-${size}`);
|
||||
select.unmount();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,31 +0,0 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`Select > should render 1`] = `
|
||||
"<div class=\\"select-wrapper\\"><select class=\\"select-md\\">
|
||||
<!--v-if-->
|
||||
</select></div>"
|
||||
`;
|
||||
|
||||
exports[`Select > should work with size prop 1`] = `
|
||||
"<div class=\\"select-wrapper\\"><select class=\\"select-lg\\">
|
||||
<!--v-if-->
|
||||
</select></div>"
|
||||
`;
|
||||
|
||||
exports[`Select > should work with size prop 2`] = `
|
||||
"<div class=\\"select-wrapper\\"><select class=\\"select-md\\">
|
||||
<!--v-if-->
|
||||
</select></div>"
|
||||
`;
|
||||
|
||||
exports[`Select > should work with size prop 3`] = `
|
||||
"<div class=\\"select-wrapper\\"><select class=\\"select-sm\\">
|
||||
<!--v-if-->
|
||||
</select></div>"
|
||||
`;
|
||||
|
||||
exports[`Select > should work with size prop 4`] = `
|
||||
"<div class=\\"select-wrapper\\"><select class=\\"select-xs\\">
|
||||
<!--v-if-->
|
||||
</select></div>"
|
||||
`;
|
|
@ -1,2 +0,0 @@
|
|||
export { default as VSelect } from "./Select.vue";
|
||||
export { default as VOption } from "./Option.vue";
|
|
@ -1 +0,0 @@
|
|||
export type Size = "lg" | "md" | "sm" | "xs";
|
|
@ -1,30 +1,6 @@
|
|||
<template>
|
||||
<Story :init-state="initState" title="Space">
|
||||
<template #default="{ state }">
|
||||
<VRadio
|
||||
v-for="(option, index) in ['row', 'column']"
|
||||
:key="index"
|
||||
v-model="state.direction"
|
||||
:label="option"
|
||||
:value="option"
|
||||
name="direction"
|
||||
></VRadio>
|
||||
<VRadio
|
||||
v-for="(option, index) in ['start', 'center', 'end', 'stretch']"
|
||||
:key="index"
|
||||
v-model="state.align"
|
||||
:label="option"
|
||||
:value="option"
|
||||
name="align"
|
||||
></VRadio>
|
||||
<VRadio
|
||||
v-for="(option, index) in ['xs', 'sm', 'md', 'lg']"
|
||||
:key="index"
|
||||
v-model="state.spacing"
|
||||
:label="option"
|
||||
:value="option"
|
||||
name="spacing"
|
||||
></VRadio>
|
||||
<VSpace
|
||||
:align="state.align"
|
||||
:direction="state.direction"
|
||||
|
@ -40,7 +16,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { VSpace } from "../../components/space";
|
||||
import { VButton } from "../../components/button";
|
||||
import { VRadio } from "../../components/radio";
|
||||
|
||||
function initState() {
|
||||
return {
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
<template>
|
||||
<Story :init-state="initState" title="Textarea">
|
||||
<template #default="{ state }">
|
||||
<div class="w-1/2">
|
||||
<VTextarea
|
||||
v-model="state.value"
|
||||
:disabled="state.disabled"
|
||||
:placeholder="state.placeholder"
|
||||
:rows="state.rows"
|
||||
:size="state.size"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #controls="{ state }">
|
||||
<HstText v-model="state.value" title="Value" />
|
||||
<HstText v-model="state.placeholder" title="Placeholder" />
|
||||
<HstCheckbox v-model="state.disabled" title="Disabled" />
|
||||
<HstNumber v-model="state.rows" title="Rows"></HstNumber>
|
||||
</template>
|
||||
</Story>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { VTextarea } from "./index";
|
||||
|
||||
function initState() {
|
||||
return {
|
||||
value: "Hello Halo",
|
||||
disabled: false,
|
||||
placeholder: "",
|
||||
rows: 3,
|
||||
};
|
||||
}
|
||||
</script>
|
|
@ -1,72 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: string;
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
rows?: number;
|
||||
}>(),
|
||||
{
|
||||
modelValue: undefined,
|
||||
disabled: false,
|
||||
placeholder: undefined,
|
||||
rows: 3,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:modelValue", value: string): void;
|
||||
}>();
|
||||
|
||||
function handleInput(e: Event) {
|
||||
const { value } = e.target as HTMLInputElement;
|
||||
emit("update:modelValue", value);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="textarea-wrapper">
|
||||
<textarea
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
:rows="rows"
|
||||
:value="modelValue"
|
||||
@input="handleInput"
|
||||
>
|
||||
</textarea>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.textarea-wrapper {
|
||||
@apply box-border
|
||||
relative
|
||||
w-full
|
||||
inline-flex;
|
||||
textarea {
|
||||
@apply outline-0
|
||||
bg-white
|
||||
antialiased
|
||||
w-full
|
||||
text-black
|
||||
block
|
||||
transition-all
|
||||
appearance-none
|
||||
p-3
|
||||
text-sm
|
||||
rounded-base;
|
||||
border: 1px solid #ced4da;
|
||||
|
||||
&:active {
|
||||
@apply border-primary;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@apply border-primary;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@apply opacity-50
|
||||
cursor-not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,70 +0,0 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { VTextarea } from "../index";
|
||||
import { mount } from "@vue/test-utils";
|
||||
|
||||
describe("Textarea", () => {
|
||||
it("should render", function () {
|
||||
expect(VTextarea).toBeDefined();
|
||||
expect(mount(VTextarea).html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should work with placeholder prop", async function () {
|
||||
const textarea = mount(VTextarea);
|
||||
expect(
|
||||
textarea.find("textarea").attributes()["placeholder"]
|
||||
).toBeUndefined();
|
||||
|
||||
// set placeholder prop
|
||||
const placeholderText = "Please enter your text";
|
||||
await textarea.setProps({ placeholder: placeholderText });
|
||||
expect(textarea.find("textarea").attributes()["placeholder"]).toBe(
|
||||
placeholderText
|
||||
);
|
||||
});
|
||||
|
||||
it("should work with disabled prop", async function () {
|
||||
const textarea = mount(VTextarea);
|
||||
expect(textarea.find("textarea").attributes()["disabled"]).toBeUndefined();
|
||||
|
||||
// set disabled prop
|
||||
await textarea.setProps({ disabled: true });
|
||||
expect(textarea.find("textarea").attributes()["disabled"]).toBeDefined();
|
||||
});
|
||||
|
||||
it("should work with rows prop", function () {
|
||||
const textarea = mount(VTextarea, {
|
||||
propsData: {
|
||||
rows: 5,
|
||||
},
|
||||
});
|
||||
expect(textarea.find("textarea").attributes()["rows"]).toBe("5");
|
||||
});
|
||||
|
||||
it("should work with v-model", async function () {
|
||||
const wrapper = mount({
|
||||
data() {
|
||||
return {
|
||||
value: "my name is ryanwang",
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<v-textarea v-model="value"/>
|
||||
`,
|
||||
components: { VTextarea },
|
||||
});
|
||||
|
||||
expect(wrapper.find("textarea").element.value).toBe("my name is ryanwang");
|
||||
|
||||
// change value
|
||||
await wrapper.setData({
|
||||
value: "my name is ryanwang, my website is https://ryanc.cc",
|
||||
});
|
||||
expect(wrapper.find("textarea").element.value).toBe(
|
||||
"my name is ryanwang, my website is https://ryanc.cc"
|
||||
);
|
||||
|
||||
// change modelValue by textarea element value
|
||||
await wrapper.find("textarea").setValue("my name is ryanwang");
|
||||
expect(wrapper.vm.$data.value).toBe("my name is ryanwang");
|
||||
});
|
||||
});
|
|
@ -1,6 +0,0 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`Textarea > should render 1`] = `
|
||||
"<div class=\\"textarea-wrapper\\"><textarea rows=\\"3\\">
|
||||
</textarea></div>"
|
||||
`;
|
|
@ -1 +0,0 @@
|
|||
export { default as VTextarea } from "./Textarea.vue";
|
Loading…
Reference in New Issue