feat: 统一管理端弹窗与内容区交互样式
- 收敛管理端公共 Modal 链路,新增表单弹窗与普通内容弹窗包装\n- 迁移 Bot、知识库、插件、工作流、资源、MCP、数据中枢与系统管理页面级弹窗\n- 统一内容区工具栏、列表容器、导航与顶部按钮的视觉密度和交互节奏
This commit is contained in:
@@ -149,6 +149,109 @@
|
||||
.card-box {
|
||||
@apply bg-card text-card-foreground border-border rounded-xl border;
|
||||
}
|
||||
|
||||
.easyflow-modal-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.easyflow-modal-form--compact {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.easyflow-modal-section {
|
||||
background: hsl(var(--modal-content-surface-strong));
|
||||
border: 1px solid hsl(var(--modal-divider));
|
||||
border-radius: 14px;
|
||||
box-shadow: none;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.easyflow-modal-grid {
|
||||
display: grid;
|
||||
gap: 14px 16px;
|
||||
}
|
||||
|
||||
.easyflow-modal-grid--2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.easyflow-modal-field-tip {
|
||||
color: hsl(var(--text-muted));
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.easyflow-modal-form .el-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.easyflow-modal-form .el-form-item__label {
|
||||
color: hsl(var(--text-strong));
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.easyflow-modal-form .el-input__wrapper,
|
||||
.easyflow-modal-form .el-select__wrapper,
|
||||
.easyflow-modal-form .el-textarea__inner,
|
||||
.easyflow-modal-form .el-date-editor.el-input__wrapper {
|
||||
background: hsl(var(--input-background));
|
||||
border-radius: 12px;
|
||||
box-shadow: inset 0 0 0 1px hsl(var(--input) / 0.92);
|
||||
transition:
|
||||
box-shadow 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
}
|
||||
|
||||
.easyflow-modal-form .el-input__wrapper,
|
||||
.easyflow-modal-form .el-select__wrapper,
|
||||
.easyflow-modal-form .el-date-editor.el-input__wrapper {
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.easyflow-modal-form .el-textarea__inner {
|
||||
min-height: 96px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.easyflow-modal-form .el-input__wrapper:hover,
|
||||
.easyflow-modal-form .el-select__wrapper:hover,
|
||||
.easyflow-modal-form .el-textarea__inner:hover,
|
||||
.easyflow-modal-form .el-date-editor.el-input__wrapper:hover {
|
||||
box-shadow: inset 0 0 0 1px hsl(var(--primary) / 0.18);
|
||||
}
|
||||
|
||||
.easyflow-modal-form .el-input__wrapper.is-focus,
|
||||
.easyflow-modal-form .el-select__wrapper.is-focused,
|
||||
.easyflow-modal-form .el-textarea__inner:focus,
|
||||
.easyflow-modal-form .el-date-editor.el-input__wrapper.is-focus {
|
||||
box-shadow:
|
||||
inset 0 0 0 1px hsl(var(--primary) / 0.72),
|
||||
0 0 0 4px hsl(var(--primary) / 0.12);
|
||||
}
|
||||
|
||||
.easyflow-modal-form .el-form-item.is-error .el-input__wrapper,
|
||||
.easyflow-modal-form .el-form-item.is-error .el-select__wrapper,
|
||||
.easyflow-modal-form .el-form-item.is-error .el-textarea__inner,
|
||||
.easyflow-modal-form
|
||||
.el-form-item.is-error
|
||||
.el-date-editor.el-input__wrapper {
|
||||
box-shadow:
|
||||
inset 0 0 0 1px hsl(var(--destructive) / 0.8),
|
||||
0 0 0 4px hsl(var(--destructive) / 0.08);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.easyflow-modal-grid--2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.invert-mode {
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
.dark[data-theme='custom'],
|
||||
.dark[data-theme='default'] {
|
||||
/* Default background color of <body />...etc */
|
||||
--background: 222.34deg 10.43% 12.27%;
|
||||
--background: 222 16% 10.5%;
|
||||
|
||||
/* 主体区域背景色 */
|
||||
--background-deep: 220deg 13.06% 9%;
|
||||
--foreground: 0 0% 95%;
|
||||
--background-deep: 220 18% 8.6%;
|
||||
--foreground: 210 18% 95%;
|
||||
|
||||
/* Background color for <Card /> */
|
||||
--card: 222.34deg 10.43% 12.27%;
|
||||
@@ -73,12 +73,12 @@
|
||||
--heavy-foreground: var(--accent-foreground);
|
||||
|
||||
/* Default border color */
|
||||
--border: 240 3.7% 22%;
|
||||
--border: 220 10% 21%;
|
||||
|
||||
/* Border color for inputs such as <Input />, <Select />, <Textarea /> */
|
||||
--input: 0deg 0% 100% / 10%;
|
||||
--input-placeholder: 218deg 11% 65%;
|
||||
--input-background: 0deg 0% 100% / 5%;
|
||||
--input: 220 10% 22%;
|
||||
--input-placeholder: 218 10% 66%;
|
||||
--input-background: 220 10% 16%;
|
||||
|
||||
/* Used for focus ring */
|
||||
--ring: 222.2 84% 4.9%;
|
||||
@@ -97,20 +97,75 @@
|
||||
|
||||
/* =============component & UI============= */
|
||||
|
||||
--sidebar: 222.34deg 10.43% 12.27%;
|
||||
--sidebar-deep: 220deg 13.06% 9%;
|
||||
--sidebar: 220 14% 12%;
|
||||
--sidebar-deep: 220 18% 9.5%;
|
||||
--menu: var(--sidebar);
|
||||
|
||||
/* header */
|
||||
--header: 222.34deg 10.43% 12.27%;
|
||||
--header: 220 18% 12.5%;
|
||||
--nav-surface: 220 18% 12.5%;
|
||||
--nav-surface-subtle: 219 18% 15.4%;
|
||||
--nav-border: 217 18% 23%;
|
||||
--nav-item-hover: 217 28% 18.8%;
|
||||
--nav-item-active: 216 46% 24%;
|
||||
--nav-item-active-foreground: 210 96% 92%;
|
||||
--nav-item-muted-foreground: 218 15% 78%;
|
||||
--nav-indicator: var(--primary);
|
||||
--nav-tool-bg: 217 34% 17%;
|
||||
--nav-tool-hover: 216 42% 21%;
|
||||
--breadcrumb-muted: 218 12% 72%;
|
||||
--breadcrumb-current: 210 96% 92%;
|
||||
--surface-canvas: 220 18% 9%;
|
||||
--surface-panel: 220 14% 12.8%;
|
||||
--surface-subtle: 220 11% 15.8%;
|
||||
--surface-elevated: 220 14% 14.5%;
|
||||
--surface-glass: 218 24% 16.2%;
|
||||
--surface-contrast-soft: 220 11% 17.2%;
|
||||
--line-subtle: 220 9% 21%;
|
||||
--divider-faint: 220 10% 23%;
|
||||
--table-row-hover: 218 16% 18%;
|
||||
--table-row-border: 220 9% 22%;
|
||||
--table-header-bg: 220 11% 15.5%;
|
||||
--toolbar-bg: 220 16% 14%;
|
||||
--toolbar-border: 220 9% 23%;
|
||||
--text-strong: 0 0% 96%;
|
||||
--text-muted: 218 10% 70%;
|
||||
--glass-tint: 218 26% 16.2%;
|
||||
--glass-border: 210 100% 98%;
|
||||
--glass-blur: 22px;
|
||||
--radius-modal: 20px;
|
||||
--modal-surface: 220 14% 13%;
|
||||
--modal-surface-strong: 220 14% 13%;
|
||||
--modal-content-surface: 220 14% 13%;
|
||||
--modal-content-surface-strong: 220 14% 13%;
|
||||
--modal-footer-surface: 220 13% 12%;
|
||||
--modal-border: 218 16% 24%;
|
||||
--modal-divider: 218 18% 24%;
|
||||
--modal-shadow: 0 24px 60px -30px hsl(212 60% 2% / 0.58);
|
||||
--modal-shell-highlight: 0 0% 100% / 0;
|
||||
--modal-shell-border-soft: 218 16% 24% / 0.96;
|
||||
--modal-shell-shadow-near: 212 50% 2% / 0.38;
|
||||
--modal-shell-shadow-far: 212 46% 2% / 0.42;
|
||||
--modal-overlay: 220 18% 10%;
|
||||
--modal-overlay-opacity: 0.48;
|
||||
--modal-preview-surface: 217 28% 18.5%;
|
||||
--modal-preview-surface-strong: 219 23% 14.5%;
|
||||
--modal-preview-border: 214 44% 31%;
|
||||
--modal-preview-glow: 211 100% 62%;
|
||||
--nav-ambient: 214 100% 44%;
|
||||
--nav-ambient-secondary: 194 82% 40%;
|
||||
--nav-sheen: 210 100% 96%;
|
||||
--shadow-subtle: 0 18px 42px -30px hsl(212 100% 5% / 0.48);
|
||||
--shadow-toolbar: 0 28px 54px -36px hsl(212 100% 4% / 0.58);
|
||||
--shadow-float: 0 34px 70px -38px hsl(212 100% 3% / 0.68);
|
||||
--bot-back-item: hsl(0deg 0% 91.76%);
|
||||
--bot-collapse-itme-back: hsl(var(--background-deep));
|
||||
--bot-chat-message-container: hsl(var(--background-deep));
|
||||
--bot-chat-message-item-back: hsl(0deg 0% 100%);
|
||||
--common-font-placeholder-color: hsl(220deg 1.45% 59.41%);
|
||||
--table-header-text-color: hsl(0deg 0% 91.76%);
|
||||
--table-header-bg-color: hsl(231.43, 7.87%, 17.45%);
|
||||
--table-cell-shadow-color: hsl(231.43, 7.87%, 17.45%);
|
||||
--table-header-text-color: hsl(var(--text-strong));
|
||||
--table-header-bg-color: hsl(var(--table-header-bg));
|
||||
--table-cell-shadow-color: hsl(var(--table-row-border));
|
||||
--bot-select-data-item-back: hsl(0deg 0% 98.04%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
--background: 0 0% 100%;
|
||||
|
||||
/* 主体区域背景色 */
|
||||
--background-deep: 220 33.33% 98.24%;
|
||||
--foreground: 210 6% 21%;
|
||||
--background-deep: 220 32% 96.9%;
|
||||
--foreground: 215 20% 18%;
|
||||
|
||||
/* Background color for <Card /> */
|
||||
--card: 0 0% 100%;
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
/* 主题颜色 */
|
||||
|
||||
--primary: 216 100% 50%;
|
||||
--primary: 211 100% 50%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
/* Used for destructive actions such as <Button variant="destructive"> */
|
||||
@@ -72,12 +72,12 @@
|
||||
--heavy-foreground: var(--accent-foreground);
|
||||
|
||||
/* Default border color */
|
||||
--border: 0 0% 94.12%;
|
||||
--border: 216 18% 90%;
|
||||
|
||||
/* Border color for inputs such as <Input />, <Select />, <Textarea /> */
|
||||
--input: 240deg 5.88% 90%;
|
||||
--input-placeholder: 217 10.6% 65%;
|
||||
--input-background: 0 0% 100%;
|
||||
--input: 216 18% 90%;
|
||||
--input-placeholder: 215 12% 58%;
|
||||
--input-background: 210 33% 98.4%;
|
||||
|
||||
/* Used for focus ring */
|
||||
--ring: 222.2 84% 4.9%;
|
||||
@@ -97,12 +97,67 @@
|
||||
/* =============component & UI============= */
|
||||
|
||||
/* menu */
|
||||
--sidebar: 0 0% 100%;
|
||||
--sidebar-deep: 0 0% 100%;
|
||||
--sidebar: 210 33% 99.4%;
|
||||
--sidebar-deep: 210 28% 98%;
|
||||
--menu: var(--sidebar);
|
||||
|
||||
/* header */
|
||||
--header: 0 0% 100%;
|
||||
--header: 212 100% 98.7%;
|
||||
--nav-surface: 212 100% 98.7%;
|
||||
--nav-surface-subtle: 211 84% 97.3%;
|
||||
--nav-border: 214 34% 89%;
|
||||
--nav-item-hover: 211 100% 96.3%;
|
||||
--nav-item-active: 211 100% 93.8%;
|
||||
--nav-item-active-foreground: 213 78% 41%;
|
||||
--nav-item-muted-foreground: 215 18% 34%;
|
||||
--nav-indicator: var(--primary);
|
||||
--nav-tool-bg: 212 88% 96.1%;
|
||||
--nav-tool-hover: 211 100% 94%;
|
||||
--breadcrumb-muted: 215 12% 48%;
|
||||
--breadcrumb-current: 213 78% 41%;
|
||||
--surface-canvas: 216 32% 97%;
|
||||
--surface-panel: 0 0% 100%;
|
||||
--surface-subtle: 210 24% 98.4%;
|
||||
--surface-elevated: 210 33% 99.5%;
|
||||
--surface-glass: 212 100% 98.9%;
|
||||
--surface-contrast-soft: 210 26% 95.8%;
|
||||
--line-subtle: 214 18% 91%;
|
||||
--divider-faint: 215 22% 90%;
|
||||
--table-row-hover: 212 55% 97%;
|
||||
--table-row-border: 214 16% 92%;
|
||||
--table-header-bg: 210 28% 97%;
|
||||
--toolbar-bg: 210 36% 98.5%;
|
||||
--toolbar-border: 214 18% 90%;
|
||||
--text-strong: 216 22% 19%;
|
||||
--text-muted: 215 10% 49%;
|
||||
--glass-tint: 212 100% 98.9%;
|
||||
--glass-border: 0 0% 100%;
|
||||
--glass-blur: 20px;
|
||||
--radius-modal: 20px;
|
||||
--modal-surface: 0 0% 100%;
|
||||
--modal-surface-strong: 0 0% 100%;
|
||||
--modal-content-surface: 0 0% 100%;
|
||||
--modal-content-surface-strong: 0 0% 100%;
|
||||
--modal-footer-surface: 210 20% 98.4%;
|
||||
--modal-border: 214 22% 86%;
|
||||
--modal-divider: 214 22% 89%;
|
||||
--modal-shadow: 0 22px 54px -28px hsl(215 28% 18% / 0.18);
|
||||
--modal-shell-highlight: 0 0% 100% / 0;
|
||||
--modal-shell-border-soft: 214 22% 86% / 0.94;
|
||||
--modal-shell-shadow-near: 216 30% 18% / 0.08;
|
||||
--modal-shell-shadow-far: 216 34% 18% / 0.12;
|
||||
--modal-overlay: 220 18% 8%;
|
||||
--modal-overlay-opacity: 0.24;
|
||||
--modal-preview-surface: 211 100% 97.3%;
|
||||
--modal-preview-surface-strong: 0 0% 100%;
|
||||
--modal-preview-border: 212 84% 88%;
|
||||
--modal-preview-glow: 211 100% 56%;
|
||||
--nav-ambient: 212 100% 84%;
|
||||
--nav-ambient-secondary: 194 93% 82%;
|
||||
--nav-sheen: 0 0% 100%;
|
||||
--shadow-subtle: 0 18px 42px -30px hsl(211 78% 48% / 0.16);
|
||||
--shadow-toolbar: 0 28px 54px -34px hsl(211 78% 48% / 0.2);
|
||||
--shadow-float: 0 34px 68px -38px hsl(211 78% 48% / 0.24);
|
||||
|
||||
accent-color: var(--primary);
|
||||
color-scheme: light;
|
||||
@@ -112,9 +167,9 @@
|
||||
--bot-chat-message-container: hsl(228deg 33.33% 97.06%);
|
||||
--bot-chat-message-item-back: hsl(0deg 0% 100%);
|
||||
--common-font-placeholder-color: hsl(220deg 1.45% 59.41%);
|
||||
--table-header-text-color: hsl(240, 0.99%, 19.8%);
|
||||
--table-header-bg-color: hsl(220 60% 98.04%);
|
||||
--table-cell-shadow-color: hsl(225, 18.18%, 95.69%);
|
||||
--table-header-text-color: hsl(var(--text-strong));
|
||||
--table-header-bg-color: hsl(var(--table-header-bg));
|
||||
--table-cell-shadow-color: hsl(var(--table-row-border));
|
||||
--bot-select-data-item-back: hsl(0deg 0% 98.04%);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ const defaultPreferences: Preferences = {
|
||||
enable: true,
|
||||
hideOnlyOne: false,
|
||||
showHome: false,
|
||||
showIcon: true,
|
||||
showIcon: false,
|
||||
styleType: 'normal',
|
||||
},
|
||||
copyright: {
|
||||
@@ -56,7 +56,7 @@ const defaultPreferences: Preferences = {
|
||||
},
|
||||
header: {
|
||||
enable: true,
|
||||
height: 50,
|
||||
height: 56,
|
||||
hidden: false,
|
||||
menuAlign: 'start',
|
||||
mode: 'fixed',
|
||||
@@ -99,7 +99,7 @@ const defaultPreferences: Preferences = {
|
||||
tabbar: {
|
||||
draggable: true,
|
||||
enable: true,
|
||||
height: 38,
|
||||
height: 32,
|
||||
keepAlive: true,
|
||||
maxCount: 0,
|
||||
middleClickToClose: false,
|
||||
@@ -107,7 +107,7 @@ const defaultPreferences: Preferences = {
|
||||
showIcon: true,
|
||||
showMaximize: true,
|
||||
showMore: true,
|
||||
styleType: 'chrome',
|
||||
styleType: 'plain',
|
||||
wheelable: true,
|
||||
},
|
||||
theme: {
|
||||
|
||||
@@ -55,7 +55,11 @@ const style = computed((): CSSProperties => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main ref="contentElement" :style="style" class="bg-background-deep relative">
|
||||
<main
|
||||
ref="contentElement"
|
||||
:style="style"
|
||||
class="relative overflow-hidden bg-[radial-gradient(circle_at_top,hsl(var(--glass-tint))/0.34,transparent_40%),linear-gradient(180deg,hsl(var(--surface-canvas)),hsl(var(--background-deep)))]"
|
||||
>
|
||||
<Slot :style="overlayStyle">
|
||||
<slot name="overlay"></slot>
|
||||
</Slot>
|
||||
|
||||
@@ -47,6 +47,11 @@ const style = computed((): CSSProperties => {
|
||||
const right = !show || !fullWidth ? undefined : 0;
|
||||
|
||||
return {
|
||||
backgroundColor: 'hsl(var(--nav-surface) / 0.86)',
|
||||
backgroundImage:
|
||||
'linear-gradient(180deg, hsl(var(--nav-surface) / 0.96), hsl(var(--glass-tint) / 0.82))',
|
||||
backdropFilter: 'blur(var(--glass-blur)) saturate(160%)',
|
||||
boxShadow: 'var(--shadow-subtle)',
|
||||
height: `${height}px`,
|
||||
marginTop: show ? 0 : `-${height}px`,
|
||||
right,
|
||||
@@ -58,20 +63,30 @@ const logoStyle = computed((): CSSProperties => {
|
||||
minWidth: `${props.isMobile ? 40 : props.sidebarWidth}px`,
|
||||
};
|
||||
});
|
||||
const ambientStyle: CSSProperties = {
|
||||
backgroundImage:
|
||||
'radial-gradient(circle at left top, hsl(var(--nav-ambient) / 0.42) 0%, hsl(var(--nav-ambient) / 0.18) 22%, transparent 56%), radial-gradient(circle at 20% 0%, hsl(var(--nav-ambient-secondary) / 0.22) 0%, transparent 40%), linear-gradient(180deg, hsl(var(--nav-sheen) / 0.22), transparent 48%)',
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header
|
||||
:class="theme"
|
||||
:style="style"
|
||||
class="border-border bg-header top-0 flex w-full flex-[0_0_auto] items-center border-b pl-2 transition-[margin-top] duration-200"
|
||||
class="relative top-0 flex w-full flex-[0_0_auto] items-center gap-1 overflow-x-hidden overflow-y-visible px-3 transition-[margin-top,background-color,box-shadow] duration-200"
|
||||
>
|
||||
<div v-if="slots.logo" :style="logoStyle">
|
||||
<div :style="ambientStyle" class="pointer-events-none absolute inset-0"></div>
|
||||
|
||||
<div v-if="slots.logo" :style="logoStyle" class="relative z-10">
|
||||
<slot name="logo"></slot>
|
||||
</div>
|
||||
|
||||
<slot name="toggle-button"> </slot>
|
||||
<div class="relative z-10">
|
||||
<slot name="toggle-button"> </slot>
|
||||
</div>
|
||||
|
||||
<slot></slot>
|
||||
<div class="relative z-10 flex min-w-0 flex-1 items-center">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
@@ -90,7 +90,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
collapseHeight: 42,
|
||||
collapseHeight: 96,
|
||||
collapseWidth: 48,
|
||||
domVisible: true,
|
||||
fixedExtra: false,
|
||||
@@ -122,7 +122,13 @@ const style = computed((): CSSProperties => {
|
||||
const { isSidebarMixed, marginTop, paddingTop, zIndex } = props;
|
||||
|
||||
return {
|
||||
'--scroll-shadow': 'var(--sidebar)',
|
||||
'--scroll-shadow': 'var(--surface-elevated)',
|
||||
backgroundColor: 'hsl(var(--nav-surface-subtle) / 0.44)',
|
||||
backgroundImage:
|
||||
'linear-gradient(180deg, hsl(var(--nav-surface) / 0.66) 0%, hsl(var(--nav-surface-subtle) / 0.5) 28%, hsl(var(--glass-tint) / 0.36) 100%)',
|
||||
backdropFilter: 'blur(calc(var(--glass-blur) * 1.5)) saturate(188%)',
|
||||
boxShadow:
|
||||
'0 26px 64px -42px hsl(var(--primary) / 0.28), inset 0 1px 0 hsl(var(--nav-sheen) / 0.42), inset -1px 0 0 hsl(var(--nav-sheen) / 0.14)',
|
||||
...calcMenuWidthStyle(false),
|
||||
height: `calc(100% - ${marginTop}px)`,
|
||||
marginTop: `${marginTop}px`,
|
||||
@@ -136,17 +142,31 @@ const extraStyle = computed((): CSSProperties => {
|
||||
const { extraWidth, show, width, zIndex } = props;
|
||||
|
||||
return {
|
||||
backgroundColor: 'hsl(var(--nav-surface-subtle) / 0.4)',
|
||||
backgroundImage:
|
||||
'linear-gradient(180deg, hsl(var(--nav-surface-subtle) / 0.62) 0%, hsl(var(--glass-tint) / 0.34) 100%)',
|
||||
backdropFilter: 'blur(calc(var(--glass-blur) * 1.34)) saturate(182%)',
|
||||
boxShadow:
|
||||
'0 26px 64px -42px hsl(var(--primary) / 0.24), inset 0 1px 0 hsl(var(--nav-sheen) / 0.34), inset -1px 0 0 hsl(var(--nav-sheen) / 0.1)',
|
||||
left: `${width}px`,
|
||||
width: extraVisible.value && show ? `${extraWidth}px` : 0,
|
||||
zIndex,
|
||||
};
|
||||
});
|
||||
const shellAmbientStyle: CSSProperties = {
|
||||
backgroundImage:
|
||||
'radial-gradient(circle at left top, hsl(var(--nav-ambient) / 0.58) 0%, hsl(var(--nav-ambient) / 0.22) 20%, transparent 56%), radial-gradient(circle at 14% 0%, hsl(var(--nav-ambient-secondary) / 0.24) 0%, transparent 34%), linear-gradient(180deg, hsl(var(--nav-sheen) / 0.34), transparent 16%)',
|
||||
};
|
||||
const extraAmbientStyle: CSSProperties = {
|
||||
backgroundImage:
|
||||
'radial-gradient(circle at left top, hsl(var(--nav-ambient) / 0.42) 0%, hsl(var(--nav-ambient) / 0.16) 18%, transparent 44%), linear-gradient(180deg, hsl(var(--nav-sheen) / 0.28), transparent 16%)',
|
||||
};
|
||||
|
||||
const extraTitleStyle = computed((): CSSProperties => {
|
||||
const { headerHeight } = props;
|
||||
|
||||
return {
|
||||
height: `${headerHeight - 1}px`,
|
||||
height: `${Math.max(headerHeight - 1, 0)}px`,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -163,7 +183,7 @@ const contentStyle = computed((): CSSProperties => {
|
||||
|
||||
return {
|
||||
height: `calc(100% - ${headerHeight + collapseHeight}px)`,
|
||||
paddingTop: '8px',
|
||||
paddingTop: headerHeight > 0 ? '10px' : '52px',
|
||||
...contentWidthStyle.value,
|
||||
};
|
||||
});
|
||||
@@ -173,7 +193,7 @@ const headerStyle = computed((): CSSProperties => {
|
||||
|
||||
return {
|
||||
...(isSidebarMixed ? { display: 'flex', justifyContent: 'center' } : {}),
|
||||
height: `${headerHeight - 1}px`,
|
||||
height: `${Math.max(headerHeight - 1, 0)}px`,
|
||||
...contentWidthStyle.value,
|
||||
};
|
||||
});
|
||||
@@ -182,12 +202,13 @@ const extraContentStyle = computed((): CSSProperties => {
|
||||
const { collapseHeight, headerHeight } = props;
|
||||
return {
|
||||
height: `calc(100% - ${headerHeight + collapseHeight}px)`,
|
||||
paddingTop: headerHeight > 0 ? '10px' : '52px',
|
||||
};
|
||||
});
|
||||
|
||||
const collapseStyle = computed((): CSSProperties => {
|
||||
const toolStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
height: `${props.collapseHeight}px`,
|
||||
top: `${Math.max(10, (props.headerHeight - 36) / 2)}px`,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -263,60 +284,70 @@ function handleMouseleave() {
|
||||
:class="[
|
||||
theme,
|
||||
{
|
||||
'bg-sidebar-deep': isSidebarMixed,
|
||||
'bg-sidebar border-border border-r': !isSidebarMixed,
|
||||
'bg-[hsl(var(--glass-tint))/0.72]': isSidebarMixed,
|
||||
'bg-[hsl(var(--glass-tint))/0.82]': !isSidebarMixed,
|
||||
},
|
||||
]"
|
||||
:style="style"
|
||||
class="fixed left-0 top-0 h-full transition-all duration-150"
|
||||
class="fixed left-0 top-0 h-full overflow-hidden transition-all duration-150"
|
||||
@mouseenter="handleMouseenter"
|
||||
@mouseleave="handleMouseleave"
|
||||
>
|
||||
<SidebarFixedButton
|
||||
v-if="!collapse && !isSidebarMixed && showFixedButton"
|
||||
v-model:expand-on-hover="expandOnHover"
|
||||
/>
|
||||
<div v-if="slots.logo" :style="headerStyle">
|
||||
<div :style="shellAmbientStyle" class="pointer-events-none absolute inset-0"></div>
|
||||
<div
|
||||
v-if="slots.logo"
|
||||
:style="headerStyle"
|
||||
class="relative z-10 px-2 pt-2"
|
||||
>
|
||||
<slot name="logo"></slot>
|
||||
</div>
|
||||
<EasyFlowScrollbar :style="contentStyle" shadow shadow-border>
|
||||
<EasyFlowScrollbar :style="contentStyle" class="relative z-10">
|
||||
<slot></slot>
|
||||
</EasyFlowScrollbar>
|
||||
|
||||
<div :style="collapseStyle"></div>
|
||||
<SidebarCollapseButton
|
||||
v-if="showCollapseButton && !isSidebarMixed"
|
||||
v-model:collapsed="collapse"
|
||||
/>
|
||||
<div
|
||||
v-if="!isSidebarMixed && !collapse && showFixedButton"
|
||||
:style="toolStyle"
|
||||
class="absolute right-3 z-20 flex"
|
||||
>
|
||||
<SidebarFixedButton
|
||||
v-model:expand-on-hover="expandOnHover"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="isSidebarMixed"
|
||||
ref="asideRef"
|
||||
:class="{
|
||||
'border-l': extraVisible,
|
||||
}"
|
||||
:style="extraStyle"
|
||||
class="border-border bg-sidebar fixed top-0 h-full overflow-hidden border-r transition-all duration-200"
|
||||
class="fixed top-0 h-full overflow-hidden transition-all duration-200"
|
||||
>
|
||||
<SidebarCollapseButton
|
||||
v-if="isSidebarMixed && expandOnHover"
|
||||
v-model:collapsed="extraCollapse"
|
||||
/>
|
||||
|
||||
<SidebarFixedButton
|
||||
<div :style="extraAmbientStyle" class="pointer-events-none absolute inset-0"></div>
|
||||
<div
|
||||
v-if="!extraCollapse"
|
||||
v-model:expand-on-hover="expandOnHover"
|
||||
/>
|
||||
<div v-if="!extraCollapse" :style="extraTitleStyle" class="pl-2">
|
||||
:style="extraTitleStyle"
|
||||
class="relative z-10 px-3"
|
||||
>
|
||||
<slot name="extra-title"></slot>
|
||||
</div>
|
||||
<EasyFlowScrollbar
|
||||
:style="extraContentStyle"
|
||||
class="border-border py-2"
|
||||
shadow
|
||||
shadow-border
|
||||
class="relative z-10 py-2"
|
||||
>
|
||||
<slot name="extra"></slot>
|
||||
</EasyFlowScrollbar>
|
||||
<div
|
||||
v-if="expandOnHover || !extraCollapse"
|
||||
:style="toolStyle"
|
||||
class="absolute right-3 z-20 flex flex-col gap-2"
|
||||
>
|
||||
<SidebarCollapseButton
|
||||
v-if="expandOnHover"
|
||||
v-model:collapsed="extraCollapse"
|
||||
/>
|
||||
<SidebarFixedButton
|
||||
v-if="!extraCollapse"
|
||||
v-model:expand-on-hover="expandOnHover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
@@ -15,16 +15,28 @@ const props = withDefaults(defineProps<Props>(), {});
|
||||
const style = computed((): CSSProperties => {
|
||||
const { height } = props;
|
||||
return {
|
||||
backgroundColor: 'hsl(var(--glass-tint) / 0.68)',
|
||||
backgroundImage:
|
||||
'linear-gradient(180deg, hsl(var(--nav-surface-subtle) / 0.92), hsl(var(--glass-tint) / 0.74))',
|
||||
backdropFilter: 'blur(calc(var(--glass-blur) * 0.9)) saturate(155%)',
|
||||
boxShadow: '0 18px 36px -30px hsl(var(--primary) / 0.12)',
|
||||
height: `${height}px`,
|
||||
};
|
||||
});
|
||||
const ambientStyle: CSSProperties = {
|
||||
backgroundImage:
|
||||
'radial-gradient(circle at left top, hsl(var(--nav-ambient) / 0.24) 0%, transparent 38%), linear-gradient(180deg, hsl(var(--nav-sheen) / 0.14), transparent 62%)',
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
:style="style"
|
||||
class="border-border bg-background flex w-full border-b transition-all"
|
||||
class="relative flex w-full overflow-hidden transition-all"
|
||||
>
|
||||
<slot></slot>
|
||||
<div :style="ambientStyle" class="pointer-events-none absolute inset-0"></div>
|
||||
<div class="relative z-10 flex w-full">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import { ChevronsLeft, ChevronsRight } from '@easyflow-core/icons';
|
||||
|
||||
const collapsed = defineModel<boolean>('collapsed');
|
||||
const buttonStyle: CSSProperties = {
|
||||
backgroundColor: 'hsl(var(--nav-tool-bg) / 0.92)',
|
||||
backgroundImage:
|
||||
'linear-gradient(180deg, hsl(var(--nav-tool-bg) / 0.96), hsl(var(--glass-tint) / 0.82))',
|
||||
border: '1px solid transparent',
|
||||
boxShadow: '0 18px 36px -28px hsl(var(--primary) / 0.24)',
|
||||
};
|
||||
|
||||
function handleCollapsed() {
|
||||
collapsed.value = !collapsed.value;
|
||||
@@ -9,11 +18,13 @@ function handleCollapsed() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex-center hover:text-foreground text-foreground/60 hover:bg-accent-hover bg-accent absolute bottom-2 left-3 z-10 cursor-pointer rounded-sm p-1"
|
||||
<button
|
||||
aria-label="切换侧边栏折叠状态"
|
||||
:style="buttonStyle"
|
||||
class="flex-center h-9 w-9 rounded-2xl text-[hsl(var(--nav-item-muted-foreground))] backdrop-blur-xl transition-[background-color,color,transform,box-shadow,border-color] duration-200 hover:-translate-y-0.5 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--primary))/0.2]"
|
||||
@click.stop="handleCollapsed"
|
||||
>
|
||||
<ChevronsRight v-if="collapsed" class="size-4" />
|
||||
<ChevronsLeft v-else class="size-4" />
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import { Pin, PinOff } from '@easyflow-core/icons';
|
||||
|
||||
const expandOnHover = defineModel<boolean>('expandOnHover');
|
||||
const buttonStyle: CSSProperties = {
|
||||
backgroundColor: 'hsl(var(--nav-tool-bg) / 0.92)',
|
||||
backgroundImage:
|
||||
'linear-gradient(180deg, hsl(var(--nav-tool-bg) / 0.96), hsl(var(--glass-tint) / 0.82))',
|
||||
border: '1px solid transparent',
|
||||
boxShadow: '0 18px 36px -28px hsl(var(--primary) / 0.24)',
|
||||
};
|
||||
|
||||
function toggleFixed() {
|
||||
expandOnHover.value = !expandOnHover.value;
|
||||
@@ -9,11 +18,13 @@ function toggleFixed() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex-center hover:text-foreground text-foreground/60 hover:bg-accent-hover bg-accent absolute bottom-2 right-3 z-10 cursor-pointer rounded-sm p-[5px] transition-all duration-300"
|
||||
<button
|
||||
aria-label="切换侧边栏固定状态"
|
||||
:style="buttonStyle"
|
||||
class="flex-center h-9 w-9 rounded-2xl text-[hsl(var(--nav-item-muted-foreground))] backdrop-blur-xl transition-[background-color,color,transform,box-shadow,border-color] duration-200 hover:-translate-y-0.5 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--primary))/0.2]"
|
||||
@click="toggleFixed"
|
||||
>
|
||||
<PinOff v-if="!expandOnHover" class="size-3.5" />
|
||||
<Pin v-else class="size-3.5" />
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -31,6 +31,24 @@ defineOptions({
|
||||
name: 'EasyFlowLayout',
|
||||
});
|
||||
|
||||
const headerToggleButtonClass =
|
||||
'my-0 mr-1 flex h-9 w-9 items-center justify-center rounded-2xl text-[hsl(var(--nav-item-muted-foreground))] backdrop-blur-xl transition-[background-color,color,transform,box-shadow,border-color] hover:-translate-y-0.5 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--primary))/0.2]';
|
||||
const headerToggleButtonStyle: CSSProperties = {
|
||||
backgroundColor: 'hsl(var(--nav-tool-bg) / 0.92)',
|
||||
backgroundImage:
|
||||
'linear-gradient(180deg, hsl(var(--nav-tool-bg) / 0.96), hsl(var(--glass-tint) / 0.8))',
|
||||
border: '1px solid hsl(var(--glass-border) / 0.2)',
|
||||
boxShadow: '0 18px 36px -28px hsl(var(--primary) / 0.24)',
|
||||
};
|
||||
const layoutAmbientLeftStyle: CSSProperties = {
|
||||
backgroundImage:
|
||||
'radial-gradient(circle at left top, hsl(var(--nav-ambient) / 0.34) 0%, hsl(var(--nav-ambient) / 0.14) 28%, transparent 72%)',
|
||||
};
|
||||
const layoutAmbientRightStyle: CSSProperties = {
|
||||
backgroundImage:
|
||||
'radial-gradient(circle at left top, hsl(var(--nav-ambient-secondary) / 0.14) 0%, transparent 72%)',
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
contentCompact: 'wide',
|
||||
contentCompactWidth: 1200,
|
||||
@@ -482,6 +500,10 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
|
||||
|
||||
<template>
|
||||
<div class="relative flex min-h-full w-full">
|
||||
<div class="pointer-events-none absolute inset-0 overflow-hidden">
|
||||
<div :style="layoutAmbientLeftStyle" class="absolute -left-16 top-0 h-[320px] w-[320px] rounded-full blur-3xl"></div>
|
||||
<div :style="layoutAmbientRightStyle" class="absolute right-[-120px] top-[-40px] h-[260px] w-[320px] rounded-full blur-3xl"></div>
|
||||
</div>
|
||||
<LayoutSidebar
|
||||
v-if="sidebarEnableState"
|
||||
v-model:collapse="sidebarCollapse"
|
||||
@@ -526,12 +548,12 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
|
||||
|
||||
<div
|
||||
ref="contentRef"
|
||||
class="flex flex-1 flex-col overflow-hidden transition-all duration-300 ease-in"
|
||||
class="relative z-[1] flex flex-1 flex-col overflow-hidden transition-all duration-300 ease-in"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
{
|
||||
'shadow-[0_16px_24px_hsl(var(--background))]': scrollY > 20,
|
||||
'shadow-[var(--shadow-toolbar)]': scrollY > 20,
|
||||
},
|
||||
SCROLL_FIXED_CLASS,
|
||||
]"
|
||||
@@ -556,7 +578,8 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
|
||||
<template #toggle-button>
|
||||
<EasyFlowIconButton
|
||||
v-if="showHeaderToggleButton"
|
||||
class="my-0 mr-1 rounded-md"
|
||||
:class="headerToggleButtonClass"
|
||||
:style="headerToggleButtonStyle"
|
||||
@click="handleHeaderToggle"
|
||||
>
|
||||
<IconifyIcon v-if="showSidebar" icon="ep:fold" />
|
||||
|
||||
@@ -70,6 +70,15 @@ function handleClick() {
|
||||
emit('click', item);
|
||||
}
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key !== 'Enter' && event.key !== ' ') {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
handleClick();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
subMenu?.addSubMenu?.(item);
|
||||
rootMenu?.addMenuItem?.(item);
|
||||
@@ -90,7 +99,9 @@ onBeforeUnmount(() => {
|
||||
is('collapse-show-title', collapseShowTitle),
|
||||
]"
|
||||
role="menuitem"
|
||||
:tabindex="disabled ? -1 : 0"
|
||||
@click.stop="handleClick"
|
||||
@keydown="handleKeydown"
|
||||
>
|
||||
<EasyFlowTooltip
|
||||
v-if="showTooltip"
|
||||
|
||||
@@ -377,12 +377,13 @@ $namespace: easyflow;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
background: var(--menu-item-active-background-color);
|
||||
box-shadow: var(--menu-item-shadow);
|
||||
}
|
||||
|
||||
@mixin menu-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
// gap: 12px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
height: var(--menu-item-height);
|
||||
padding: var(--menu-item-padding-y) var(--menu-item-padding-x);
|
||||
@@ -401,7 +402,13 @@ $namespace: easyflow;
|
||||
background 0.15s ease,
|
||||
color 0.15s ease,
|
||||
padding 0.15s ease,
|
||||
border-color 0.15s ease;
|
||||
border-color 0.15s ease,
|
||||
box-shadow 0.15s ease,
|
||||
transform 0.15s ease;
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
cursor: not-allowed;
|
||||
@@ -425,6 +432,12 @@ $namespace: easyflow;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
box-shadow:
|
||||
0 0 0 1px hsl(var(--glass-border) / 0.72),
|
||||
0 0 0 4px hsl(var(--ring) / 0.16);
|
||||
}
|
||||
|
||||
* {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
@@ -445,68 +458,106 @@ $namespace: easyflow;
|
||||
.#{$namespace}-menu__popup-container,
|
||||
.#{$namespace}-menu {
|
||||
--menu-title-width: 140px;
|
||||
--menu-item-icon-size: 16px;
|
||||
--menu-item-height: 38px;
|
||||
--menu-item-padding-y: 21px;
|
||||
--menu-item-padding-x: 12px;
|
||||
--menu-item-popup-padding-y: 20px;
|
||||
--menu-item-popup-padding-x: 12px;
|
||||
--menu-item-margin-y: 2px;
|
||||
--menu-item-margin-x: 0px;
|
||||
--menu-item-collapse-padding-y: 23.5px;
|
||||
--menu-item-icon-size: 18px;
|
||||
--menu-item-height: 40px;
|
||||
--menu-item-padding-y: 0px;
|
||||
--menu-item-padding-x: 14px;
|
||||
--menu-item-popup-padding-y: 0px;
|
||||
--menu-item-popup-padding-x: 14px;
|
||||
--menu-item-margin-y: 4px;
|
||||
--menu-item-margin-x: 8px;
|
||||
--menu-item-collapse-padding-y: 0px;
|
||||
--menu-item-collapse-padding-x: 0px;
|
||||
--menu-item-collapse-margin-y: 4px;
|
||||
--menu-item-collapse-margin-x: 0px;
|
||||
--menu-item-radius: 0px;
|
||||
--menu-item-indent: 16px;
|
||||
--menu-item-collapse-margin-x: 8px;
|
||||
--menu-item-radius: 14px;
|
||||
--menu-item-shadow:
|
||||
inset 0 1px 0 hsl(var(--nav-sheen) / 0.54),
|
||||
0 22px 40px -30px hsl(var(--primary) / 0.24);
|
||||
--menu-item-indent: 18px;
|
||||
--menu-font-size: 14px;
|
||||
--menu-item-indicator-width: 0px;
|
||||
|
||||
&.is-dark {
|
||||
--menu-background-color: hsl(var(--menu));
|
||||
// --menu-submenu-opened-background-color: hsl(var(--menu-opened-dark));
|
||||
--menu-item-background-color: var(--menu-background-color);
|
||||
--menu-item-color: hsl(216.92, 15.12%, 33.73% / 80%);
|
||||
--menu-item-hover-color: hsl(var(--accent-foreground));
|
||||
--menu-item-hover-background-color: hsl(var(--accent));
|
||||
--menu-item-active-color: hsl(var(--accent-foreground));
|
||||
--menu-item-active-background-color: hsl(var(--accent));
|
||||
--menu-background-color: hsl(var(--nav-surface));
|
||||
--menu-item-background-color: transparent;
|
||||
--menu-item-color: hsl(var(--nav-item-muted-foreground));
|
||||
--menu-item-hover-color: hsl(var(--foreground));
|
||||
--menu-item-hover-background-color: linear-gradient(
|
||||
180deg,
|
||||
hsl(var(--nav-item-hover) / 0.78),
|
||||
hsl(var(--glass-tint) / 0.46)
|
||||
);
|
||||
--menu-item-active-color: hsl(var(--nav-item-active-foreground));
|
||||
--menu-item-active-background-color: linear-gradient(
|
||||
135deg,
|
||||
hsl(var(--nav-item-active) / 0.92) 0%,
|
||||
hsl(var(--nav-item-hover) / 0.72) 54%,
|
||||
hsl(var(--glass-tint) / 0.52) 100%
|
||||
);
|
||||
--menu-submenu-hover-color: hsl(var(--foreground));
|
||||
--menu-submenu-hover-background-color: hsl(var(--accent));
|
||||
--menu-submenu-active-color: hsl(var(--foreground));
|
||||
--menu-submenu-active-background-color: transparent;
|
||||
--menu-submenu-hover-background-color: linear-gradient(
|
||||
180deg,
|
||||
hsl(var(--nav-item-hover) / 0.78),
|
||||
hsl(var(--glass-tint) / 0.46)
|
||||
);
|
||||
--menu-submenu-active-color: hsl(var(--nav-item-active-foreground));
|
||||
--menu-submenu-active-background-color: linear-gradient(
|
||||
135deg,
|
||||
hsl(var(--nav-item-active) / 0.92) 0%,
|
||||
hsl(var(--nav-item-hover) / 0.72) 54%,
|
||||
hsl(var(--glass-tint) / 0.52) 100%
|
||||
);
|
||||
--menu-submenu-background-color: var(--menu-background-color);
|
||||
}
|
||||
|
||||
&.is-light {
|
||||
--menu-background-color: hsl(var(--menu));
|
||||
// --menu-submenu-opened-background-color: hsl(var(--menu-opened));
|
||||
--menu-item-background-color: var(--menu-background-color);
|
||||
--menu-item-color: hsl(216.92, 15.12%, 33.73%);
|
||||
--menu-item-hover-color: var(--menu-item-color);
|
||||
--menu-item-hover-background-color: hsl(var(--accent));
|
||||
--menu-item-active-color: hsl(var(--primary));
|
||||
--menu-item-active-background-color: hsl(var(--primary) / 15%);
|
||||
--menu-submenu-hover-color: hsl(var(--primary));
|
||||
--menu-submenu-hover-background-color: hsl(var(--accent));
|
||||
--menu-submenu-active-color: hsl(var(--primary));
|
||||
--menu-submenu-active-background-color: transparent;
|
||||
--menu-background-color: hsl(var(--nav-surface));
|
||||
--menu-item-background-color: transparent;
|
||||
--menu-item-color: hsl(var(--nav-item-muted-foreground));
|
||||
--menu-item-hover-color: hsl(var(--foreground));
|
||||
--menu-item-hover-background-color: linear-gradient(
|
||||
180deg,
|
||||
hsl(var(--nav-item-hover) / 0.78),
|
||||
hsl(var(--glass-tint) / 0.46)
|
||||
);
|
||||
--menu-item-active-color: hsl(var(--nav-item-active-foreground));
|
||||
--menu-item-active-background-color: linear-gradient(
|
||||
135deg,
|
||||
hsl(var(--nav-item-active) / 0.92) 0%,
|
||||
hsl(var(--nav-item-hover) / 0.72) 54%,
|
||||
hsl(var(--glass-tint) / 0.52) 100%
|
||||
);
|
||||
--menu-submenu-hover-color: hsl(var(--foreground));
|
||||
--menu-submenu-hover-background-color: linear-gradient(
|
||||
180deg,
|
||||
hsl(var(--nav-item-hover) / 0.78),
|
||||
hsl(var(--glass-tint) / 0.46)
|
||||
);
|
||||
--menu-submenu-active-color: hsl(var(--nav-item-active-foreground));
|
||||
--menu-submenu-active-background-color: linear-gradient(
|
||||
135deg,
|
||||
hsl(var(--nav-item-active) / 0.92) 0%,
|
||||
hsl(var(--nav-item-hover) / 0.72) 54%,
|
||||
hsl(var(--glass-tint) / 0.52) 100%
|
||||
);
|
||||
--menu-submenu-background-color: var(--menu-background-color);
|
||||
}
|
||||
|
||||
&.is-rounded {
|
||||
--menu-item-margin-x: 8px;
|
||||
--menu-item-collapse-margin-x: 6px;
|
||||
--menu-item-radius: 8px;
|
||||
--menu-item-collapse-margin-x: 8px;
|
||||
--menu-item-radius: 10px;
|
||||
}
|
||||
|
||||
&.is-horizontal:not(.is-rounded) {
|
||||
--menu-item-height: 40px;
|
||||
--menu-item-radius: 6px;
|
||||
--menu-item-radius: 14px;
|
||||
}
|
||||
|
||||
&.is-horizontal.is-rounded {
|
||||
--menu-item-height: 40px;
|
||||
--menu-item-radius: 6px;
|
||||
--menu-item-radius: 14px;
|
||||
--menu-item-padding-x: 12px;
|
||||
}
|
||||
|
||||
@@ -519,25 +570,61 @@ $namespace: easyflow;
|
||||
--menu-background-color: transparent;
|
||||
|
||||
&.is-dark {
|
||||
--menu-item-hover-color: hsl(var(--accent-foreground));
|
||||
--menu-item-hover-background-color: hsl(var(--accent));
|
||||
--menu-item-active-color: hsl(var(--accent-foreground));
|
||||
--menu-item-active-background-color: hsl(var(--accent));
|
||||
--menu-submenu-active-color: hsl(var(--foreground));
|
||||
--menu-submenu-active-background-color: hsl(var(--accent));
|
||||
--menu-submenu-hover-color: hsl(var(--accent-foreground));
|
||||
--menu-submenu-hover-background-color: hsl(var(--accent));
|
||||
--menu-item-hover-color: hsl(var(--foreground));
|
||||
--menu-item-hover-background-color: linear-gradient(
|
||||
180deg,
|
||||
hsl(var(--nav-item-hover) / 0.98),
|
||||
hsl(var(--nav-surface-subtle) / 0.84)
|
||||
);
|
||||
--menu-item-active-color: hsl(var(--nav-item-active-foreground));
|
||||
--menu-item-active-background-color: linear-gradient(
|
||||
135deg,
|
||||
hsl(var(--nav-item-active)) 0%,
|
||||
hsl(var(--nav-item-hover) / 0.98) 55%,
|
||||
hsl(var(--glass-tint) / 0.98) 100%
|
||||
);
|
||||
--menu-submenu-active-color: hsl(var(--nav-item-active-foreground));
|
||||
--menu-submenu-active-background-color: linear-gradient(
|
||||
135deg,
|
||||
hsl(var(--nav-item-active)) 0%,
|
||||
hsl(var(--nav-item-hover) / 0.98) 55%,
|
||||
hsl(var(--glass-tint) / 0.98) 100%
|
||||
);
|
||||
--menu-submenu-hover-color: hsl(var(--foreground));
|
||||
--menu-submenu-hover-background-color: linear-gradient(
|
||||
180deg,
|
||||
hsl(var(--nav-item-hover) / 0.98),
|
||||
hsl(var(--nav-surface-subtle) / 0.84)
|
||||
);
|
||||
}
|
||||
|
||||
&.is-light {
|
||||
--menu-item-active-color: hsl(var(--primary));
|
||||
--menu-item-active-background-color: hsl(var(--primary) / 15%);
|
||||
--menu-item-hover-background-color: hsl(var(--accent));
|
||||
--menu-item-hover-color: hsl(var(--primary));
|
||||
--menu-submenu-active-color: hsl(var(--primary));
|
||||
--menu-submenu-active-background-color: hsl(var(--primary) / 15%);
|
||||
--menu-submenu-hover-color: hsl(var(--primary));
|
||||
--menu-submenu-hover-background-color: hsl(var(--accent));
|
||||
--menu-item-active-color: hsl(var(--nav-item-active-foreground));
|
||||
--menu-item-active-background-color: linear-gradient(
|
||||
135deg,
|
||||
hsl(var(--nav-item-active)) 0%,
|
||||
hsl(var(--nav-item-hover) / 0.98) 55%,
|
||||
hsl(var(--glass-tint) / 0.98) 100%
|
||||
);
|
||||
--menu-item-hover-background-color: linear-gradient(
|
||||
180deg,
|
||||
hsl(var(--nav-item-hover) / 0.98),
|
||||
hsl(var(--nav-surface-subtle) / 0.84)
|
||||
);
|
||||
--menu-item-hover-color: hsl(var(--foreground));
|
||||
--menu-submenu-active-color: hsl(var(--nav-item-active-foreground));
|
||||
--menu-submenu-active-background-color: linear-gradient(
|
||||
135deg,
|
||||
hsl(var(--nav-item-active)) 0%,
|
||||
hsl(var(--nav-item-hover) / 0.98) 55%,
|
||||
hsl(var(--glass-tint) / 0.98) 100%
|
||||
);
|
||||
--menu-submenu-hover-color: hsl(var(--foreground));
|
||||
--menu-submenu-hover-background-color: linear-gradient(
|
||||
180deg,
|
||||
hsl(var(--nav-item-hover) / 0.98),
|
||||
hsl(var(--nav-surface-subtle) / 0.84)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -674,6 +761,10 @@ $namespace: easyflow;
|
||||
&.is-active {
|
||||
background: var(--menu-item-active-background-color) !important;
|
||||
border-radius: var(--menu-item-radius);
|
||||
|
||||
&::before {
|
||||
left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -719,7 +810,7 @@ $namespace: easyflow;
|
||||
flex-shrink: 0;
|
||||
width: var(--menu-item-icon-size);
|
||||
height: var(--menu-item-icon-size);
|
||||
margin-right: 8px;
|
||||
margin-right: 0;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -741,6 +832,7 @@ $namespace: easyflow;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: var(--menu-item-height);
|
||||
gap: 10px;
|
||||
|
||||
span {
|
||||
@include menu-title;
|
||||
@@ -775,6 +867,8 @@ $namespace: easyflow;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
background: var(--menu-item-hover-background-color) !important;
|
||||
box-shadow: 0 18px 34px -30px hsl(var(--primary) / 0.18);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.#{$namespace}-menu-tooltip__trigger {
|
||||
@@ -809,8 +903,20 @@ $namespace: easyflow;
|
||||
cursor: pointer;
|
||||
background: var(--menu-submenu-active-background-color);
|
||||
fill: var(--menu-submenu-active-color);
|
||||
box-shadow: var(--menu-item-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-visible > .#{$namespace}-sub-menu-content,
|
||||
&:focus-within > .#{$namespace}-sub-menu-content {
|
||||
box-shadow:
|
||||
0 0 0 1px hsl(var(--glass-border) / 0.72),
|
||||
0 0 0 4px hsl(var(--ring) / 0.16);
|
||||
}
|
||||
}
|
||||
|
||||
.#{$namespace}-sub-menu-content {
|
||||
@@ -833,6 +939,7 @@ $namespace: easyflow;
|
||||
|
||||
&__title {
|
||||
@include menu-title;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.is-collapse-show-title {
|
||||
@@ -866,10 +973,13 @@ $namespace: easyflow;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
background: var(--menu-submenu-hover-background-color) !important;
|
||||
|
||||
// svg {
|
||||
// fill: var(--menu-submenu-hover-color);
|
||||
// }
|
||||
box-shadow: 0 18px 34px -30px hsl(var(--primary) / 0.18);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
}
|
||||
|
||||
.#{$namespace}-sub-menu .#{$namespace}-menu-item,
|
||||
.#{$namespace}-sub-menu .#{$namespace}-sub-menu-content {
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -120,6 +120,15 @@ function handleClick() {
|
||||
});
|
||||
}
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key !== 'Enter' && event.key !== ' ') {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
handleClick();
|
||||
}
|
||||
|
||||
function handleMouseenter(event: FocusEvent | MouseEvent, showTimeout = 300) {
|
||||
if (event.type === 'focus') {
|
||||
return;
|
||||
@@ -197,9 +206,11 @@ onBeforeUnmount(() => {
|
||||
is('active', active),
|
||||
is('disabled', disabled),
|
||||
]"
|
||||
:tabindex="disabled ? -1 : 0"
|
||||
@focus="handleMouseenter"
|
||||
@mouseenter="handleMouseenter"
|
||||
@mouseleave="() => handleMouseleave()"
|
||||
@keydown="handleKeydown"
|
||||
>
|
||||
<template v-if="rootMenu.isMenuPopup">
|
||||
<EasyFlowHoverCard
|
||||
|
||||
@@ -184,7 +184,7 @@ const getForceMount = computed(() => {
|
||||
<SheetContent
|
||||
:append-to="getAppendTo"
|
||||
:class="
|
||||
cn('flex w-[520px] flex-col', drawerClass, {
|
||||
cn('flex w-[520px] flex-col overflow-hidden border border-[hsl(var(--glass-border))/0.18] bg-[hsl(var(--glass-tint))/0.84] shadow-[var(--shadow-float)] supports-[backdrop-filter]:bg-[hsl(var(--glass-tint))/0.62]', drawerClass, {
|
||||
'!w-full': isMobile || placement === 'bottom' || placement === 'top',
|
||||
'max-h-[100vh]': placement === 'bottom' || placement === 'top',
|
||||
hidden: isClosed,
|
||||
@@ -209,7 +209,7 @@ const getForceMount = computed(() => {
|
||||
v-if="showHeader"
|
||||
:class="
|
||||
cn(
|
||||
'!flex flex-row items-center justify-between border-b px-6 py-5',
|
||||
'!flex flex-row items-center justify-between border-b border-[hsl(var(--divider-faint))/0.32] px-6 py-5',
|
||||
headerClass,
|
||||
{
|
||||
'px-4 py-3': closable,
|
||||
@@ -223,7 +223,7 @@ const getForceMount = computed(() => {
|
||||
v-if="closable && closeIconPlacement === 'left'"
|
||||
as-child
|
||||
:disabled="submitting"
|
||||
class="data-[state=open]:bg-secondary ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
|
||||
class="data-[state=open]:bg-[hsl(var(--surface-contrast-soft))/0.92] ml-[2px] cursor-pointer rounded-full opacity-84 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
|
||||
>
|
||||
<slot name="close-icon">
|
||||
<EasyFlowIconButton>
|
||||
@@ -264,7 +264,7 @@ const getForceMount = computed(() => {
|
||||
v-if="closable && closeIconPlacement === 'right'"
|
||||
as-child
|
||||
:disabled="submitting"
|
||||
class="data-[state=open]:bg-secondary ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
|
||||
class="data-[state=open]:bg-[hsl(var(--surface-contrast-soft))/0.92] ml-[2px] cursor-pointer rounded-full opacity-84 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
|
||||
>
|
||||
<slot name="close-icon">
|
||||
<EasyFlowIconButton>
|
||||
@@ -295,7 +295,7 @@ const getForceMount = computed(() => {
|
||||
v-if="showFooter"
|
||||
:class="
|
||||
cn(
|
||||
'w-full flex-row items-center justify-end border-t p-2 px-3',
|
||||
'w-full flex-row items-center justify-end border-t border-[hsl(var(--divider-faint))/0.32] p-2 px-3',
|
||||
footerClass,
|
||||
)
|
||||
"
|
||||
|
||||
@@ -59,7 +59,7 @@ export class ModalApi {
|
||||
showCancelButton: true,
|
||||
showConfirmButton: true,
|
||||
title: '',
|
||||
animationType: 'slide',
|
||||
animationType: 'scale',
|
||||
};
|
||||
|
||||
this.store = new Store<ModalState>(
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { ModalApi } from './modal-api';
|
||||
export interface ModalProps {
|
||||
/**
|
||||
* 动画类型
|
||||
* @default 'slide'
|
||||
* @default 'scale'
|
||||
*/
|
||||
animationType?: 'scale' | 'slide';
|
||||
/**
|
||||
|
||||
@@ -231,12 +231,13 @@ function handleClosed() {
|
||||
:append-to="getAppendTo"
|
||||
:class="
|
||||
cn(
|
||||
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] w-[520px] flex-col p-0',
|
||||
shouldFullscreen ? 'sm:rounded-none' : 'sm:rounded-[var(--radius)]',
|
||||
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] w-[520px] flex-col overflow-hidden border border-[hsl(var(--modal-shell-border-soft))] bg-[hsl(var(--modal-surface))] p-0',
|
||||
shouldFullscreen
|
||||
? 'sm:rounded-none'
|
||||
: 'sm:rounded-[var(--radius-modal)]',
|
||||
modalClass,
|
||||
{
|
||||
'border-border border': bordered,
|
||||
'shadow-3xl': !bordered,
|
||||
'border-[hsl(var(--modal-shell-border-soft))]': bordered,
|
||||
'left-0 top-0 size-full max-h-full !translate-x-0 !translate-y-0':
|
||||
shouldFullscreen,
|
||||
'top-1/2 !-translate-y-1/2': centered && !shouldFullscreen,
|
||||
@@ -248,6 +249,16 @@ function handleClosed() {
|
||||
:force-mount="getForceMount"
|
||||
:modal="modal"
|
||||
:open="state?.isOpen"
|
||||
:overlay-style="{
|
||||
backgroundColor:
|
||||
'hsl(var(--modal-overlay) / var(--modal-overlay-opacity))',
|
||||
}"
|
||||
:content-style="{
|
||||
backdropFilter: 'none',
|
||||
WebkitBackdropFilter: 'none',
|
||||
backgroundColor: 'hsl(var(--modal-surface))',
|
||||
boxShadow: 'var(--modal-shadow)',
|
||||
}"
|
||||
:show-close="closable"
|
||||
:animation-type="animationType"
|
||||
:z-index="zIndex"
|
||||
@@ -267,9 +278,8 @@ function handleClosed() {
|
||||
ref="headerRef"
|
||||
:class="
|
||||
cn(
|
||||
'px-5 py-4',
|
||||
'px-5 py-4 sm:px-6 sm:py-5',
|
||||
{
|
||||
'border-b': bordered,
|
||||
hidden: !header,
|
||||
'cursor-move select-none': shouldDraggable,
|
||||
},
|
||||
@@ -301,9 +311,13 @@ function handleClosed() {
|
||||
<div
|
||||
ref="wrapperRef"
|
||||
:class="
|
||||
cn('relative min-h-40 flex-1 overflow-y-auto p-3', contentClass, {
|
||||
'pointer-events-none': showLoading || submitting,
|
||||
})
|
||||
cn(
|
||||
'relative min-h-0 flex-1 overflow-y-auto bg-[hsl(var(--modal-content-surface))] px-5 pb-5 pt-4 sm:px-6 sm:pb-6',
|
||||
contentClass,
|
||||
{
|
||||
'pointer-events-none': showLoading || submitting,
|
||||
},
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
@@ -311,7 +325,7 @@ function handleClosed() {
|
||||
<EasyFlowLoading v-if="showLoading || submitting" spinning />
|
||||
<EasyFlowIconButton
|
||||
v-if="fullscreenButton"
|
||||
class="hover:bg-accent hover:text-accent-foreground text-foreground/80 flex-center absolute right-10 top-3 hidden size-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none sm:block"
|
||||
class="text-foreground/80 flex-center opacity-78 absolute right-11 top-4 hidden size-8 rounded-full bg-[hsl(var(--modal-preview-surface-strong))/0.76] px-1 text-lg shadow-[0_16px_28px_-24px_hsl(var(--foreground)/0.4)] transition-all hover:-translate-y-0.5 hover:opacity-100 focus:outline-none disabled:pointer-events-none sm:block"
|
||||
@click="handleFullscreen"
|
||||
>
|
||||
<Shrink v-if="fullscreen" class="size-3.5" />
|
||||
@@ -323,9 +337,9 @@ function handleClosed() {
|
||||
ref="footerRef"
|
||||
:class="
|
||||
cn(
|
||||
'flex-row items-center justify-end p-2',
|
||||
'flex-row items-center justify-end gap-3 bg-[hsl(var(--modal-footer-surface))] px-5 py-4 sm:px-6',
|
||||
{
|
||||
'border-t': bordered,
|
||||
'border-t border-[hsl(var(--modal-divider))]': bordered,
|
||||
},
|
||||
footerClass,
|
||||
)
|
||||
|
||||
@@ -35,7 +35,7 @@ function handleClick(path?: string) {
|
||||
</script>
|
||||
<template>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbList class="easyflow-breadcrumb flex-nowrap">
|
||||
<TransitionGroup name="breadcrumb-transition">
|
||||
<template
|
||||
v-for="(item, index) in breadcrumbs"
|
||||
@@ -44,10 +44,10 @@ function handleClick(path?: string) {
|
||||
<BreadcrumbItem>
|
||||
<div v-if="item.items?.length ?? 0 > 0">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger class="flex items-center gap-1">
|
||||
<EasyFlowIcon v-if="showIcon" :icon="item.icon" class="size-5" />
|
||||
{{ item.title }}
|
||||
<ChevronDown class="size-4" />
|
||||
<DropdownMenuTrigger class="easyflow-breadcrumb__link">
|
||||
<EasyFlowIcon v-if="showIcon" :icon="item.icon" class="size-4" />
|
||||
<span class="max-w-[180px] truncate">{{ item.title }}</span>
|
||||
<ChevronDown class="size-3.5" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<template
|
||||
@@ -63,32 +63,37 @@ function handleClick(path?: string) {
|
||||
</div>
|
||||
<BreadcrumbLink
|
||||
v-else-if="index !== breadcrumbs.length - 1"
|
||||
class="easyflow-breadcrumb__link"
|
||||
href="javascript:void 0"
|
||||
@click.stop="handleClick(item.path)"
|
||||
>
|
||||
<div class="flex-center">
|
||||
<div class="flex items-center">
|
||||
<EasyFlowIcon
|
||||
v-if="showIcon"
|
||||
:class="{ 'size-5': item.isHome }"
|
||||
:icon="item.icon"
|
||||
class="mr-1 size-4"
|
||||
/>
|
||||
{{ item.title }}
|
||||
<span class="max-w-[180px] truncate">{{ item.title }}</span>
|
||||
</div>
|
||||
</BreadcrumbLink>
|
||||
<BreadcrumbPage v-else>
|
||||
<div class="flex-center">
|
||||
<BreadcrumbPage
|
||||
v-else
|
||||
class="easyflow-breadcrumb__current"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<EasyFlowIcon
|
||||
v-if="showIcon"
|
||||
:class="{ 'size-5': item.isHome }"
|
||||
:icon="item.icon"
|
||||
class="mr-1 size-4"
|
||||
/>
|
||||
{{ item.title }}
|
||||
<span class="max-w-[220px] truncate">{{ item.title }}</span>
|
||||
</div>
|
||||
</BreadcrumbPage>
|
||||
<BreadcrumbSeparator
|
||||
v-if="index < breadcrumbs.length - 1 && !item.isHome"
|
||||
class="easyflow-breadcrumb__separator"
|
||||
/>
|
||||
</BreadcrumbItem>
|
||||
</template>
|
||||
@@ -96,3 +101,60 @@ function handleClick(path?: string) {
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.easyflow-breadcrumb {
|
||||
gap: 4px;
|
||||
color: hsl(var(--breadcrumb-muted));
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.easyflow-breadcrumb__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
color: hsl(var(--breadcrumb-muted));
|
||||
transition:
|
||||
color 0.16s ease,
|
||||
background-color 0.16s ease,
|
||||
transform 0.16s ease;
|
||||
}
|
||||
|
||||
.easyflow-breadcrumb__link:hover {
|
||||
background: hsl(var(--nav-item-hover) / 0.7);
|
||||
color: hsl(var(--nav-item-active-foreground));
|
||||
transform: translateY(-0.5px);
|
||||
}
|
||||
|
||||
.easyflow-breadcrumb__current {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
padding: 4px 10px;
|
||||
color: hsl(var(--breadcrumb-current));
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.01em;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
hsl(var(--nav-item-active) / 0.88),
|
||||
hsl(var(--glass-tint) / 0.92)
|
||||
);
|
||||
border-radius: 999px;
|
||||
box-shadow:
|
||||
inset 0 1px 0 hsl(var(--nav-sheen) / 0.42),
|
||||
0 10px 22px -18px hsl(var(--primary) / 0.22);
|
||||
}
|
||||
|
||||
.easyflow-breadcrumb__separator {
|
||||
color: hsl(var(--breadcrumb-muted) / 0.72);
|
||||
}
|
||||
|
||||
.easyflow-breadcrumb__separator :deep(svg) {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -72,12 +72,16 @@ defineExpose({
|
||||
</Transition>
|
||||
<AlertDialogContent
|
||||
ref="contentRef"
|
||||
:style="{ ...(zIndex ? { zIndex } : {}), position: 'fixed' }"
|
||||
:style="{
|
||||
...(zIndex ? { zIndex } : {}),
|
||||
position: 'fixed',
|
||||
backdropFilter: 'blur(var(--glass-blur)) saturate(170%)',
|
||||
}"
|
||||
@animationend="onAnimationEnd"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'z-popup bg-background p-6 shadow-lg outline-none sm:rounded-xl',
|
||||
'z-popup border border-[hsl(var(--glass-border))/0.18] bg-[hsl(var(--glass-tint))/0.84] p-6 shadow-[var(--shadow-float)] outline-none supports-[backdrop-filter]:bg-[hsl(var(--glass-tint))/0.62] sm:rounded-[22px]',
|
||||
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
||||
{
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogContentEmits, DialogContentProps } from 'reka-ui';
|
||||
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import type { ClassType } from '@easyflow-core/typings';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
@@ -20,16 +22,19 @@ const props = withDefaults(
|
||||
class?: ClassType;
|
||||
closeClass?: ClassType;
|
||||
closeDisabled?: boolean;
|
||||
contentStyle?: CSSProperties;
|
||||
modal?: boolean;
|
||||
open?: boolean;
|
||||
overlayBlur?: number;
|
||||
overlayClass?: ClassType;
|
||||
overlayStyle?: CSSProperties;
|
||||
showClose?: boolean;
|
||||
zIndex?: number;
|
||||
}
|
||||
>(),
|
||||
{
|
||||
appendTo: 'body',
|
||||
animationType: 'slide',
|
||||
animationType: 'scale',
|
||||
closeDisabled: false,
|
||||
showClose: true,
|
||||
},
|
||||
@@ -41,8 +46,11 @@ const emits = defineEmits<
|
||||
const delegatedProps = computed(() => {
|
||||
const {
|
||||
class: _,
|
||||
contentStyle: _contentStyle,
|
||||
modal: _modal,
|
||||
open: _open,
|
||||
overlayClass: _overlayClass,
|
||||
overlayStyle: _overlayStyle,
|
||||
showClose: __,
|
||||
animationType: ___,
|
||||
...delegated
|
||||
@@ -86,9 +94,11 @@ defineExpose({
|
||||
<Transition name="fade">
|
||||
<DialogOverlay
|
||||
v-if="open && modal"
|
||||
:class="props.overlayClass"
|
||||
:style="{
|
||||
...(zIndex ? { zIndex } : {}),
|
||||
position,
|
||||
...props.overlayStyle,
|
||||
backdropFilter:
|
||||
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
|
||||
}"
|
||||
@@ -97,14 +107,18 @@ defineExpose({
|
||||
</Transition>
|
||||
<DialogContent
|
||||
ref="contentRef"
|
||||
:style="{ ...(zIndex ? { zIndex } : {}), position }"
|
||||
:style="{
|
||||
...(zIndex ? { zIndex } : {}),
|
||||
position,
|
||||
...props.contentStyle,
|
||||
}"
|
||||
@animationend="onAnimationEnd"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'z-popup bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 w-full p-6 shadow-lg outline-none sm:rounded-xl',
|
||||
'z-popup data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:duration-220 data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 w-full border border-[hsl(var(--glass-border))/0.18] bg-[hsl(var(--glass-tint))/0.84] p-6 shadow-[var(--shadow-float)] outline-none data-[state=closed]:duration-150 data-[state=closed]:ease-in data-[state=open]:ease-out supports-[backdrop-filter]:bg-[hsl(var(--glass-tint))/0.62] sm:rounded-[22px]',
|
||||
{
|
||||
'data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%]':
|
||||
'data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-4':
|
||||
animationType === 'slide',
|
||||
},
|
||||
props.class,
|
||||
@@ -118,7 +132,7 @@ defineExpose({
|
||||
:disabled="closeDisabled"
|
||||
:class="
|
||||
cn(
|
||||
'data-[state=open]:bg-accent data-[state=open]:text-muted-foreground hover:bg-accent hover:text-accent-foreground text-foreground/80 flex-center absolute right-3 top-3 h-6 w-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none',
|
||||
'data-[state=open]:text-muted-foreground hover:text-accent-foreground text-foreground/80 flex-center opacity-78 absolute right-3 top-3 h-7 w-7 rounded-full px-1 text-lg shadow-[0_10px_24px_-24px_hsl(var(--foreground)/0.34)] transition-opacity hover:bg-[hsl(var(--surface-contrast-soft))/0.98] hover:opacity-100 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-[hsl(var(--surface-contrast-soft))/0.92]',
|
||||
props.closeClass,
|
||||
)
|
||||
"
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { inject } from 'vue';
|
||||
import { computed, inject, useAttrs } from 'vue';
|
||||
|
||||
import { useScrollLock } from '@easyflow-core/composables';
|
||||
import { cn } from '@easyflow-core/shared/utils';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
useScrollLock();
|
||||
|
||||
const attrs = useAttrs();
|
||||
const id = inject('DISMISSABLE_MODAL_ID');
|
||||
const overlayClass = computed(() => {
|
||||
const customClass = attrs.class as string | undefined;
|
||||
return cn(
|
||||
customClass ? 'z-popup inset-0' : 'bg-overlay z-popup inset-0',
|
||||
customClass,
|
||||
);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div :data-dismissable-modal="id" class="bg-overlay z-popup inset-0"></div>
|
||||
<div
|
||||
:data-dismissable-modal="id"
|
||||
:class="overlayClass"
|
||||
:style="$attrs.style"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
@@ -91,6 +91,7 @@ function onAnimationEnd(event: AnimationEvent) {
|
||||
:style="{
|
||||
...(zIndex ? { zIndex } : {}),
|
||||
position,
|
||||
backdropFilter: 'blur(var(--glass-blur)) saturate(170%)',
|
||||
}"
|
||||
@animationend="onAnimationEnd"
|
||||
v-bind="{ ...forwarded, ...$attrs }"
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
export const sheetVariants = cva(
|
||||
'bg-background shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 border-border',
|
||||
'border-[hsl(var(--glass-border))/0.18] bg-[hsl(var(--glass-tint))/0.84] shadow-[var(--shadow-float)] transition ease-in-out supports-[backdrop-filter]:bg-[hsl(var(--glass-tint))/0.62] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
|
||||
{
|
||||
defaultVariants: {
|
||||
side: 'right',
|
||||
@@ -11,11 +11,11 @@ export const sheetVariants = cva(
|
||||
variants: {
|
||||
side: {
|
||||
bottom:
|
||||
'inset-x-0 bottom-0 border-t border-border data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
||||
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left ',
|
||||
'inset-x-0 bottom-0 border-t border-[hsl(var(--divider-faint))/0.3] data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
||||
left: 'inset-y-0 left-0 h-full w-3/4 border-r border-[hsl(var(--divider-faint))/0.3] data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left ',
|
||||
right:
|
||||
'inset-y-0 right-0 w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right',
|
||||
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
||||
'inset-y-0 right-0 w-3/4 border-l border-[hsl(var(--divider-faint))/0.3] data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right',
|
||||
top: 'inset-x-0 top-0 border-b border-[hsl(var(--divider-faint))/0.3] data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ defineOptions({
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
contentClass: 'easyflow-tabs-content',
|
||||
contextMenus: () => [],
|
||||
gap: 7,
|
||||
gap: 8,
|
||||
tabs: () => [],
|
||||
});
|
||||
|
||||
@@ -76,7 +76,7 @@ function onMouseDown(e: MouseEvent, tab: TabConfig) {
|
||||
ref="contentRef"
|
||||
:class="contentClass"
|
||||
:style="style"
|
||||
class="tabs-chrome !flex h-full w-max overflow-y-hidden pr-6"
|
||||
class="tabs-chrome !flex h-full w-max items-center gap-1 overflow-y-hidden px-2"
|
||||
>
|
||||
<TransitionGroup name="slide-left">
|
||||
<div
|
||||
@@ -92,7 +92,7 @@ function onMouseDown(e: MouseEvent, tab: TabConfig) {
|
||||
]"
|
||||
:data-active-tab="active"
|
||||
:data-index="i"
|
||||
class="tabs-chrome__item draggable translate-all group relative -mr-3 flex h-full select-none items-center"
|
||||
class="tabs-chrome__item draggable translate-all group relative flex h-full min-w-0 select-none items-center"
|
||||
data-tab-item="true"
|
||||
@click="active = tab.key"
|
||||
@mousedown="onMouseDown($event, tab)"
|
||||
@@ -103,63 +103,45 @@ function onMouseDown(e: MouseEvent, tab: TabConfig) {
|
||||
:modal="false"
|
||||
item-class="pr-6"
|
||||
>
|
||||
<div class="relative size-full px-1">
|
||||
<div class="relative size-full">
|
||||
<!-- divider -->
|
||||
<div
|
||||
v-if="i !== 0 && tab.key !== active"
|
||||
class="tabs-chrome__divider bg-border absolute left-[var(--gap)] top-1/2 z-0 h-4 w-[1px] translate-y-[-50%] transition-all"
|
||||
></div>
|
||||
<!-- background -->
|
||||
<div
|
||||
class="tabs-chrome__background absolute z-[-1] size-full px-[calc(var(--gap)-1px)] py-0 transition-opacity duration-150"
|
||||
class="tabs-chrome__background absolute inset-0 z-[-1] px-0 py-0 transition-opacity duration-150"
|
||||
>
|
||||
<div
|
||||
class="tabs-chrome__background-content group-[.is-active]:bg-primary/15 dark:group-[.is-active]:bg-accent h-full rounded-tl-[var(--gap)] rounded-tr-[var(--gap)] duration-150"
|
||||
class="tabs-chrome__background-content h-[calc(100%-8px)] rounded-2xl bg-transparent transition-all duration-150 group-[.is-active]:bg-[hsl(var(--glass-tint))/0.74] group-[.is-active]:shadow-[0_16px_28px_-24px_hsl(var(--foreground)/0.34)]"
|
||||
></div>
|
||||
<svg
|
||||
class="tabs-chrome__background-before group-[.is-active]:fill-primary/15 dark:group-[.is-active]:fill-accent absolute bottom-0 left-[-1px] fill-transparent transition-all duration-150"
|
||||
height="7"
|
||||
width="7"
|
||||
>
|
||||
<path d="M 0 7 A 7 7 0 0 0 7 0 L 7 7 Z" />
|
||||
</svg>
|
||||
<svg
|
||||
class="tabs-chrome__background-after group-[.is-active]:fill-primary/15 dark:group-[.is-active]:fill-accent absolute bottom-0 right-[-1px] fill-transparent transition-all duration-150"
|
||||
height="7"
|
||||
width="7"
|
||||
>
|
||||
<path d="M 0 0 A 7 7 0 0 0 7 7 L 0 7 Z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- extra -->
|
||||
<div
|
||||
class="tabs-chrome__extra absolute right-[var(--gap)] top-1/2 z-[3] size-4 translate-y-[-50%]"
|
||||
class="tabs-chrome__extra absolute right-2 top-1/2 z-[3] size-4 -translate-y-1/2"
|
||||
>
|
||||
<!-- close-icon -->
|
||||
<X
|
||||
v-show="!tab.affixTab && tabsView.length > 1 && tab.closable"
|
||||
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground text-accent-foreground/80 group-[.is-active]:text-accent-foreground mt-[2px] size-3 cursor-pointer rounded-full transition-all"
|
||||
class="mt-[2px] size-3 cursor-pointer rounded-full stroke-[hsl(var(--nav-item-muted-foreground))] transition-all hover:bg-[hsl(var(--surface-contrast-soft))/0.92] hover:stroke-[hsl(var(--foreground))] group-[.is-active]:stroke-[hsl(var(--nav-item-active-foreground))]"
|
||||
@click.stop="() => emit('close', tab.key)"
|
||||
/>
|
||||
<Pin
|
||||
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
|
||||
class="hover:text-accent-foreground text-accent-foreground/80 group-[.is-active]:text-accent-foreground mt-[1px] size-3.5 cursor-pointer rounded-full transition-all"
|
||||
class="mt-[1px] size-3.5 cursor-pointer rounded-full stroke-[hsl(var(--nav-item-muted-foreground))] transition-all hover:bg-[hsl(var(--surface-contrast-soft))/0.92] hover:stroke-[hsl(var(--foreground))] group-[.is-active]:stroke-[hsl(var(--nav-item-active-foreground))]"
|
||||
@click.stop="() => emit('unpin', tab)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- tab-item-main -->
|
||||
<div
|
||||
class="tabs-chrome__item-main group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground text-accent-foreground z-[2] mx-[calc(var(--gap)*2)] my-0 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pl-2 pr-4 duration-150"
|
||||
class="tabs-chrome__item-main z-[2] mx-3 my-0 flex h-full min-w-0 items-center overflow-hidden pl-2 pr-5 text-[hsl(var(--nav-item-muted-foreground))] duration-150 group-[.is-active]:font-medium group-[.is-active]:text-[hsl(var(--nav-item-active-foreground))]"
|
||||
>
|
||||
<EasyFlowIcon
|
||||
v-if="showIcon"
|
||||
:icon="tab.icon"
|
||||
class="mr-1 flex size-4 items-center overflow-hidden"
|
||||
class="mr-2 flex size-[15px] items-center overflow-hidden"
|
||||
/>
|
||||
|
||||
<span class="flex-1 overflow-hidden whitespace-nowrap text-sm">
|
||||
<span class="flex-1 overflow-hidden whitespace-nowrap text-[13px]">
|
||||
{{ tab.title }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -187,10 +169,8 @@ function onMouseDown(e: MouseEvent, tab: TabConfig) {
|
||||
}
|
||||
|
||||
.tabs-chrome__background {
|
||||
@apply pb-[2px];
|
||||
|
||||
&-content {
|
||||
@apply bg-accent mx-[2px] rounded-md;
|
||||
@apply bg-[hsl(var(--nav-item-hover))];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ const typeWithClass = computed(() => {
|
||||
},
|
||||
plain: {
|
||||
content:
|
||||
'h-full [&:not(:first-child)]:border-l last:border-r border-border',
|
||||
"h-[calc(100%-8px)] rounded-2xl border border-transparent bg-transparent transition-all duration-150 [&.is-active]:bg-[hsl(var(--glass-tint))/0.72] [&.is-active]:shadow-[0_16px_28px_-24px_hsl(var(--foreground)/0.34)] [&:not(.is-active)]:hover:bg-[hsl(var(--nav-item-hover))/0.9]",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -81,7 +81,7 @@ function onMouseDown(e: MouseEvent, tab: TabConfig) {
|
||||
<template>
|
||||
<div
|
||||
:class="contentClass"
|
||||
class="relative !flex h-full w-max items-center overflow-hidden pr-6"
|
||||
class="relative !flex h-full w-max items-center gap-1 overflow-hidden pr-3"
|
||||
>
|
||||
<TransitionGroup name="slide-left">
|
||||
<div
|
||||
@@ -89,14 +89,14 @@ function onMouseDown(e: MouseEvent, tab: TabConfig) {
|
||||
:key="tab.key"
|
||||
:class="[
|
||||
{
|
||||
'is-active dark:bg-accent bg-primary/15': tab.key === active,
|
||||
'is-active': tab.key === active,
|
||||
draggable: !tab.affixTab,
|
||||
'affix-tab': tab.affixTab,
|
||||
},
|
||||
typeWithClass.content,
|
||||
]"
|
||||
:data-index="i"
|
||||
class="tab-item [&:not(.is-active)]:hover:bg-accent translate-all group relative flex cursor-pointer select-none"
|
||||
class="tab-item translate-all group relative flex min-w-0 cursor-pointer select-none"
|
||||
data-tab-item="true"
|
||||
@click="active = tab.key"
|
||||
@mousedown="onMouseDown($event, tab)"
|
||||
@@ -115,28 +115,28 @@ function onMouseDown(e: MouseEvent, tab: TabConfig) {
|
||||
<!-- close-icon -->
|
||||
<X
|
||||
v-show="!tab.affixTab && tabsView.length > 1 && tab.closable"
|
||||
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground dark:group-[.is-active]:text-accent-foreground group-[.is-active]:text-primary size-3 cursor-pointer rounded-full transition-all"
|
||||
class="size-3 cursor-pointer rounded-full stroke-[hsl(var(--nav-item-muted-foreground))] transition-all hover:bg-[hsl(var(--surface-contrast-soft))/0.92] hover:stroke-[hsl(var(--foreground))] group-[.is-active]:stroke-[hsl(var(--nav-item-active-foreground))]"
|
||||
@click.stop="() => emit('close', tab.key)"
|
||||
/>
|
||||
<Pin
|
||||
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
|
||||
class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mt-[1px] size-3.5 cursor-pointer rounded-full transition-all"
|
||||
class="mt-[1px] size-3.5 cursor-pointer rounded-full stroke-[hsl(var(--nav-item-muted-foreground))] transition-all hover:bg-[hsl(var(--surface-contrast-soft))/0.92] hover:stroke-[hsl(var(--foreground))] group-[.is-active]:stroke-[hsl(var(--nav-item-active-foreground))]"
|
||||
@click.stop="() => emit('unpin', tab)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- tab-item-main -->
|
||||
<div
|
||||
class="text-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mx-3 mr-4 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pr-3 transition-all duration-300"
|
||||
class="mx-3 mr-4 flex h-full items-center overflow-hidden pr-3 text-[hsl(var(--nav-item-muted-foreground))] transition-all duration-150 group-[.is-active]:font-medium group-[.is-active]:text-[hsl(var(--nav-item-active-foreground))]"
|
||||
>
|
||||
<EasyFlowIcon
|
||||
v-if="showIcon"
|
||||
:icon="tab.icon"
|
||||
class="mr-2 flex size-4 items-center overflow-hidden"
|
||||
class="mr-2 flex size-[15px] items-center overflow-hidden"
|
||||
fallback
|
||||
/>
|
||||
|
||||
<span class="flex-1 overflow-hidden whitespace-nowrap text-sm">
|
||||
<span class="flex-1 overflow-hidden whitespace-nowrap text-[13px]">
|
||||
{{ tab.title }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ defineProps<DropdownMenuProps>();
|
||||
<template>
|
||||
<EasyFlowDropdownMenu :menus="menus" :modal="false">
|
||||
<div
|
||||
class="flex-center hover:bg-muted hover:text-foreground text-muted-foreground border-border h-full cursor-pointer border-l px-2 text-lg font-semibold"
|
||||
class="flex-center mr-1 h-8 w-8 cursor-pointer rounded-2xl border border-transparent bg-[hsl(var(--glass-tint))/0.52] text-[hsl(var(--nav-item-muted-foreground))] shadow-[0_10px_24px_-24px_hsl(var(--foreground)/0.3)] backdrop-blur-xl transition-[background-color,color,transform] hover:-translate-y-0.5 hover:bg-[hsl(var(--surface-contrast-soft))/0.92] hover:text-foreground"
|
||||
>
|
||||
<ChevronDown class="size-4" />
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ function toggleScreen() {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex-center hover:bg-muted hover:text-foreground text-muted-foreground border-border h-full cursor-pointer border-l px-2 text-lg font-semibold"
|
||||
class="flex-center h-8 w-8 cursor-pointer rounded-2xl border border-transparent bg-[hsl(var(--glass-tint))/0.52] text-[hsl(var(--nav-item-muted-foreground))] shadow-[0_10px_24px_-24px_hsl(var(--foreground)/0.3)] backdrop-blur-xl transition-[background-color,color,transform] hover:-translate-y-0.5 hover:bg-[hsl(var(--surface-contrast-soft))/0.92] hover:text-foreground"
|
||||
@click="toggleScreen"
|
||||
>
|
||||
<Minimize2 v-if="screen" class="size-4" />
|
||||
|
||||
@@ -53,10 +53,10 @@ useTabsDrag(props, emit);
|
||||
<span
|
||||
v-show="showScrollButton"
|
||||
:class="{
|
||||
'hover:bg-muted text-muted-foreground cursor-pointer': !scrollIsAtLeft,
|
||||
'cursor-pointer text-[hsl(var(--nav-item-muted-foreground))] hover:bg-[hsl(var(--surface-contrast-soft))] hover:text-foreground': !scrollIsAtLeft,
|
||||
'pointer-events-none opacity-30': scrollIsAtLeft,
|
||||
}"
|
||||
class="border-r px-2"
|
||||
class="mx-1 my-1 flex items-center rounded-2xl border border-transparent bg-[hsl(var(--glass-tint))/0.44] px-2 shadow-[0_10px_24px_-24px_hsl(var(--foreground)/0.3)] backdrop-blur-xl transition-[background-color,color,transform]"
|
||||
@click="scrollDirection('left')"
|
||||
>
|
||||
<ChevronLeft class="size-4 h-full" />
|
||||
@@ -64,7 +64,7 @@ useTabsDrag(props, emit);
|
||||
|
||||
<div
|
||||
:class="{
|
||||
'pt-[3px]': styleType === 'chrome',
|
||||
'pt-[2px]': styleType === 'chrome',
|
||||
}"
|
||||
class="size-full flex-1 overflow-hidden"
|
||||
>
|
||||
@@ -94,10 +94,10 @@ useTabsDrag(props, emit);
|
||||
<span
|
||||
v-show="showScrollButton"
|
||||
:class="{
|
||||
'hover:bg-muted text-muted-foreground cursor-pointer': !scrollIsAtRight,
|
||||
'cursor-pointer text-[hsl(var(--nav-item-muted-foreground))] hover:bg-[hsl(var(--surface-contrast-soft))] hover:text-foreground': !scrollIsAtRight,
|
||||
'pointer-events-none opacity-30': scrollIsAtRight,
|
||||
}"
|
||||
class="hover:bg-muted text-muted-foreground cursor-pointer border-l px-2"
|
||||
class="mx-1 my-1 flex items-center rounded-2xl border border-transparent bg-[hsl(var(--glass-tint))/0.44] px-2 shadow-[0_10px_24px_-24px_hsl(var(--foreground)/0.3)] backdrop-blur-xl transition-[background-color,color,transform]"
|
||||
@click="scrollDirection('right')"
|
||||
>
|
||||
<ChevronRight class="size-4 h-full" />
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { TabsProps } from './types';
|
||||
|
||||
import type { ComponentPublicInstance } from 'vue';
|
||||
|
||||
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
import { EasyFlowScrollbar } from '@easyflow-core/shadcn-ui';
|
||||
@@ -7,12 +9,13 @@ import { EasyFlowScrollbar } from '@easyflow-core/shadcn-ui';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
|
||||
type DomElement = Element | null | undefined;
|
||||
type ScrollbarInstance = ComponentPublicInstance & { $el: HTMLElement };
|
||||
|
||||
export function useTabsViewScroll(props: TabsProps) {
|
||||
let resizeObserver: null | ResizeObserver = null;
|
||||
let mutationObserver: MutationObserver | null = null;
|
||||
let tabItemCount = 0;
|
||||
const scrollbarRef = ref<InstanceType<typeof EasyFlowScrollbar> | null>(null);
|
||||
const scrollbarRef = ref<ScrollbarInstance | null>(null);
|
||||
const scrollViewportEl = ref<DomElement>(null);
|
||||
const showScrollButton = ref(false);
|
||||
const scrollIsAtLeft = ref(true);
|
||||
|
||||
Reference in New Issue
Block a user