diff --git a/components.d.ts b/components.d.ts index b1ab688..cc00ca1 100644 --- a/components.d.ts +++ b/components.d.ts @@ -35,9 +35,11 @@ declare module '@vue/runtime-core' { AMenu: typeof import('ant-design-vue/es')['Menu'] AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider'] AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] + AMenuItemGroup: typeof import('ant-design-vue/es')['MenuItemGroup'] AModal: typeof import('ant-design-vue/es')['Modal'] APagination: typeof import('ant-design-vue/es')['Pagination'] APopconfirm: typeof import('ant-design-vue/es')['Popconfirm'] + APopover: typeof import('ant-design-vue/es')['Popover'] ARadio: typeof import('ant-design-vue/es')['Radio'] ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] @@ -81,6 +83,8 @@ declare module '@vue/runtime-core' { CustomTag: typeof import('./src/components/CustomTag.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + ThemeMenuSection: typeof import('./src/core/components/ThemeMenuSection.vue')['default'] + ThemeToggle: typeof import('./src/core/components/ThemeToggle.vue')['default'] UploadPic: typeof import('./src/components/UploadPic.vue')['default'] } } diff --git a/index.html b/index.html index 8b063a4..b523c65 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,17 @@ admin +
diff --git a/my-uno.config.ts b/my-uno.config.ts index 8934ed6..3bfbbee 100644 --- a/my-uno.config.ts +++ b/my-uno.config.ts @@ -11,7 +11,10 @@ import { export default defineConfig({ shortcuts: [ - // ... + ['surface', 'bg-white dark:bg-[#1f1f1f]'], + ['surface-muted', 'bg-gray-50 dark:bg-[#262626]'], + ['border-base', 'border-gray-200 dark:border-[color:var(--admin-border)]'], + ['text-muted', 'text-gray-600 dark:text-gray-400'], ], theme: { colors: { @@ -20,7 +23,12 @@ export default defineConfig({ } }, presets: [ - presetUno(), + presetUno({ + dark: { + dark: 'html.is-dark', + light: 'html:not(.is-dark)', + }, + }), presetAttributify(), presetIcons(), presetTypography(), diff --git a/src/App.vue b/src/App.vue index a187001..66e44ed 100644 --- a/src/App.vue +++ b/src/App.vue @@ -6,17 +6,21 @@ diff --git a/src/core/components/BaseIcon/src/Icon.vue b/src/core/components/BaseIcon/src/Icon.vue index 3c92524..34683ed 100644 --- a/src/core/components/BaseIcon/src/Icon.vue +++ b/src/core/components/BaseIcon/src/Icon.vue @@ -12,7 +12,7 @@ //todo convert to async load import icons from '@core/assets/icons/icon.json' import { usePrefix } from '@core/composable/usePrefix' -import { computed, onMounted, ref } from 'vue' +import { computed, nextTick, onMounted, ref, watch } from 'vue' import { baseIconSize } from '@root/config' interface Props { icon: string @@ -27,20 +27,37 @@ const props = withDefaults(defineProps(), { const { prefixCls } = usePrefix('icons') const tempIcon = ref() const refIcon = ref() -//@ts-ignore -tempIcon.value = icons[`${props.icon}`] as string + +function resolveIcon(iconName: string) { + return icons[`${iconName}` as keyof typeof icons] as string +} + +function applySize() { + if (!props.size || !refIcon.value) return + + const svg = refIcon.value.children[0] as SVGElement | undefined + if (!svg) return + + svg.setAttribute('width', String(props.size)) + svg.setAttribute('height', String(props.size)) +} + +tempIcon.value = resolveIcon(props.icon) + +watch( + () => props.icon, + (iconName) => { + tempIcon.value = resolveIcon(iconName) + nextTick(() => applySize()) + } +) + const color = computed(() => { return props.color }) onMounted(() => { - if (props.size) { - const svg: SVGElement = refIcon.value?.children[0] as SVGElement - // console.log(svg) - svg.setAttribute('width', props.size as string) - svg.setAttribute('height', props.size as string) - // console.log(props.icon) - } + applySize() }) diff --git a/src/core/components/BaseTable/src/BaseTable.vue b/src/core/components/BaseTable/src/BaseTable.vue index 42112eb..5a0a64f 100644 --- a/src/core/components/BaseTable/src/BaseTable.vue +++ b/src/core/components/BaseTable/src/BaseTable.vue @@ -2,7 +2,7 @@
-
+
+ + +
+
+ + {{ opt.label }} +
+ +
+
+ + وضعیت فعلی: {{ isDark ? 'تاریک' : 'روشن' }} + +
+ + + + + + diff --git a/src/core/components/ThemeToggle.vue b/src/core/components/ThemeToggle.vue new file mode 100644 index 0000000..98f554d --- /dev/null +++ b/src/core/components/ThemeToggle.vue @@ -0,0 +1,146 @@ + + + + + + + diff --git a/src/core/composable/useAdminTheme.ts b/src/core/composable/useAdminTheme.ts new file mode 100644 index 0000000..e37c6be --- /dev/null +++ b/src/core/composable/useAdminTheme.ts @@ -0,0 +1,17 @@ +import { computed } from 'vue' +import { storeToRefs } from 'pinia' +import { useDarkmode } from '@core/stores/darkmode' + +export function useAdminTheme() { + const darkmode = useDarkmode() + const { isDark, colorSchema } = storeToRefs(darkmode) + + const menuTheme = computed(() => (isDark.value ? 'dark' : 'light') as 'dark' | 'light') + + return { + isDark, + colorSchema, + menuTheme, + darkmode, + } +} diff --git a/src/core/layouts/BaseSettings.vue b/src/core/layouts/BaseSettings.vue index e9fe982..1afdd62 100644 --- a/src/core/layouts/BaseSettings.vue +++ b/src/core/layouts/BaseSettings.vue @@ -10,6 +10,14 @@
+
+ حالت نمایش + + روشن + تاریک + خودکار + +
نوار کناری (Sidebar) @@ -59,10 +67,14 @@ import { usePrefix } from '@core/composable/usePrefix' import { useSettingsStore } from '@core/stores/settings' import { useLayoutStore } from '@core/stores/layout' +import { useDarkmode } from '@core/stores/darkmode' +import { storeToRefs } from 'pinia' //const visible = ref(false) const { prefixCls } = usePrefix('base-settings') const settingsStore = useSettingsStore() const layoutStore = useLayoutStore() +const darkmode = useDarkmode() +const { colorSchema } = storeToRefs(darkmode) diff --git a/src/core/layouts/composnts/BasicMenu.vue b/src/core/layouts/composnts/BasicMenu.vue index 246e387..576433b 100644 --- a/src/core/layouts/composnts/BasicMenu.vue +++ b/src/core/layouts/composnts/BasicMenu.vue @@ -14,6 +14,7 @@ > @@ -64,6 +65,7 @@ import { useLayoutStore } from '@core/stores/layout' import BaseMenuItem from '@core/layouts/composnts/BaseMenuItem.vue' import type { RouteRecordName, RouteRecordRaw } from 'vue-router' import { useRouterStore } from '@core/stores/router' +import { useAdminTheme } from '@core/composable/useAdminTheme' const SCROLL_STEP = 180 const EDGE_THRESHOLD = 2 @@ -73,6 +75,7 @@ const layoutStore = useLayoutStore() const currentRoute = useRoute() const selectedKeys = ref<(RouteRecordName | undefined)[]>([]) const routerStore = useRouterStore() +const { menuTheme } = useAdminTheme() const menuScrollRef = ref(null) const showLeftArrow = ref(false) const showRightArrow = ref(false) @@ -224,6 +227,77 @@ useResizeObserver(menuScrollRef, () => { position: relative; min-width: 0; flex: 1; + height: 100%; + display: flex; + align-items: center; + + .top-menu-scroll .ant-menu.ant-menu-horizontal { + display: flex; + flex-wrap: nowrap; + align-items: center; + width: max-content; + height: 40px; + line-height: 40px; + border-bottom: none !important; + padding: 0 4px !important; + background: transparent !important; + } + + .top-menu-scroll .ant-menu-horizontal > .ant-menu-item, + .top-menu-scroll .ant-menu-horizontal > .ant-menu-submenu, + .top-menu-scroll .ant-menu-horizontal > .ant-menu-submenu > .ant-menu-submenu-title { + top: 0 !important; + margin-top: 0 !important; + height: 36px !important; + line-height: 36px !important; + padding: 0 10px !important; + margin-inline: 2px !important; + border-radius: 6px; + display: inline-flex; + align-items: center; + transition: background-color 0.15s ease, color 0.15s ease !important; + } + + .top-menu-scroll .ant-menu-horizontal > .ant-menu-item::after, + .top-menu-scroll .ant-menu-horizontal > .ant-menu-submenu::after { + display: none !important; + } + + .top-menu-scroll .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected, + .top-menu-scroll .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected { + background-color: var(--admin-menu-selected); + color: var(--admin-menu-selected-text); + } + + .top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-item-selected, + .top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-submenu-selected > .ant-menu-submenu-title { + background-color: var(--admin-menu-selected) !important; + color: var(--admin-menu-selected-text) !important; + } + + .top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-item-selected .ant-menu-item-icon, + .top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-submenu-selected .ant-menu-item-icon, + .top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-submenu-selected .ant-menu-submenu-arrow { + color: var(--admin-menu-selected-text) !important; + } + + .top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-item:not(.ant-menu-item-selected):hover, + .top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-submenu:not(.ant-menu-submenu-selected):hover > .ant-menu-submenu-title { + background-color: var(--admin-bg-hover) !important; + color: var(--admin-gold-light) !important; + box-shadow: inset 0 0 0 1px var(--admin-border-strong) !important; + } + + .top-menu-scroll .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected::after, + .top-menu-scroll .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected::after { + display: none !important; + } + + .top-menu-scroll .ant-menu-horizontal .ant-menu-item-icon + .ant-menu-title-content, + .top-menu-scroll .ant-menu-horizontal .ant-menu-item-icon + span { + margin-inline-start: 6px !important; + margin-inline-end: 0 !important; + } } .top-menu-nav__btn { @@ -233,31 +307,31 @@ useResizeObserver(menuScrollRef, () => { display: flex; align-items: center; justify-content: center; - width: 30px; - height: 36px; + width: 28px; + height: 32px; border: none; border-radius: 6px; - background: #fff; - color: @primary-color; + background: var(--admin-bg-elevated); + color: var(--admin-gold-light); cursor: pointer; transform: translateY(-50%); - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06); + box-shadow: 0 0 0 1px var(--admin-border); transition: background-color 0.2s ease; &:hover { - background: lighten(@primary-color, 53%); + background: var(--admin-bg-hover); } &--left { left: 0; - padding-right: 4px; - background: linear-gradient(to right, #fff 55%, rgba(255, 255, 255, 0.85)); + padding-right: 2px; + background: linear-gradient(to right, var(--admin-gradient-surface) 60%, var(--admin-gradient-surface-fade)); } &--right { right: 0; - padding-left: 4px; - background: linear-gradient(to left, #fff 55%, rgba(255, 255, 255, 0.85)); + padding-left: 2px; + background: linear-gradient(to left, var(--admin-gradient-surface) 60%, var(--admin-gradient-surface-fade)); } } @@ -267,48 +341,14 @@ useResizeObserver(menuScrollRef, () => { overflow-y: hidden; flex: 1; min-width: 0; + height: 100%; + display: flex; + align-items: center; scrollbar-width: none; &::-webkit-scrollbar { display: none; } - - .ant-menu { - display: flex; - flex-wrap: nowrap; - width: max-content; - border-bottom: none; - padding-top: 4px; - padding-bottom: 4px; - } - - .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item, - .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu { - padding: 0 5px; - } - - .ant-menu-horizontal > .ant-menu-item::after, - .ant-menu-horizontal > .ant-menu-submenu::after { - right: 0 !important; - left: 0 !important; - } - - .ant-menu-horizontal > .ant-menu-item-selected::after, - .ant-menu-horizontal > .ant-menu-submenu-selected::after { - position: absolute; - bottom: 0; - border-bottom: 2px solid @primary-color; - transition: border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); - content: ''; - } - - .ant-menu-item-selected { - background-color: lighten(@primary-color, 53%); - } - - .ant-menu-item { - margin-left: 12px; - } } .ant-menu-item, @@ -321,40 +361,34 @@ useResizeObserver(menuScrollRef, () => { } } - /** start style for horizontal menu */ - - .ant-menu-horizontal { - padding: 0 !important; - - li { - padding: 5px; - } - } - - /** end style for horizontal menu */ - &-side-menu { - .ant-menu-item:not(.ant-menu-item-disabled):not(.ant-menu-item-selected):hover, - .ant-menu-submenu .ant-menu-submenu-title:not(.ant-menu-item-disabled):hover { + .ant-menu:not(.ant-menu-dark) .ant-menu-item:not(.ant-menu-item-disabled):not(.ant-menu-item-selected):hover, + .ant-menu:not(.ant-menu-dark) .ant-menu-submenu .ant-menu-submenu-title:not(.ant-menu-item-disabled):hover { transition: background-color 0.9ms linear; border-radius: var(--border-radius-base); color: unset !important; - background-color: #f3f3f3 !important; + background-color: var(--admin-bg-hover) !important; } } - .ant-menu { + .ant-menu:not(.ant-menu-horizontal) { padding: 10px; user-select: none; + + .ant-menu-item, + .ant-menu-submenu-title { + @apply px-2 !important; + } } .ant-menu-title-content { - font-size: 15px; + font-size: 14px; } - .ant-menu-item, - .ant-menu-submenu-title { - @apply px-2 !important; + .ant-menu:not(.ant-menu-horizontal) .ant-menu-item.ant-menu-item-selected { + &::after { + content: unset; + } } .ant-menu.ant-menu-inline-collapsed > .ant-menu-item .ant-menu-item-icon + span, @@ -380,12 +414,6 @@ useResizeObserver(menuScrollRef, () => { height: 36px; } - .ant-menu-item.ant-menu-item-selected { - &::after { - content: unset; - } - } - .ant-menu.ant-menu-sub.ant-menu-inline { background-color: unset !important; } diff --git a/src/core/layouts/composnts/BasicSubMenu.vue b/src/core/layouts/composnts/BasicSubMenu.vue index b59235e..8b4e663 100644 --- a/src/core/layouts/composnts/BasicSubMenu.vue +++ b/src/core/layouts/composnts/BasicSubMenu.vue @@ -64,11 +64,13 @@ const menu = computed(() => { @prefix:~ '@{prefixCls}-basic-sub-menu'; .@{prefix}{ - &.ant-menu-submenu-selected{ + &.ant-menu-submenu-selected { border-radius: var(--border-radius-base); - background-color: var(--ant-primary-1); - .ant-menu-submenu-arrow{ - color: var(--ant-primary) + background-color: var(--admin-menu-selected); + color: var(--admin-menu-selected-text); + + .ant-menu-submenu-arrow { + color: var(--admin-menu-selected-text); } } diff --git a/src/core/stores/darkmode.ts b/src/core/stores/darkmode.ts index d9e698e..50987bd 100644 --- a/src/core/stores/darkmode.ts +++ b/src/core/stores/darkmode.ts @@ -12,6 +12,7 @@ import { computed, watchEffect } from 'vue' import { usePreferredDark, useStorage } from '@vueuse/core' import { acceptHMRUpdate, defineStore } from 'pinia' +import { syncAntDarkStyle } from '@core/utils/syncAntDarkStyle' export const DARK_MODE_BODY_CLASS = 'is-dark' export type DarkModeSchema = 'auto' | 'dark' | 'light' @@ -24,13 +25,16 @@ export const initDarkmode = () => { */ watchEffect(() => { if (!import.meta.env.SSR) { - const body = document.documentElement + const root = document.documentElement + const isDark = darkmode.isDark - if (darkmode.isDark) { - body.classList.add(DARK_MODE_BODY_CLASS) + if (isDark) { + root.classList.add(DARK_MODE_BODY_CLASS) } else { - body.classList.remove(DARK_MODE_BODY_CLASS) + root.classList.remove(DARK_MODE_BODY_CLASS) } + + void syncAntDarkStyle(isDark) } }) } @@ -56,9 +60,20 @@ export const useDarkmode = defineStore('darkmode', () => { isDark.value = !target.checked } + function toggle() { + isDark.value = !isDark.value + } + + function setSchema(schema: DarkModeSchema) { + colorSchema.value = schema + } + return { isDark, + colorSchema, onChange, + toggle, + setSchema, } }) diff --git a/src/core/stores/layout.ts b/src/core/stores/layout.ts index a9873ac..0cf979c 100644 --- a/src/core/stores/layout.ts +++ b/src/core/stores/layout.ts @@ -24,7 +24,7 @@ export const useLayoutStore = defineStore('layout', () => { })*/ const header = reactive({ - height: '45px', + height: '56px', fixed: true }) diff --git a/src/core/utils/syncAntDarkStyle.ts b/src/core/utils/syncAntDarkStyle.ts new file mode 100644 index 0000000..baa5ebe --- /dev/null +++ b/src/core/utils/syncAntDarkStyle.ts @@ -0,0 +1,161 @@ +const STYLE_ATTR = 'data-antd-dark' + +const BRAND_OVERRIDES = ` +html.is-dark body { + background-color: #141414; +} +html.is-dark { + --ant-primary-color: #d4af37; + --ant-primary-color-hover: #e0bc3a; + --ant-primary-color-active: #c9a227; + --admin-menu-selected: #d4af37; + --admin-menu-selected-text: #141414; +} +html.is-dark .ant-layout-header { + background: #141414 !important; + border-bottom: 1px solid var(--admin-border-strong) !important; + padding: 0 12px !important; +} +html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal { + background: transparent !important; +} +html.is-dark .ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected, +html.is-dark .ant-menu.ant-menu-dark .ant-menu-item-selected, +html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal > .ant-menu-item-selected, +html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-selected > .ant-menu-submenu-title { + background-color: var(--admin-menu-selected) !important; + color: var(--admin-menu-selected-text) !important; +} +html.is-dark .ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected .ant-menu-item-icon, +html.is-dark .ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected .anticon, +html.is-dark .ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected .ant-menu-title-content, +html.is-dark .ant-menu.ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon, +html.is-dark .ant-menu.ant-menu-dark .ant-menu-item-selected .anticon, +html.is-dark .ant-menu.ant-menu-dark .ant-menu-item-selected .ant-menu-title-content, +html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal > .ant-menu-item-selected .ant-menu-item-icon, +html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-selected .ant-menu-item-icon, +html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-selected .ant-menu-submenu-arrow { + color: var(--admin-menu-selected-text) !important; +} +html.is-dark .ant-menu-dark .ant-menu-item:not(.ant-menu-item-selected):hover, +html.is-dark .ant-menu-dark .ant-menu-submenu:not(.ant-menu-submenu-selected) > .ant-menu-submenu-title:hover, +html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal > .ant-menu-item:hover, +html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu:hover > .ant-menu-submenu-title { + background-color: var(--admin-bg-hover) !important; + color: var(--admin-gold-light) !important; + box-shadow: inset 0 0 0 1px var(--admin-border-strong) !important; +} +html.is-dark .hr-tamin-base-button { + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + height: auto !important; + line-height: 1 !important; +} +html.is-dark .hr-tamin-base-button.ant-btn-sm { + min-height: 32px !important; + padding: 5px 12px !important; + overflow: visible !important; +} +html.is-dark .ant-card-body { + padding: 12px !important; +} +html.is-dark .ant-card, +html.is-dark .ant-card-head { + border-color: var(--admin-border-strong) !important; +} +html.is-dark .ant-input, +html.is-dark .ant-input-affix-wrapper, +html.is-dark .ant-select-selector, +html.is-dark .ant-picker { + border-color: var(--admin-border-strong) !important; +} +html.is-dark .ant-input:focus, +html.is-dark .ant-input-focused, +html.is-dark .ant-input-affix-wrapper-focused, +html.is-dark .ant-input-affix-wrapper:focus, +html.is-dark .ant-select-focused:not(.ant-select-disabled) .ant-select-selector, +html.is-dark .ant-picker-focused { + border-color: var(--admin-gold-light) !important; + box-shadow: 0 0 0 2px rgba(212, 175, 55, 0.18) !important; +} +html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) { + color: var(--admin-menu-selected-text) !important; + background: var(--admin-gold-light) !important; + border-color: var(--admin-gold-light) !important; +} +html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover, +html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active { + color: var(--admin-menu-selected-text) !important; + background: var(--admin-gold-hover) !important; + border-color: var(--admin-gold-hover) !important; +} +html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before, +html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover::before, +html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active::before { + background-color: var(--admin-gold-light) !important; +} +html.is-dark .ant-radio-button-wrapper:hover { + color: var(--admin-gold-hover) !important; +} +html.is-dark .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) { + color: var(--admin-gold-light) !important; + border-color: var(--admin-gold-light) !important; +} +html.is-dark .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before { + background-color: var(--admin-gold-light) !important; +} +html.is-dark .ant-btn-primary { + background: var(--admin-gold-light) !important; + border-color: var(--admin-gold-light) !important; +} +html.is-dark .ant-btn-primary:not([disabled]):hover, +html.is-dark .ant-btn-primary:not([disabled]):focus { + background: var(--admin-gold-hover) !important; + border-color: var(--admin-gold-hover) !important; +} +html.is-dark .ant-btn-default:not([disabled]):hover, +html.is-dark .ant-btn-default:not([disabled]):focus { + color: var(--admin-gold-light) !important; + border-color: var(--admin-gold-light) !important; +} +@media (max-width: 376px) { + html.is-dark .ant-card-body { + padding: 5px !important; + } +} +` + +let darkStyleEl: HTMLStyleElement | null = null +let loadingPromise: Promise | null = null + +async function injectDarkStyle() { + if (darkStyleEl) return + + if (!loadingPromise) { + loadingPromise = import('ant-design-vue/dist/antd.dark.css?inline').then((mod) => { + darkStyleEl = document.createElement('style') + darkStyleEl.setAttribute(STYLE_ATTR, '') + darkStyleEl.textContent = `${mod.default}\n${BRAND_OVERRIDES}` + document.head.appendChild(darkStyleEl) + }) + } + + await loadingPromise +} + +function removeDarkStyle() { + darkStyleEl?.remove() + darkStyleEl = null + loadingPromise = null +} + +export async function syncAntDarkStyle(isDark: boolean) { + if (import.meta.env.SSR) return + + if (isDark) { + await injectDarkStyle() + } else { + removeDarkStyle() + } +} diff --git a/src/pages/dashboard/components/dashboardItem.vue b/src/pages/dashboard/components/dashboardItem.vue index 7bf4c9a..4281e95 100644 --- a/src/pages/dashboard/components/dashboardItem.vue +++ b/src/pages/dashboard/components/dashboardItem.vue @@ -28,25 +28,25 @@ const props = defineProps() const bgColor = computed(() => { switch (props.color) { case 'fuchsia': - return 'bg-fuchsia-100' + return 'bg-fuchsia-100 dark:bg-fuchsia-500/20' case 'orange': - return 'bg-orange-100' + return 'bg-orange-100 dark:bg-orange-500/20' case 'green': - return 'bg-green-100' + return 'bg-green-100 dark:bg-green-500/20' case 'blue': - return 'bg-blue-100' + return 'bg-blue-100 dark:bg-blue-500/20' } }) const textColor = computed(() => { switch (props.color) { case 'fuchsia': - return 'text-fuchsia-600' + return 'text-fuchsia-600 dark:text-fuchsia-300' case 'orange': - return 'text-orange-600' + return 'text-orange-600 dark:text-orange-300' case 'green': - return 'text-green-600' + return 'text-green-600 dark:text-green-300' case 'blue': - return 'text-blue-600' + return 'text-blue-600 dark:text-blue-300' } }) diff --git a/src/pages/dashboard/index.vue b/src/pages/dashboard/index.vue index 0019f14..1901738 100644 --- a/src/pages/dashboard/index.vue +++ b/src/pages/dashboard/index.vue @@ -859,7 +859,9 @@ onMounted(() => { } .selected_item { -@apply ! bg- [ #0A1B37 ] ! text-white rounded-lg; + background: #2563eb !important; + color: #fff !important; + border-radius: 0.5rem; } .dashboard-chart-toolbar { @@ -869,10 +871,10 @@ onMounted(() => { gap: 0.5rem; button { - border: 1px solid #d1d5db; + border: 1px solid var(--admin-border-strong); border-radius: 0.5rem; - background: #fff; - color: #374151; + background: var(--admin-bg-elevated); + color: var(--admin-text); cursor: pointer; font-family: inherit; font-size: 0.75rem; @@ -881,8 +883,8 @@ onMounted(() => { &.active, &:hover { - background: #0a1b37; - border-color: #0a1b37; + background: #2563eb; + border-color: #2563eb; color: #fff; } } diff --git a/src/pages/login/components/LoginPanel.vue b/src/pages/login/components/LoginPanel.vue index 3e4a323..363e91f 100644 --- a/src/pages/login/components/LoginPanel.vue +++ b/src/pages/login/components/LoginPanel.vue @@ -1,5 +1,5 @@