feat: add native select component

Signed-off-by: Ryan Wang <i@ryanc.cc>
pull/581/head
Ryan Wang 2022-04-19 12:06:17 +08:00
parent 074d5b1fea
commit 6a960e573e
10 changed files with 280 additions and 32 deletions

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import type { PropType } from "vue";
import { computed } from "vue";
import type { PropType } from "vue";
import type { Size } from "@/components/base/input/interface";
const props = defineProps({
@ -65,6 +65,7 @@ function handleInput(e: Event) {
@apply text-black;
@apply block;
@apply transition-all;
@apply appearance-none;
border: 1px solid #ced4da;
border-radius: 4px;
@ -76,6 +77,11 @@ function handleInput(e: Event) {
border-color: #4ccba0;
}
&:disabled {
@apply opacity-50;
@apply cursor-not-allowed;
}
&.input-lg {
@apply h-11;
@apply px-4;

View File

@ -0,0 +1,8 @@
// Vitest Snapshot v1
exports[`Input > should render 1`] = `
"<div class=\\"input-wrapper\\">
<!--v-if--><input class=\\"input-md\\" type=\\"text\\">
<!--v-if-->
</div>"
`;

View File

@ -0,0 +1,13 @@
<script lang="ts" setup>
defineProps({
value: {
type: [String, Number, Boolean],
},
});
</script>
<template>
<option :value="value">
<slot />
</option>
</template>

View File

@ -0,0 +1,107 @@
<script lang="ts" setup>
import type { PropType } from "vue";
import type { Size } from "./interface";
import { computed } from "vue";
import Option from "@/components/base/select/Option.vue";
const props = defineProps({
modelValue: {
type: String,
},
size: {
type: String as PropType<Size>,
default: "md",
},
disabled: {
type: Boolean,
default: false,
},
placeholder: {
type: String,
},
});
const emit = defineEmits(["update:modelValue"]);
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
:value="modelValue"
@change="handleChange"
:disabled="disabled"
:class="classes"
>
<option v-if="placeholder" value="" key="placeholder" disabled hidden>
{{ placeholder }}
</option>
<slot />
</select>
</div>
</template>
<style lang="scss">
.select-wrapper {
@apply box-border;
@apply relative;
@apply w-full;
@apply inline-flex;
select {
@apply outline-0;
@apply bg-white;
@apply antialiased;
@apply w-full;
@apply text-black;
@apply block;
@apply transition-all;
@apply appearance-none;
border: 1px solid #ced4da;
border-radius: 4px;
&:active {
border-color: #4ccba0;
}
&:focus {
border-color: #4ccba0;
}
&:disabled {
@apply opacity-50;
@apply cursor-not-allowed;
}
&.select-lg {
@apply h-11;
@apply px-4;
@apply text-lg;
}
&.select-md {
@apply h-9;
@apply px-3;
@apply text-sm;
}
&.select-sm {
@apply h-7;
@apply px-3;
@apply text-xs;
}
&.select-xs {
@apply h-6;
@apply px-2;
@apply text-xs;
}
}
}
</style>

View File

@ -0,0 +1,20 @@
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

@ -0,0 +1,31 @@
// 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

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

View File

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

View File

@ -1,82 +1,85 @@
import type { RouteRecordRaw } from "vue-router";
import HomeView from "../views/HomeView.vue";
import AboutView from "../views/AboutView.vue";
import ViewComponents from "../views/ViewComponents.vue";
export const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "Dashboard",
component: () => import("../views/HomeView.vue"),
component: HomeView,
},
{
path: "/about",
name: "about",
component: () => import("../views/AboutView.vue"),
component: AboutView,
},
{
path: "/posts",
name: "Posts",
component: () => import("../views/AboutView.vue"),
component: AboutView,
children: [
{
path: "/posts/categories",
name: "Categories",
component: () => import("../views/AboutView.vue"),
component: AboutView,
},
{
path: "/posts/tags",
name: "Tags",
component: () => import("../views/AboutView.vue"),
component: AboutView,
},
],
},
{
path: "/sheets",
name: "Sheets",
component: () => import("../views/AboutView.vue"),
component: AboutView,
},
{
path: "/comment",
name: "Comment",
component: () => import("../views/AboutView.vue"),
component: AboutView,
},
{
path: "/attachment",
name: "Attachment",
component: () => import("../views/AboutView.vue"),
component: AboutView,
},
{
path: "/themes",
name: "Themes",
component: () => import("../views/AboutView.vue"),
component: AboutView,
},
{
path: "/menus",
name: "Menus",
component: () => import("../views/AboutView.vue"),
component: AboutView,
},
{
path: "/visual",
name: "Visual",
component: () => import("../views/AboutView.vue"),
component: AboutView,
},
{
path: "/plugins",
name: "Plugins",
component: () => import("../views/AboutView.vue"),
component: AboutView,
},
{
path: "/users",
name: "Users",
component: () => import("../views/AboutView.vue"),
component: AboutView,
},
{
path: "/settings",
name: "Settings",
component: () => import("../views/AboutView.vue"),
component: AboutView,
},
{
path: "/components",
name: "Components",
component: () => import("../views/ViewComponents.vue"),
component: ViewComponents,
},
];

View File

@ -37,29 +37,71 @@
<VButton disabled type="danger"> Danger</VButton>
</div>
</section>
<section class="box border-2 rounded p-2">
<section class="box border-2 rounded p-2 mb-3">
<h1 class="text-xl font-bold mb-2">Input</h1>
<h2 class="mb-1">Size:</h2>
<div class="mb-3">
<VInput class="mb-2" placeholder="请输入邮箱" size="lg" />
<VInput class="mb-2" placeholder="请输入邮箱" size="md" />
<VInput class="mb-2" placeholder="请输入邮箱" size="sm" />
<VInput placeholder="请输入邮箱" size="xs" />
<VInput
class="mb-2"
v-model="inputValue"
placeholder="请输入邮箱"
size="lg"
/>
<VInput
class="mb-2"
v-model="inputValue"
placeholder="请输入邮箱"
size="md"
/>
<VInput
class="mb-2"
v-model="inputValue"
placeholder="请输入邮箱"
size="sm"
/>
<VInput v-model="inputValue" placeholder="请输入邮箱" size="xs" />
</div>
<h2 class="mb-1">With Icon:</h2>
<!-- <h2 class="mb-1">With Icon:</h2>-->
<!-- <div class="mb-3">-->
<!-- <VInput v-model="inputValue" class="mb-2" size="md">-->
<!-- <template #prefix>-->
<!-- <IconDashboard />-->
<!-- </template>-->
<!-- <template #suffix>-->
<!-- <IconDashboard />-->
<!-- </template>-->
<!-- </VInput>-->
<!-- </div>-->
<h2 class="mb-1">Disabled:</h2>
<div class="mb-3">
<VInput class="mb-2" size="md">
<template #prefix>
<IconDashboard />
</template>
<template #suffix>
<IconDashboard />
</template>
</VInput>
<VInput v-model="inputValue" disabled />
</div>
</section>
<section class="box border-2 rounded p-2">
<h1 class="text-xl font-bold mb-2">Select</h1>
<h2 class="mb-1">Size:</h2>
<div class="mb-3">
<VSelect v-model="selectValue">
<VOption
v-for="(option, index) in selectData"
:value="option.value"
:key="index"
>
{{ option.label }}
</VOption>
</VSelect>
</div>
<h2 class="mb-1">Disabled:</h2>
<div class="mb-3">
<VInput disabled />
<VSelect v-model="selectValue" disabled>
<VOption
v-for="(option, index) in selectData"
:value="option.value"
:key="index"
>
{{ option.label }}
</VOption>
</VSelect>
</div>
</section>
</div>
@ -70,5 +112,20 @@
import { FilledLayout } from "../layouts";
import { VButton } from "@/components/base/button";
import { VInput } from "@/components/base/input";
import { IconDashboard } from "@/core/icons";
import { VSelect, VOption } from "@/components/base/select";
import { ref } from "vue";
const inputValue = ref();
const selectValue = ref();
const selectData = [
{
value: "1",
label: "1",
},
{
value: "2",
label: "2",
},
];
</script>