mirror of
https://github.com/sendevia/website.git
synced 2026-03-06 07:40:50 +08:00
feat: add reusable Button component
This commit is contained in:
261
.vitepress/theme/components/Button.vue
Normal file
261
.vitepress/theme/components/Button.vue
Normal file
@@ -0,0 +1,261 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
text?: string;
|
||||
href?: string;
|
||||
icon?: string;
|
||||
size?: "xs" | "s" | "m" | "l" | "xl";
|
||||
shape?: "round" | "square";
|
||||
color?: "elevated" | "filled" | "tonal" | "outlined" | "standard" | "text";
|
||||
target?: "_blank" | "_self" | "_parent" | "_top";
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: "s",
|
||||
shape: "round",
|
||||
color: "filled",
|
||||
target: "_blank",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a v-if="href" :href="href" :target="target" class="md-button" :class="[props.shape, props.size, props.color, props.icon ? 'icon' : '']">
|
||||
<span v-if="props.icon">
|
||||
{{ props.icon }}
|
||||
</span>
|
||||
{{ props.text }}
|
||||
</a>
|
||||
<button v-else class="md-button" :class="[props.shape, props.size, props.color, props.icon ? 'icon' : '']">
|
||||
<span v-if="props.icon">
|
||||
{{ props.icon }}
|
||||
</span>
|
||||
{{ props.text }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "../styles/mixin";
|
||||
|
||||
.md-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
position: relative;
|
||||
|
||||
text-decoration: none !important;
|
||||
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
border-width: 0px;
|
||||
|
||||
background-color: transparent;
|
||||
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
transition: border-radius var(--md-sys-motion-spring-fast-spatial-duration) var(--md-sys-motion-spring-fast-spatial);
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
background-color: var(--md-sys-state-hover-state-layer);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
@include mixin.focus-ring($thickness: 3, $offset: 2);
|
||||
|
||||
&::after {
|
||||
background-color: var(--md-sys-state-focus-state-layer);
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
&::after {
|
||||
background-color: var(--md-sys-state-pressed-state-layer);
|
||||
}
|
||||
}
|
||||
|
||||
&.xs {
|
||||
@include mixin.typescale-style("label-large");
|
||||
|
||||
gap: 8px;
|
||||
|
||||
height: 32px;
|
||||
|
||||
padding-inline: 12px;
|
||||
|
||||
border-width: 1px;
|
||||
|
||||
span {
|
||||
@include mixin.material-symbols($size: 20);
|
||||
}
|
||||
|
||||
&.round {
|
||||
border-radius: var(--md-sys-shape-corner-full);
|
||||
}
|
||||
|
||||
&.square {
|
||||
border-radius: var(--md-sys-shape-corner-medium);
|
||||
}
|
||||
|
||||
&:active {
|
||||
border-radius: var(--md-sys-shape-corner-medium);
|
||||
}
|
||||
}
|
||||
|
||||
&.s {
|
||||
@include mixin.typescale-style("label-large");
|
||||
|
||||
gap: 8px;
|
||||
|
||||
height: 40px;
|
||||
|
||||
padding-inline: 16px;
|
||||
|
||||
border-width: 1px;
|
||||
|
||||
span {
|
||||
@include mixin.material-symbols($size: 20);
|
||||
}
|
||||
|
||||
&.round {
|
||||
border-radius: var(--md-sys-shape-corner-full);
|
||||
}
|
||||
|
||||
&.square {
|
||||
border-radius: var(--md-sys-shape-corner-medium);
|
||||
}
|
||||
|
||||
&:active {
|
||||
border-radius: var(--md-sys-shape-corner-medium);
|
||||
}
|
||||
}
|
||||
|
||||
&.m {
|
||||
@include mixin.typescale-style("title-medium");
|
||||
|
||||
gap: 8px;
|
||||
|
||||
height: 56px;
|
||||
|
||||
padding-inline: 24px;
|
||||
|
||||
border-width: 1px;
|
||||
|
||||
span {
|
||||
@include mixin.material-symbols($size: 24);
|
||||
}
|
||||
|
||||
&.round {
|
||||
border-radius: var(--md-sys-shape-corner-full);
|
||||
}
|
||||
|
||||
&.square {
|
||||
border-radius: var(--md-sys-shape-corner-large);
|
||||
}
|
||||
|
||||
&:active {
|
||||
border-radius: var(--md-sys-shape-corner-large);
|
||||
}
|
||||
}
|
||||
|
||||
&.l {
|
||||
@include mixin.typescale-style("headline-small");
|
||||
|
||||
gap: 12px;
|
||||
|
||||
height: 96px;
|
||||
|
||||
padding-inline: 48px;
|
||||
|
||||
border-width: 2px;
|
||||
|
||||
span {
|
||||
@include mixin.material-symbols($size: 32);
|
||||
}
|
||||
|
||||
&.round {
|
||||
border-radius: var(--md-sys-shape-corner-full);
|
||||
}
|
||||
|
||||
&.square {
|
||||
border-radius: var(--md-sys-shape-corner-extra-large);
|
||||
}
|
||||
|
||||
&:active {
|
||||
border-radius: var(--md-sys-shape-corner-extra-large);
|
||||
}
|
||||
}
|
||||
|
||||
&.xl {
|
||||
@include mixin.typescale-style("headline-large");
|
||||
|
||||
gap: 16px;
|
||||
|
||||
height: 136px;
|
||||
|
||||
padding-inline: 64px;
|
||||
|
||||
border-width: 3px;
|
||||
|
||||
span {
|
||||
@include mixin.material-symbols($size: 40);
|
||||
}
|
||||
|
||||
&.round {
|
||||
border-radius: var(--md-sys-shape-corner-full);
|
||||
}
|
||||
|
||||
&.square {
|
||||
border-radius: var(--md-sys-shape-corner-extra-large);
|
||||
}
|
||||
|
||||
&:active {
|
||||
border-radius: var(--md-sys-shape-corner-extra-large);
|
||||
}
|
||||
}
|
||||
|
||||
&.elevated {
|
||||
color: var(--md-sys-color-primary);
|
||||
|
||||
background-color: var(--md-sys-color-surface-container-low);
|
||||
|
||||
box-shadow: 0px 1px 3px var(--md-sys-color-shadow);
|
||||
}
|
||||
|
||||
&.filled {
|
||||
color: var(--md-sys-color-on-primary);
|
||||
|
||||
background-color: var(--md-sys-color-primary);
|
||||
}
|
||||
|
||||
&.tonal {
|
||||
color: var(--md-sys-color-on-secondary-container);
|
||||
|
||||
background-color: var(--md-sys-color-secondary-container);
|
||||
}
|
||||
|
||||
&.outlined {
|
||||
color: var(--md-sys-color-on-surface-variant);
|
||||
|
||||
border-color: var(--md-sys-color-outline-variant);
|
||||
}
|
||||
|
||||
&.standard,
|
||||
&.text {
|
||||
color: var(--md-sys-color-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,14 @@
|
||||
import Layout from "./layouts/Default.vue";
|
||||
import type { Theme } from "vitepress";
|
||||
|
||||
import Layout from "./layouts/Default.vue";
|
||||
import Button from "./components/Button.vue";
|
||||
|
||||
import "./styles/main.scss";
|
||||
|
||||
export default {
|
||||
Layout,
|
||||
enhanceApp({ app }) {
|
||||
app.component("MainLaylout", Layout);
|
||||
app.component("MainLayout", Layout);
|
||||
app.component("MaterialButton", Button);
|
||||
},
|
||||
} satisfies Theme;
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
--md-ref-palette-yellow80: #d1cb54;
|
||||
--md-ref-palette-yellow90: #eee76c;
|
||||
--md-ref-palette-yellow95: #fcf579;
|
||||
--md-sys-state-hover-state-layer: #00000014;
|
||||
--md-sys-state-focus-state-layer: #0000001a;
|
||||
--md-sys-state-pressed-state-layer: #0000001a;
|
||||
|
||||
// Motion(https://m3.material.io/styles/motion/overview/specs)
|
||||
--md-sys-motion-duration-short1: 50ms;
|
||||
@@ -158,7 +161,7 @@
|
||||
--md-sys-shape-corner-extra-large-top: 28px 28px 0px 0px;
|
||||
--md-sys-shape-corner-extra-large-increased: 32px;
|
||||
--md-sys-shape-corner-extra-extra-large: 40px;
|
||||
--md-sys-shape-corner-full: 100%;
|
||||
--md-sys-shape-corner-full: 100px;
|
||||
|
||||
// MD Ripple(material-web)
|
||||
--md-ripple-hover-color: var(--md-sys-color-primary);
|
||||
|
||||
Reference in New Issue
Block a user