1
0
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:
2025-11-09 15:59:55 +08:00
parent 6b2059b188
commit 3bce8e20ff
3 changed files with 271 additions and 3 deletions

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

View File

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

View File

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