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/alert";
|
||||||
export * from "./components/button";
|
export * from "./components/button";
|
||||||
export * from "./components/card";
|
export * from "./components/card";
|
||||||
export * from "./components/checkbox";
|
|
||||||
export * from "./components/header";
|
export * from "./components/header";
|
||||||
export * from "./components/input";
|
|
||||||
export * from "./components/menu";
|
export * from "./components/menu";
|
||||||
export * from "./components/modal";
|
export * from "./components/modal";
|
||||||
export * from "./components/radio";
|
|
||||||
export * from "./components/select";
|
|
||||||
export * from "./components/space";
|
export * from "./components/space";
|
||||||
export * from "./components/tabs";
|
export * from "./components/tabs";
|
||||||
export * from "./components/tag";
|
export * from "./components/tag";
|
||||||
export * from "./components/textarea";
|
|
||||||
export * from "./components/switch";
|
export * from "./components/switch";
|
||||||
export * from "./components/dialog";
|
export * from "./components/dialog";
|
||||||
export * from "./components/pagination";
|
export * from "./components/pagination";
|
||||||
|
|
|
@ -1,35 +1,10 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { VCard } from "./index";
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Story title="Card">
|
<Story title="Card">
|
||||||
<template #default>
|
<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/-->
|
<!-- https://lofiui.co/-->
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
<VCard title="Page Views">
|
<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>
|
<script lang="ts" setup>
|
||||||
import { computed, mergeProps } from "vue";
|
import { computed } from "vue";
|
||||||
import type { RouteLocationRaw } from "vue-router";
|
import type { RouteLocationRaw } from "vue-router";
|
||||||
|
|
||||||
const props = withDefaults(
|
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>
|
<template>
|
||||||
<Story :init-state="initState" title="Space">
|
<Story :init-state="initState" title="Space">
|
||||||
<template #default="{ state }">
|
<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
|
<VSpace
|
||||||
:align="state.align"
|
:align="state.align"
|
||||||
:direction="state.direction"
|
:direction="state.direction"
|
||||||
|
@ -40,7 +16,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { VSpace } from "../../components/space";
|
import { VSpace } from "../../components/space";
|
||||||
import { VButton } from "../../components/button";
|
import { VButton } from "../../components/button";
|
||||||
import { VRadio } from "../../components/radio";
|
|
||||||
|
|
||||||
function initState() {
|
function initState() {
|
||||||
return {
|
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