初始化项目
This commit is contained in:
53
packages/materials/src/libs/page-tab/button-tab.vue
Normal file
53
packages/materials/src/libs/page-tab/button-tab.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
import type { PageTabProps } from '../../types';
|
||||
import style from './index.module.css';
|
||||
|
||||
defineOptions({
|
||||
name: 'ButtonTab'
|
||||
});
|
||||
|
||||
defineProps<PageTabProps>();
|
||||
|
||||
type SlotFn = (props?: Record<string, unknown>) => any;
|
||||
|
||||
type Slots = {
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The center content of the tab
|
||||
*/
|
||||
default?: SlotFn;
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The left content of the tab
|
||||
*/
|
||||
prefix?: SlotFn;
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The right content of the tab
|
||||
*/
|
||||
suffix?: SlotFn;
|
||||
};
|
||||
|
||||
defineSlots<Slots>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class=":soy: relative inline-flex cursor-pointer items-center justify-center gap-12px whitespace-nowrap border-1px rounded-4px border-solid px-12px py-4px"
|
||||
:class="[
|
||||
style['button-tab'],
|
||||
{ [style['button-tab_dark']]: darkMode },
|
||||
{ [style['button-tab_active']]: active },
|
||||
{ [style['button-tab_active_dark']]: active && darkMode }
|
||||
]"
|
||||
>
|
||||
<slot name="prefix"></slot>
|
||||
<slot></slot>
|
||||
<slot name="suffix"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
31
packages/materials/src/libs/page-tab/chrome-tab-bg.vue
Normal file
31
packages/materials/src/libs/page-tab/chrome-tab-bg.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'ChromeTabBg'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg class="size-full">
|
||||
<defs>
|
||||
<symbol id="geometry-left" viewBox="0 0 214 36">
|
||||
<path d="M17 0h197v36H0v-2c4.5 0 9-3.5 9-8V8c0-4.5 3.5-8 8-8z" />
|
||||
</symbol>
|
||||
<symbol id="geometry-right" viewBox="0 0 214 36">
|
||||
<use xlink:href="#geometry-left" />
|
||||
</symbol>
|
||||
<clipPath>
|
||||
<rect width="100%" height="100%" x="0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<svg width="51%" height="100%">
|
||||
<use xlink:href="#geometry-left" width="214" height="36" fill="currentColor" />
|
||||
</svg>
|
||||
<g transform="scale(-1, 1)">
|
||||
<svg width="51%" height="100%" x="-100%" y="0">
|
||||
<use xlink:href="#geometry-right" width="214" height="36" fill="currentColor" />
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
58
packages/materials/src/libs/page-tab/chrome-tab.vue
Normal file
58
packages/materials/src/libs/page-tab/chrome-tab.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import type { PageTabProps } from '../../types';
|
||||
import ChromeTabBg from './chrome-tab-bg.vue';
|
||||
import style from './index.module.css';
|
||||
|
||||
defineOptions({
|
||||
name: 'ChromeTab'
|
||||
});
|
||||
|
||||
defineProps<PageTabProps>();
|
||||
|
||||
type SlotFn = (props?: Record<string, unknown>) => any;
|
||||
|
||||
type Slots = {
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The center content of the tab
|
||||
*/
|
||||
default?: SlotFn;
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The left content of the tab
|
||||
*/
|
||||
prefix?: SlotFn;
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The right content of the tab
|
||||
*/
|
||||
suffix?: SlotFn;
|
||||
};
|
||||
|
||||
defineSlots<Slots>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class=":soy: relative inline-flex cursor-pointer items-center justify-center gap-16px whitespace-nowrap px-24px py-6px -mr-18px"
|
||||
:class="[
|
||||
style['chrome-tab'],
|
||||
{ [style['chrome-tab_dark']]: darkMode },
|
||||
{ [style['chrome-tab_active']]: active },
|
||||
{ [style['chrome-tab_active_dark']]: active && darkMode }
|
||||
]"
|
||||
>
|
||||
<div class=":soy: pointer-events-none absolute left-0 top-0 h-full w-full -z-1" :class="[style['chrome-tab__bg']]">
|
||||
<ChromeTabBg />
|
||||
</div>
|
||||
<slot name="prefix"></slot>
|
||||
<slot></slot>
|
||||
<slot name="suffix"></slot>
|
||||
<div class=":soy: absolute right-7px h-16px w-1px bg-#1f2225" :class="[style['chrome-tab-divider']]"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
31
packages/materials/src/libs/page-tab/icon-close.vue
Normal file
31
packages/materials/src/libs/page-tab/icon-close.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'IconClose'
|
||||
});
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'click'): void;
|
||||
}
|
||||
|
||||
function handleClick() {
|
||||
emit('click');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class=":soy: relative inline-flex justify-center items-center w-16px h-16px text-14px rd-50%"
|
||||
@click.stop="handleClick"
|
||||
>
|
||||
<svg width="1em" height="1em" viewBox="0 0 1024 1024">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8L295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512L196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1l216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
97
packages/materials/src/libs/page-tab/index.module.css
Normal file
97
packages/materials/src/libs/page-tab/index.module.css
Normal file
@@ -0,0 +1,97 @@
|
||||
/* @type */
|
||||
|
||||
.button-tab {
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.button-tab_dark {
|
||||
border-color: #ffffff3d;
|
||||
}
|
||||
|
||||
.button-tab:hover {
|
||||
color: var(--soy-primary-color);
|
||||
border-color: var(--soy-primary-color-opacity3);
|
||||
}
|
||||
|
||||
.button-tab_active {
|
||||
color: var(--soy-primary-color);
|
||||
border-color: var(--soy-primary-color-opacity3);
|
||||
background-color: var(--soy-primary-color-opacity1);
|
||||
}
|
||||
|
||||
.button-tab_active_dark {
|
||||
background-color: var(--soy-primary-color-opacity2);
|
||||
}
|
||||
|
||||
.button-tab .svg-close:hover {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
background-color: var(--soy-primary-color);
|
||||
}
|
||||
|
||||
.button-tab_dark .svg-close:hover {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.chrome-tab:hover {
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.chrome-tab_active {
|
||||
z-index: 10;
|
||||
color: var(--soy-primary-color);
|
||||
}
|
||||
|
||||
.chrome-tab__bg {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.chrome-tab_active .chrome-tab__bg {
|
||||
color: var(--soy-primary-color1);
|
||||
}
|
||||
|
||||
.chrome-tab_active_dark .chrome-tab__bg {
|
||||
color: var(--soy-primary-color2);
|
||||
}
|
||||
|
||||
.chrome-tab:hover .chrome-tab__bg {
|
||||
color: #dee1e6;
|
||||
}
|
||||
|
||||
.chrome-tab_active:hover .chrome-tab__bg {
|
||||
color: var(--soy-primary-color1);
|
||||
}
|
||||
|
||||
.chrome-tab_dark:hover .chrome-tab__bg {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.chrome-tab_active_dark:hover .chrome-tab__bg {
|
||||
color: var(--soy-primary-color2);
|
||||
}
|
||||
|
||||
.chrome-tab .svg-close:hover {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
background-color: #9ca3af;
|
||||
}
|
||||
|
||||
.chrome-tab_active .svg-close:hover {
|
||||
background-color: var(--soy-primary-color);
|
||||
}
|
||||
|
||||
.chrome-tab_dark .svg-close:hover {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.chrome-tab_active .chrome-tab-divider {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.chrome-tab:hover .chrome-tab-divider {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.chrome-tab_dark .chrome-tab-divider {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
14
packages/materials/src/libs/page-tab/index.module.css.d.ts
vendored
Normal file
14
packages/materials/src/libs/page-tab/index.module.css.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
declare const styles: {
|
||||
readonly 'button-tab': string;
|
||||
readonly 'button-tab_dark': string;
|
||||
readonly 'button-tab_active': string;
|
||||
readonly 'button-tab_active_dark': string;
|
||||
readonly 'chrome-tab': string;
|
||||
readonly 'chrome-tab_active': string;
|
||||
readonly 'chrome-tab__bg': string;
|
||||
readonly 'chrome-tab_active_dark': string;
|
||||
readonly 'chrome-tab_dark': string;
|
||||
readonly 'chrome-tab-divider': string;
|
||||
readonly 'svg-close': string;
|
||||
};
|
||||
export default styles;
|
||||
3
packages/materials/src/libs/page-tab/index.ts
Normal file
3
packages/materials/src/libs/page-tab/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import PageTab from './index.vue';
|
||||
|
||||
export default PageTab;
|
||||
72
packages/materials/src/libs/page-tab/index.vue
Normal file
72
packages/materials/src/libs/page-tab/index.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import type { PageTabMode, PageTabProps } from '../../types';
|
||||
import { ACTIVE_COLOR, createTabCssVars } from './shared';
|
||||
import ChromeTab from './chrome-tab.vue';
|
||||
import ButtonTab from './button-tab.vue';
|
||||
import SvgClose from './svg-close.vue';
|
||||
import style from './index.module.css';
|
||||
|
||||
defineOptions({
|
||||
name: 'PageTab'
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<PageTabProps>(), {
|
||||
mode: 'chrome',
|
||||
commonClass: 'transition-all-300',
|
||||
activeColor: ACTIVE_COLOR,
|
||||
closable: true
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'close'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const activeTabComponent = computed(() => {
|
||||
const { mode, chromeClass, buttonClass } = props;
|
||||
|
||||
const tabComponentMap = {
|
||||
chrome: {
|
||||
component: ChromeTab,
|
||||
class: chromeClass
|
||||
},
|
||||
button: {
|
||||
component: ButtonTab,
|
||||
class: buttonClass
|
||||
}
|
||||
} satisfies Record<PageTabMode, { component: Component; class?: string }>;
|
||||
|
||||
return tabComponentMap[mode];
|
||||
});
|
||||
|
||||
const cssVars = computed(() => createTabCssVars(props.activeColor));
|
||||
|
||||
const bindProps = computed(() => {
|
||||
const { chromeClass: _chromeCls, buttonClass: _btnCls, ...rest } = props;
|
||||
|
||||
return rest;
|
||||
});
|
||||
|
||||
function handleClose() {
|
||||
emit('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="activeTabComponent.component" :class="activeTabComponent.class" :style="cssVars" v-bind="bindProps">
|
||||
<template #prefix>
|
||||
<slot name="prefix"></slot>
|
||||
</template>
|
||||
<slot></slot>
|
||||
<template #suffix>
|
||||
<slot name="suffix">
|
||||
<SvgClose v-if="closable" :class="[style['svg-close']]" @click="handleClose" />
|
||||
</slot>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
31
packages/materials/src/libs/page-tab/shared.ts
Normal file
31
packages/materials/src/libs/page-tab/shared.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { addColorAlpha, transformColorWithOpacity } from '@sa/utils';
|
||||
import type { PageTabCssVars, PageTabCssVarsProps } from '../../types';
|
||||
|
||||
/** The active color of the tab */
|
||||
export const ACTIVE_COLOR = '#1890ff';
|
||||
|
||||
function createCssVars(props: PageTabCssVarsProps) {
|
||||
const cssVars: PageTabCssVars = {
|
||||
'--soy-primary-color': props.primaryColor,
|
||||
'--soy-primary-color1': props.primaryColor1,
|
||||
'--soy-primary-color2': props.primaryColor2,
|
||||
'--soy-primary-color-opacity1': props.primaryColorOpacity1,
|
||||
'--soy-primary-color-opacity2': props.primaryColorOpacity2,
|
||||
'--soy-primary-color-opacity3': props.primaryColorOpacity3
|
||||
};
|
||||
|
||||
return cssVars;
|
||||
}
|
||||
|
||||
export function createTabCssVars(primaryColor: string) {
|
||||
const cssProps: PageTabCssVarsProps = {
|
||||
primaryColor,
|
||||
primaryColor1: transformColorWithOpacity(primaryColor, 0.1, '#ffffff'),
|
||||
primaryColor2: transformColorWithOpacity(primaryColor, 0.3, '#000000'),
|
||||
primaryColorOpacity1: addColorAlpha(primaryColor, 0.1),
|
||||
primaryColorOpacity2: addColorAlpha(primaryColor, 0.15),
|
||||
primaryColorOpacity3: addColorAlpha(primaryColor, 0.3)
|
||||
};
|
||||
|
||||
return createCssVars(cssProps);
|
||||
}
|
||||
31
packages/materials/src/libs/page-tab/svg-close.vue
Normal file
31
packages/materials/src/libs/page-tab/svg-close.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'SvgClose'
|
||||
});
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'click'): void;
|
||||
}
|
||||
|
||||
function handleClick() {
|
||||
emit('click');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class=":soy: relative h-16px w-16px inline-flex items-center justify-center rd-50% text-14px"
|
||||
@click.stop="handleClick"
|
||||
>
|
||||
<svg width="1em" height="1em" viewBox="0 0 1024 1024">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8L295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512L196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1l216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
Reference in New Issue
Block a user