Skip to content

Commit

Permalink
feat: add dialog component with shortcuts and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mnenie committed Dec 14, 2024
1 parent 0c6d857 commit a95ef01
Show file tree
Hide file tree
Showing 23 changed files with 415 additions and 3 deletions.
1 change: 1 addition & 0 deletions core/client/.storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { themes } from '@storybook/theming'
import { useColorMode } from '@vueuse/core'

import 'virtual:uno.css'
import '@unocss/reset/tailwind-compat.css'
import '@/app/styles/primary/index.css'

const preview: Preview = {
Expand Down
2 changes: 1 addition & 1 deletion core/client/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"framework": "vite",
"aliases": {
"components": "@/shared/ui",
"components": "@/shared",
"utils": "@/shared/lib/shadcn/utils"
}
}
2 changes: 1 addition & 1 deletion core/client/src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { pinia, router } from './providers'

import './styles/primary/index.css'
import 'virtual:uno.css'
import '@unocss/reset/tailwind.css'
import '@unocss/reset/tailwind-compat.css'
import 'floating-vue/dist/style.css'
import 'vue-data-ui/style.css'

Expand Down
Empty file.
14 changes: 14 additions & 0 deletions core/client/src/features/share/ui/ShareLink.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script setup lang="ts">
import { ShimmerButton } from '@/shared/ui'
</script>

<template>
<ShimmerButton shimmer-size="2px">
<div i-hugeicons-user-multiple-02 class="text-neutral-900 dark:text-neutral-100 text-sm" />
<span
class="whitespace-pre-wrap text-center text-neutral-800 dark:text-neutral-100 text-sm fw500"
>
{{ $t('header.share') }}
</span>
</ShimmerButton>
</template>
20 changes: 20 additions & 0 deletions core/client/src/shared/ui/__docs__/Dialog.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{/* Dialog.mdx */}

import { Canvas, Story, Meta, Markdown } from '@storybook/blocks';
import * as DialogStories from '../dialog/UiDialog.stories';

<Meta of={DialogStories} name="Docs" />

# UiDialog

A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.

### Main Components:

`UiDialog`, `UiDialogTrigger`, `UiDialogContent`

<Canvas of={DialogStories.DefaultDialog} />

### Props

refs: https://www.radix-vue.com/components/dialog
19 changes: 19 additions & 0 deletions core/client/src/shared/ui/_shortcuts/dialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
type DialogPrefix = 'dialog'

export const staticDialog: Record<`${DialogPrefix}-${string}` | DialogPrefix, string> = {
'dialog': '',
'dialog-overlay': 'fixed inset-0 z-50 bg-black/30',
'dialog-content': 'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border border-neutral-200 bg-white p-6 shadow-lg duration-200 sm:rounded-lg dark:(border-neutral-800 bg-neutral-800)',
'dialog-close': 'absolute right-4 top-4 w-4 h-4 flex items-center justify-center rounded-sm opacity-70 ring-offset-0 transition-opacity hover:opacity-100 focus:outline-none focus:ring-0 disabled:pointer-events-none dark:(ring-offset-0 focus:ring-0)',
'dialog-title': 'text-lg font-semibold leading-none tracking-tight',
'dialog-description': 'text-sm text-neutral-500 dark:text-neutral-400',
'dialog-footer': 'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2',
'dialog-header': 'flex flex-col gap-y-1.5 text-center sm:text-left',
'dialog-scroll-overlay': 'dialog-overlay grid place-items-center overflow-y-auto',
'dialog-scroll-content': 'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-neutral-200 bg-white p-6 shadow-lg duration-200 sm:rounded-lg md:w-full dark:(border-neutral-800 bg-neutral-800)',
'dialog-scroll-close': 'fixed inset-0 z-50 cursor-pointer opacity-0',
}

export const dialog = [
staticDialog,
]
2 changes: 2 additions & 0 deletions core/client/src/shared/ui/_shortcuts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Preset, StaticShortcutMap } from '@unocss/core'
import { alert } from './alert'
import { badge } from './badge'
import { btn } from './button'
import { dialog } from './dialog'
import { dropdownMenu } from './dropdown-menu'
import { input } from './input'
import { select } from './select'
Expand All @@ -13,4 +14,5 @@ export const shortcuts = [
...alert,
...dropdownMenu,
...select,
...dialog,
] as Exclude<Preset['shortcuts'], undefined | StaticShortcutMap>
27 changes: 27 additions & 0 deletions core/client/src/shared/ui/dialog/UIDialogDescription.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script setup lang="ts">
import { cn } from '@/shared/lib/shadcn/utils'
import { DialogDescription, type DialogDescriptionProps, useForwardProps } from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
<DialogDescription
v-bind="forwardedProps"
:class="cn(
'dialog-description',
props.class,
)"
>
<slot />
</DialogDescription>
</template>
36 changes: 36 additions & 0 deletions core/client/src/shared/ui/dialog/UiDialog.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Meta, StoryFn } from '@storybook/vue3'
import { UiButton } from '../button'
import {
UiDialog,
UiDialogContent,
UiDialogTrigger,
} from './index'

export default {
title: 'UiDialog',
component: UiDialog,
} as Meta<typeof UiDialog>

const Template: StoryFn<typeof UiDialog> = args => ({
components: {
UiDialog,
UiDialogTrigger,
UiDialogContent,
UiButton,
},
setup() {
return { args }
},
template:
`<UiDialog>
<UiDialogTrigger as-child>
<UiButton variant="default">Open</UiButton>
</UiDialogTrigger>
<UiDialogContent>
hello from dialog
<template v-if="${'default' in args}" v-slot>${args.default}</template>
</UiDialogContent>
</UiDialog>`,
})

export const DefaultDialog: StoryFn<typeof UiDialog> = Template.bind({})
14 changes: 14 additions & 0 deletions core/client/src/shared/ui/dialog/UiDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script setup lang="ts">
import { DialogRoot, type DialogRootEmits, type DialogRootProps, useForwardPropsEmits } from 'radix-vue'
const props = defineProps<DialogRootProps>()
const emits = defineEmits<DialogRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>

<template>
<DialogRoot v-bind="forwarded">
<slot />
</DialogRoot>
</template>
11 changes: 11 additions & 0 deletions core/client/src/shared/ui/dialog/UiDialogClose.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup lang="ts">
import { DialogClose, type DialogCloseProps } from 'radix-vue'
const props = defineProps<DialogCloseProps>()
</script>

<template>
<DialogClose v-bind="props">
<slot />
</DialogClose>
</template>
56 changes: 56 additions & 0 deletions core/client/src/shared/ui/dialog/UiDialogContent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script setup lang="ts">
import { cn } from '@/shared/lib/shadcn/utils'
import {
DialogClose,
DialogContent,
type DialogContentEmits,
type DialogContentProps,
DialogOverlay,
DialogPortal,
useForwardPropsEmits,
} from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<DialogContentEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>

<template>
<DialogPortal>
<DialogOverlay
:class="cn(
'dialog-overlay',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
)"
/>
<DialogContent
v-bind="forwarded"
:class="
cn(
'dialog-content',
'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 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]',
props.class,
)"
>
<slot />

<DialogClose
:class="cn(
'dialog-close',
'data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-500 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-400',
)"
>
<div i-lucide-x class="w-4 h-4" />
<span class="sr-only">Close</span>
</DialogClose>
</DialogContent>
</DialogPortal>
</template>
19 changes: 19 additions & 0 deletions core/client/src/shared/ui/dialog/UiDialogFooter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/shared/lib/shadcn/utils'
const props = defineProps<{ class?: HTMLAttributes['class'] }>()
</script>

<template>
<div
:class="
cn(
'dialog-footer',
props.class,
)
"
>
<slot />
</div>
</template>
19 changes: 19 additions & 0 deletions core/client/src/shared/ui/dialog/UiDialogHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/shared/lib/shadcn/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>

<template>
<div
:class="cn(
'dialog-header',
props.class,
)"
>
<slot />
</div>
</template>
61 changes: 61 additions & 0 deletions core/client/src/shared/ui/dialog/UiDialogScrollContent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<script setup lang="ts">
import { cn } from '@/shared/lib/shadcn/utils'
import {
DialogClose,
DialogContent,
type DialogContentEmits,
type DialogContentProps,
DialogOverlay,
DialogPortal,
useForwardPropsEmits,
} from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<DialogContentEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>

<template>
<DialogPortal>
<DialogOverlay
:class="cn(
'dialog-scroll-overlay',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
)"
>
<DialogContent
:class="
cn(
'dialog-scroll-content',
props.class,
)
"
v-bind="forwarded"
@pointer-down-outside="(event) => {
const originalEvent = event.detail.originalEvent;
const target = originalEvent.target as HTMLElement;
if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) {
event.preventDefault();
}
}"
>
<slot />

<DialogClose
class="dialog-scroll-close"
>
<div i-lucide-x class="w-4 h-4" />
<span class="sr-only">Close</span>
</DialogClose>
</DialogContent>
</DialogOverlay>
</DialogPortal>
</template>
29 changes: 29 additions & 0 deletions core/client/src/shared/ui/dialog/UiDialogTitle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script setup lang="ts">
import { cn } from '@/shared/lib/shadcn/utils'
import { DialogTitle, type DialogTitleProps, useForwardProps } from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
<DialogTitle
v-bind="forwardedProps"
:class="
cn(
'dialog-title',
props.class,
)
"
>
<slot />
</DialogTitle>
</template>
11 changes: 11 additions & 0 deletions core/client/src/shared/ui/dialog/UiDialogTrigger.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup lang="ts">
import { DialogTrigger, type DialogTriggerProps } from 'radix-vue'
const props = defineProps<DialogTriggerProps>()
</script>

<template>
<DialogTrigger v-bind="props">
<slot />
</DialogTrigger>
</template>
Loading

0 comments on commit a95ef01

Please sign in to comment.