added dark mode
This commit is contained in:
@@ -1,20 +1,24 @@
|
||||
<template>
|
||||
<AButton :type="props.type" :size="props.size" :class="[prefixCls, _class]">
|
||||
<!-- py-1 px-2-->
|
||||
<div
|
||||
class="flex items-center gap-1 justify-center flex-row-reverse gap-2 py-1 px-2"
|
||||
:class="[{ 'flex-row-reverse': suffixIcon }]"
|
||||
>
|
||||
<slot v-if="$slots.default"></slot>
|
||||
|
||||
<BaseIcon :size="iconSize" :icon="icon" v-if="icon" :class="iconClass" />
|
||||
</div>
|
||||
<AButton
|
||||
:type="props.type"
|
||||
:size="props.size"
|
||||
:class="[prefixCls, _class, { [`${prefixCls}--icon-suffix`]: suffixIcon }]"
|
||||
>
|
||||
<span v-if="icon && !suffixIcon" class="base-button__icon">
|
||||
<BaseIcon :size="resolvedIconSize" :icon="icon" :class="iconClass" />
|
||||
</span>
|
||||
<span v-if="$slots.default" class="base-button__label">
|
||||
<slot />
|
||||
</span>
|
||||
<span v-if="icon && suffixIcon" class="base-button__icon">
|
||||
<BaseIcon :size="resolvedIconSize" :icon="icon" :class="iconClass" />
|
||||
</span>
|
||||
</AButton>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { usePrefix } from '@core/composable/usePrefix'
|
||||
import { computed } from 'vue'
|
||||
import { computed, useSlots } from 'vue'
|
||||
import { baseButtonIconSize } from '@root/config'
|
||||
|
||||
interface Props {
|
||||
@@ -24,7 +28,6 @@ interface Props {
|
||||
suffixIcon?: boolean
|
||||
type?: string
|
||||
size?: string
|
||||
// prefixIcon?: string
|
||||
}
|
||||
|
||||
const slot = useSlots()
|
||||
@@ -40,17 +43,74 @@ const { prefixCls } = usePrefix('base-button')
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
iconSize: baseButtonIconSize,
|
||||
// iconLeft: false,
|
||||
iconClass: ''
|
||||
})
|
||||
|
||||
const resolvedIconSize = computed(() => {
|
||||
if (props.iconSize !== baseButtonIconSize) {
|
||||
return props.iconSize
|
||||
}
|
||||
|
||||
switch (props.size) {
|
||||
case 'small':
|
||||
return '16'
|
||||
case 'large':
|
||||
return '20'
|
||||
default:
|
||||
return '18'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@prefix:~'@{prefixCls}-base-button';
|
||||
.@{prefix}{
|
||||
//width: fit-content;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
/*padding: 8px;*/
|
||||
|
||||
.@{prefix} {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
gap: 6px;
|
||||
height: auto !important;
|
||||
min-height: 32px;
|
||||
padding: 4px 15px !important;
|
||||
line-height: 1 !important;
|
||||
white-space: nowrap;
|
||||
overflow: visible;
|
||||
vertical-align: middle;
|
||||
|
||||
&.ant-btn-sm {
|
||||
min-height: 32px;
|
||||
padding: 5px 12px !important;
|
||||
font-size: 14px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&.ant-btn-lg {
|
||||
min-height: 40px;
|
||||
padding: 6px 16px !important;
|
||||
}
|
||||
|
||||
&--icon-suffix {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.base-button__icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
line-height: 0;
|
||||
|
||||
:deep(.hr-tamin-icons),
|
||||
:deep(svg) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.base-button__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
line-height: 1 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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<Props>(), {
|
||||
const { prefixCls } = usePrefix('icons')
|
||||
const tempIcon = ref<string>()
|
||||
const refIcon = ref<HTMLDivElement>()
|
||||
//@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()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div :class="[prefixCls, 'min-w-0 max-w-full']">
|
||||
<ASpin tip="در حال دریافت اطلاعات..." :indicator="indicator" :spinning="loading">
|
||||
<div class="scrollable-container" id="components-affix-demo-target" ref="containerRef">
|
||||
<div class="table-toolbar sticky top-0 z-10 mb-2 shrink-0 bg-white">
|
||||
<div class="table-toolbar sticky top-0 z-10 mb-2 shrink-0">
|
||||
<div class="flex flex-col gap-2 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div
|
||||
v-if="showOptionHeader"
|
||||
@@ -707,12 +707,16 @@ defineExpose({
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.table-toolbar-extra:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-table-wrapper {
|
||||
border: 1px solid #eee;
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: var(--border-radius-base);
|
||||
}
|
||||
.ant-spin-container,
|
||||
|
||||
@@ -673,7 +673,7 @@ defineExpose({
|
||||
overflow-y: auto;
|
||||
}
|
||||
.ant-table-wrapper {
|
||||
border: 1px solid #eee;
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: var(--border-radius-base);
|
||||
}
|
||||
.ant-spin-container,
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<AMenuItemGroup title="پوسته">
|
||||
<AMenuItem
|
||||
v-for="opt in themeOptions"
|
||||
:key="opt.value"
|
||||
@click="darkmode.setSchema(opt.value)"
|
||||
>
|
||||
<div class="theme-menu-item" :class="{ 'theme-menu-item--active': colorSchema === opt.value }">
|
||||
<div class="theme-menu-item__main">
|
||||
<Icon :icon="opt.icon" size="18" />
|
||||
<span>{{ opt.label }}</span>
|
||||
</div>
|
||||
<Icon
|
||||
v-if="colorSchema === opt.value"
|
||||
icon="vuesax-linear:tick-circle"
|
||||
size="16"
|
||||
class="theme-menu-item__check"
|
||||
/>
|
||||
</div>
|
||||
</AMenuItem>
|
||||
<AMenuItem v-if="colorSchema === 'auto'" disabled class="theme-menu-hint-item">
|
||||
<span class="theme-menu-hint">وضعیت فعلی: {{ isDark ? 'تاریک' : 'روشن' }}</span>
|
||||
</AMenuItem>
|
||||
</AMenuItemGroup>
|
||||
<AMenuDivider />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { useAdminTheme } from '@core/composable/useAdminTheme'
|
||||
import type { DarkModeSchema } from '@core/stores/darkmode'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const { isDark, darkmode } = useAdminTheme()
|
||||
const { colorSchema } = storeToRefs(darkmode)
|
||||
|
||||
const themeOptions: Array<{ value: DarkModeSchema; label: string; icon: string }> = [
|
||||
{ value: 'light', label: 'روشن', icon: 'vuesax-linear:sun-1' },
|
||||
{ value: 'dark', label: 'تاریک', icon: 'vuesax-linear:moon' },
|
||||
{ value: 'auto', label: 'سیستم', icon: 'vuesax-linear:monitor' }
|
||||
]
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.theme-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
min-width: 10rem;
|
||||
}
|
||||
|
||||
.theme-menu-item__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.theme-menu-item--active {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.theme-menu-item__check {
|
||||
color: var(--admin-gold-light, #d4af37);
|
||||
}
|
||||
|
||||
.theme-menu-hint-item {
|
||||
height: auto !important;
|
||||
line-height: 1.3 !important;
|
||||
cursor: default !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.theme-menu-hint {
|
||||
font-size: 11px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
html.is-dark .theme-menu-hint {
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<div class="theme-toggle">
|
||||
<span v-if="showLabel" class="theme-toggle__label">پوسته</span>
|
||||
<APopover
|
||||
v-model:open="open"
|
||||
trigger="click"
|
||||
placement="bottomLeft"
|
||||
overlay-class-name="theme-toggle-popover"
|
||||
>
|
||||
<template #content>
|
||||
<div class="theme-toggle__menu">
|
||||
<button
|
||||
v-for="opt in themeOptions"
|
||||
:key="opt.value"
|
||||
type="button"
|
||||
class="theme-toggle__option"
|
||||
:class="{ 'theme-toggle__option--active': colorSchema === opt.value }"
|
||||
@click="selectSchema(opt.value)"
|
||||
>
|
||||
<BaseIcon :icon="opt.icon" size="18" />
|
||||
<span>{{ opt.label }}</span>
|
||||
</button>
|
||||
<div v-if="colorSchema === 'auto'" class="theme-toggle__hint">
|
||||
وضعیت فعلی: {{ isDark ? 'تاریک' : 'روشن' }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<BaseButton
|
||||
type="text"
|
||||
:icon="triggerIcon"
|
||||
icon-size="20"
|
||||
aria-label="انتخاب پوسته"
|
||||
/>
|
||||
</APopover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BaseIcon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { useAdminTheme } from '@core/composable/useAdminTheme'
|
||||
import type { DarkModeSchema } from '@core/stores/darkmode'
|
||||
import { computed, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
showLabel?: boolean
|
||||
}>(),
|
||||
{
|
||||
showLabel: true
|
||||
}
|
||||
)
|
||||
|
||||
const open = ref(false)
|
||||
const { isDark, darkmode } = useAdminTheme()
|
||||
const { colorSchema } = storeToRefs(darkmode)
|
||||
|
||||
const themeOptions: Array<{ value: DarkModeSchema; label: string; icon: string }> = [
|
||||
{ value: 'light', label: 'روشن', icon: 'vuesax-linear:sun-1' },
|
||||
{ value: 'dark', label: 'تاریک', icon: 'vuesax-linear:moon' },
|
||||
{ value: 'auto', label: 'سیستم', icon: 'vuesax-linear:monitor' }
|
||||
]
|
||||
|
||||
const triggerIcon = computed(() => {
|
||||
if (colorSchema.value === 'dark') return 'vuesax-linear:moon'
|
||||
if (colorSchema.value === 'light') return 'vuesax-linear:sun-1'
|
||||
return 'vuesax-linear:monitor'
|
||||
})
|
||||
|
||||
function selectSchema(schema: DarkModeSchema) {
|
||||
darkmode.setSchema(schema)
|
||||
open.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.theme-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.theme-toggle__label {
|
||||
font-size: 13px;
|
||||
color: var(--admin-text-secondary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.theme-toggle__menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 9rem;
|
||||
}
|
||||
|
||||
.theme-toggle__option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--admin-text);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--admin-bg-hover);
|
||||
color: var(--admin-gold-light);
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: var(--admin-menu-selected);
|
||||
color: var(--admin-menu-selected-text);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-toggle__hint {
|
||||
margin-top: 4px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--admin-border);
|
||||
font-size: 11px;
|
||||
color: var(--admin-text-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.theme-toggle-popover {
|
||||
.ant-popover-inner {
|
||||
background: var(--admin-bg-elevated);
|
||||
border: 1px solid var(--admin-border-strong);
|
||||
}
|
||||
|
||||
.ant-popover-inner-content {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.ant-popover-arrow-content::before {
|
||||
background: var(--admin-bg-elevated);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user