feat: add input component

Signed-off-by: Ryan Wang <i@ryanc.cc>
pull/581/head
Ryan Wang 2022-04-18 18:01:03 +08:00
parent 2ca0191802
commit 074d5b1fea
9 changed files with 238 additions and 14 deletions

View File

@ -85,6 +85,7 @@ function handleClick() {
&:disabled { &:disabled {
@apply opacity-50; @apply opacity-50;
@apply cursor-not-allowed;
} }
} }

View File

@ -1,2 +1,2 @@
export type Type = "default" | "primary" | "secondary" | "error"; export type Type = "default" | "primary" | "secondary" | "danger";
export type Size = "lg" | "md" | "sm" | "xs"; export type Size = "lg" | "md" | "sm" | "xs";

View File

@ -1,17 +1,123 @@
<script lang="ts" setup> <script lang="ts" setup>
defineProps({ import type { PropType } from "vue";
import { computed } from "vue";
import type { Size } from "@/components/base/input/interface";
const props = defineProps({
modelValue: { modelValue: {
type: String, type: String,
}, },
size: {
type: String as PropType<Size>,
default: "md",
},
disabled: {
type: Boolean,
default: false,
},
placeholder: {
type: String,
},
}); });
const emit = defineEmits(["update:modelValue"]); const emit = defineEmits(["update:modelValue"]);
const classes = computed(() => {
return [`input-${props.size}`];
});
function handleInput(e: Event) { function handleInput(e: Event) {
const { value } = e.target as HTMLInputElement; const { value } = e.target as HTMLInputElement;
emit("update:modelValue", value); emit("update:modelValue", value);
} }
</script> </script>
<template> <template>
<input type="text" :value="modelValue" @input="handleInput" /> <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> </template>
<style lang="scss">
.input-wrapper {
@apply box-border;
@apply relative;
@apply w-full;
@apply inline-flex;
input {
@apply outline-0;
@apply bg-white;
@apply antialiased;
@apply resize-none;
@apply w-full;
@apply text-black;
@apply block;
@apply transition-all;
border: 1px solid #ced4da;
border-radius: 4px;
&:active {
border-color: #4ccba0;
}
&:focus {
border-color: #4ccba0;
}
&.input-lg {
@apply h-11;
@apply px-4;
@apply text-lg;
}
&.input-md {
@apply h-9;
@apply px-3;
@apply text-sm;
}
&.input-sm {
@apply h-7;
@apply px-3;
@apply text-xs;
}
&.input-xs {
@apply h-6;
@apply px-2;
@apply 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

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

View File

@ -8,5 +8,31 @@ import IconBookRead from "~icons/ri/book-read-line";
import IconPages from "~icons/ri/pages-line"; import IconPages from "~icons/ri/pages-line";
// @ts-ignore // @ts-ignore
import IconMessage from "~icons/ri/message-3-line"; import IconMessage from "~icons/ri/message-3-line";
// @ts-ignore
export { IconDashboard, IconArrowRight, IconBookRead, IconPages, IconMessage }; import IconPalette from "~icons/ri/palette-line";
// @ts-ignore
import IconListSettings from "~icons/ri/list-settings-line";
// @ts-ignore
import IconMagic from "~icons/ri/magic-line";
// @ts-ignore
import IconUserSettings from "~icons/ri/user-settings-line";
// @ts-ignore
import IconSettings from "~icons/ri/settings-4-line";
// @ts-ignore
import IconPlug from "~icons/ri/plug-2-line";
// @ts-ignore
import IconEye from "~icons/ri/eye-line";
export {
IconDashboard,
IconArrowRight,
IconBookRead,
IconPages,
IconMessage,
IconPalette,
IconListSettings,
IconMagic,
IconUserSettings,
IconSettings,
IconPlug,
IconEye,
};

View File

@ -1,8 +1,15 @@
import { import {
IconDashboard,
IconBookRead, IconBookRead,
IconPages, IconDashboard,
IconEye,
IconListSettings,
IconMagic,
IconMessage, IconMessage,
IconPages,
IconPalette,
IconPlug,
IconSettings,
IconUserSettings,
} from "@/core/icons"; } from "@/core/icons";
import type { Component } from "vue"; import type { Component } from "vue";
@ -60,17 +67,17 @@ export const menus: MenuGroupType[] = [
{ {
name: "主题", name: "主题",
path: "/themes", path: "/themes",
icon: IconDashboard, icon: IconPalette,
}, },
{ {
name: "菜单", name: "菜单",
path: "/menus", path: "/menus",
icon: IconDashboard, icon: IconListSettings,
}, },
{ {
name: "可视化", name: "可视化",
path: "/visual", path: "/visual",
icon: IconDashboard, icon: IconEye,
}, },
], ],
}, },
@ -80,17 +87,22 @@ export const menus: MenuGroupType[] = [
{ {
name: "插件", name: "插件",
path: "/plugins", path: "/plugins",
icon: IconDashboard, icon: IconPlug,
}, },
{ {
name: "用户", name: "用户",
path: "/users", path: "/users",
icon: IconDashboard, icon: IconUserSettings,
}, },
{ {
name: "设置", name: "设置",
path: "/settings", path: "/settings",
icon: IconDashboard, icon: IconSettings,
},
{
name: "组件",
path: "/components",
icon: IconMagic,
}, },
], ],
}, },

View File

@ -73,6 +73,11 @@ export const routes: Array<RouteRecordRaw> = [
name: "Settings", name: "Settings",
component: () => import("../views/AboutView.vue"), component: () => import("../views/AboutView.vue"),
}, },
{
path: "/components",
name: "Components",
component: () => import("../views/ViewComponents.vue"),
},
]; ];
export default routes; export default routes;

View File

@ -1,6 +1,5 @@
<template> <template>
<FilledLayout> <FilledLayout>
<main class="text-red-600">Hello Halo!</main>
<VInput v-model="value" /> <VInput v-model="value" />
{{ value }} {{ value }}
</FilledLayout> </FilledLayout>

View File

@ -0,0 +1,74 @@
<template>
<FilledLayout>
<div class="container p-3">
<section class="box border-2 rounded p-2 mb-3">
<h1 class="text-xl font-bold mb-2">Button</h1>
<h2 class="mb-1">Type:</h2>
<div class="mb-3">
<VButton class="mr-2" type="primary">Primary</VButton>
<VButton class="mr-2" type="secondary">Secondary</VButton>
<VButton class="mr-2" type="danger">Danger</VButton>
<VButton type="default">Default</VButton>
</div>
<h2 class="mb-1">Size:</h2>
<div class="mb-3">
<VButton class="mr-2" size="lg" type="secondary"> Large</VButton>
<VButton class="mr-2" type="secondary"> Default</VButton>
<VButton class="mr-2" size="sm" type="secondary"> sm</VButton>
<VButton size="xs" type="secondary"> xs</VButton>
</div>
<h2 class="mb-1">Circle:</h2>
<div class="mb-3">
<VButton circle class="mr-2" size="lg" type="secondary"> lg</VButton>
<VButton circle class="mr-2" type="secondary"> d</VButton>
<VButton circle class="mr-2" size="sm" type="secondary"> sm</VButton>
<VButton circle size="xs" type="secondary"> xs</VButton>
</div>
<h2 class="mb-1">Block:</h2>
<div class="mb-3">
<VButton block class="mb-2" type="primary"> Primary</VButton>
<VButton block class="mb-2" type="secondary"> Secondary</VButton>
<VButton block type="danger"> Danger</VButton>
</div>
<h2 class="mb-1">Disabled:</h2>
<div>
<VButton class="mr-2" disabled type="primary"> Primary</VButton>
<VButton class="mr-2" disabled type="secondary"> Secondary</VButton>
<VButton disabled type="danger"> Danger</VButton>
</div>
</section>
<section class="box border-2 rounded p-2">
<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" />
</div>
<h2 class="mb-1">With Icon:</h2>
<div class="mb-3">
<VInput 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 disabled />
</div>
</section>
</div>
</FilledLayout>
</template>
<script lang="ts" setup>
import { FilledLayout } from "../layouts";
import { VButton } from "@/components/base/button";
import { VInput } from "@/components/base/input";
import { IconDashboard } from "@/core/icons";
</script>