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
Ryan Wang 2022-12-12 17:12:42 +08:00 committed by GitHub
parent 7f9de2ffd3
commit 7e0daaedac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1 additions and 1479 deletions

View File

@ -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";

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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");
});
});

View File

@ -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"]);
});
});

View File

@ -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>"
`;

View File

@ -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>"
`;

View File

@ -1,2 +0,0 @@
export { default as VCheckbox } from "./CheckBox.vue";
export { default as VCheckboxGroup } from "./CheckBoxGroup.vue";

View File

@ -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(

View File

@ -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>

View File

@ -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>

View File

@ -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");
});
});

View File

@ -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>"
`;

View File

@ -1 +0,0 @@
export { default as VInput } from "./Input.vue";

View File

@ -1 +0,0 @@
export type Size = "lg" | "md" | "sm" | "xs";

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"
);
});
});

View File

@ -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"
);
});
});

View File

@ -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>"
`;

View File

@ -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>"
`;

View File

@ -1,2 +0,0 @@
export { default as VRadio } from "./Radio.vue";
export { default as VRadioGroup } from "./RadioGroup.vue";

View File

@ -1,11 +0,0 @@
<script lang="ts" setup>
defineProps<{
value?: string | number | boolean;
}>();
</script>
<template>
<option :value="value">
<slot />
</option>
</template>

View File

@ -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>

View File

@ -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>

View File

@ -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();
});
});
});

View File

@ -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>"
`;

View File

@ -1,2 +0,0 @@
export { default as VSelect } from "./Select.vue";
export { default as VOption } from "./Option.vue";

View File

@ -1 +0,0 @@
export type Size = "lg" | "md" | "sm" | "xs";

View File

@ -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 {

View File

@ -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>

View File

@ -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>

View File

@ -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");
});
});

View File

@ -1,6 +0,0 @@
// Vitest Snapshot v1
exports[`Textarea > should render 1`] = `
"<div class=\\"textarea-wrapper\\"><textarea rows=\\"3\\">
</textarea></div>"
`;

View File

@ -1 +0,0 @@
export { default as VTextarea } from "./Textarea.vue";