init
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import {
|
||||
DialogContent,
|
||||
type DialogContentEmits,
|
||||
type DialogContentProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _class, ...delegated } = props
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogContent
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'ui-dialog-content fixed left-1/2 top-1/2 z-50 grid w-[min(92vw,620px)] -translate-x-1/2 -translate-y-1/2 outline-none data-[state=closed]:animate-[dialog-out_120ms_ease-in] data-[state=open]:animate-[dialog-in_160ms_ease-out]',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</DialogContent>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ui-dialog-content {
|
||||
overflow: hidden;
|
||||
border: 1px solid #6c5a37;
|
||||
border-radius: 0;
|
||||
background: #050606;
|
||||
box-shadow:
|
||||
0 18px 38px rgb(0 0 0 / 80%),
|
||||
inset 0 0 0 1px #0e1418;
|
||||
color: var(--ui-surface-foreground);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { DialogDescription, type DialogDescriptionProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes['class'] }>()
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _class, ...delegated } = props
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogDescription
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('text-xs text-muted-foreground', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DialogDescription>
|
||||
</template>
|
||||
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div class="ui-dialog-footer">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ui-dialog-footer {
|
||||
display: flex;
|
||||
min-height: 38px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 4px 8px;
|
||||
border-top: 1px solid #332c22;
|
||||
background: linear-gradient(#20201d, #11110f);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,140 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { DialogClose } from 'reka-ui'
|
||||
import closeIcon from '../../../../../design/src-web/dialog/img/sprite_interface.NPK/windowcommon_2x.img/6.PNG'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{ closable?: boolean; closeLabel?: string; focused?: boolean }>(),
|
||||
{ closable: true, closeLabel: '关闭窗口', focused: true },
|
||||
)
|
||||
|
||||
const effectModules = import.meta.glob<string>(
|
||||
'../../../../../design/src-web/dialog/img/sprite_interface.NPK/windowheadereffect.img/*.PNG',
|
||||
{ eager: true, import: 'default', query: '?url' },
|
||||
)
|
||||
const effectFrames = Object.entries(effectModules)
|
||||
.sort(([a], [b]) => Number(a.match(/(\d+)\.PNG$/)?.[1]) - Number(b.match(/(\d+)\.PNG$/)?.[1]))
|
||||
.map(([, url]) => url)
|
||||
const frameIndex = ref(0)
|
||||
let timer: ReturnType<typeof setInterval> | undefined
|
||||
|
||||
onMounted(() => {
|
||||
timer = setInterval(() => {
|
||||
if (props.focused) frameIndex.value = (frameIndex.value + 1) % effectFrames.length
|
||||
}, 66)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => clearInterval(timer))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ui-dialog-header" :class="{ 'is-focused': focused, 'is-unfocused': !focused }">
|
||||
<img
|
||||
v-if="focused && effectFrames.length"
|
||||
class="ui-dialog-header__effect"
|
||||
:src="effectFrames[frameIndex]"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div class="ui-dialog-header__title"><slot /></div>
|
||||
<div class="ui-dialog-header__actions">
|
||||
<slot name="actions" />
|
||||
<DialogClose v-if="closable" class="ui-dialog-header__close" :aria-label="closeLabel">
|
||||
<img :src="closeIcon" alt="" />
|
||||
</DialogClose>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ui-dialog-header {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
height: 28px;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid #1d2735;
|
||||
background: linear-gradient(#303b50, #263247 56%, #1d2839);
|
||||
color: var(--ui-titlebar-foreground);
|
||||
text-shadow: 0 1px 1px #000;
|
||||
}
|
||||
|
||||
.ui-dialog-header.is-focused {
|
||||
background: linear-gradient(#6079a5, #435a80 55%, #30425f);
|
||||
color: #f4f6f2;
|
||||
}
|
||||
|
||||
.ui-dialog-header__effect {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 1100px;
|
||||
height: 100%;
|
||||
max-width: none;
|
||||
object-fit: fill;
|
||||
opacity: 0.72;
|
||||
mix-blend-mode: screen;
|
||||
filter: brightness(1.55) contrast(1.06);
|
||||
transform: translateX(-50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ui-dialog-header__title {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.ui-dialog-header__actions {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
grid-column: 3;
|
||||
justify-self: end;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ui-dialog-header__close {
|
||||
appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
outline: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ui-dialog-header__close img {
|
||||
width: 10px;
|
||||
height: 9px;
|
||||
filter: brightness(1);
|
||||
}
|
||||
|
||||
.ui-dialog-header__close:hover img,
|
||||
.ui-dialog-header__close:focus-visible img {
|
||||
filter: brightness(1.28);
|
||||
}
|
||||
.ui-dialog-header__close:focus-visible {
|
||||
outline: 1px solid rgb(225 204 135 / 72%);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
.ui-dialog-header__close:active img {
|
||||
filter: brightness(1.28);
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.ui-dialog-header__effect {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { DialogOverlay, type DialogOverlayProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _class, ...delegated } = props
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogOverlay
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'ui-dialog-overlay fixed inset-0 z-50 data-[state=closed]:animate-[fade-out_120ms_ease-in] data-[state=open]:animate-[fade-in_140ms_ease-out]',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ui-dialog-overlay {
|
||||
background: var(--ui-overlay);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { DialogTitle, type DialogTitleProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes['class'] }>()
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _class, ...delegated } = props
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogTitle
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('text-[13px] font-normal text-[#f0f2ef]', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DialogTitle>
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
export { DialogClose, DialogPortal, DialogRoot, DialogTrigger } from 'reka-ui'
|
||||
export { default as UiDialogContent } from './UiDialogContent.vue'
|
||||
export { default as UiDialogDescription } from './UiDialogDescription.vue'
|
||||
export { default as UiDialogFooter } from './UiDialogFooter.vue'
|
||||
export { default as UiDialogHeader } from './UiDialogHeader.vue'
|
||||
export { default as UiDialogOverlay } from './UiDialogOverlay.vue'
|
||||
export { default as UiDialogTitle } from './UiDialogTitle.vue'
|
||||
Reference in New Issue
Block a user