@proyecto-viviana/ui 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -0
- package/dist/autocomplete/index.d.ts +89 -0
- package/dist/autocomplete/index.d.ts.map +1 -0
- package/dist/breadcrumbs/index.d.ts +38 -0
- package/dist/breadcrumbs/index.d.ts.map +1 -0
- package/dist/button/Button.d.ts.map +1 -1
- package/dist/calendar/DateField.d.ts +47 -0
- package/dist/calendar/DateField.d.ts.map +1 -0
- package/dist/calendar/DatePicker.d.ts +48 -0
- package/dist/calendar/DatePicker.d.ts.map +1 -0
- package/dist/calendar/RangeCalendar.d.ts +42 -0
- package/dist/calendar/RangeCalendar.d.ts.map +1 -0
- package/dist/calendar/TimeField.d.ts +44 -0
- package/dist/calendar/TimeField.d.ts.map +1 -0
- package/dist/calendar/index.d.ts +50 -0
- package/dist/calendar/index.d.ts.map +1 -0
- package/dist/checkbox/index.d.ts.map +1 -1
- package/dist/color/index.d.ts +228 -0
- package/dist/color/index.d.ts.map +1 -0
- package/dist/combobox/index.d.ts +81 -0
- package/dist/combobox/index.d.ts.map +1 -0
- package/dist/components.css +116 -14
- package/dist/custom/chip/index.d.ts +7 -2
- package/dist/custom/chip/index.d.ts.map +1 -1
- package/dist/custom/event-card/index.d.ts +5 -1
- package/dist/custom/event-card/index.d.ts.map +1 -1
- package/dist/custom/header/index.d.ts +16 -0
- package/dist/custom/header/index.d.ts.map +1 -0
- package/dist/custom/logo/index.d.ts +2 -0
- package/dist/custom/logo/index.d.ts.map +1 -1
- package/dist/custom/page-layout/index.d.ts +2 -0
- package/dist/custom/page-layout/index.d.ts.map +1 -1
- package/dist/custom/profile-card/index.d.ts +5 -1
- package/dist/custom/profile-card/index.d.ts.map +1 -1
- package/dist/custom/timeline-item/index.d.ts +12 -2
- package/dist/custom/timeline-item/index.d.ts.map +1 -1
- package/dist/dialog/Dialog.d.ts +67 -0
- package/dist/dialog/Dialog.d.ts.map +1 -0
- package/dist/dialog/index.d.ts +2 -17
- package/dist/dialog/index.d.ts.map +1 -1
- package/dist/disclosure/index.d.ts +84 -0
- package/dist/disclosure/index.d.ts.map +1 -0
- package/dist/gridlist/index.d.ts +92 -0
- package/dist/gridlist/index.d.ts.map +1 -0
- package/dist/index.d.ts +58 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6984 -783
- package/dist/index.js.map +1 -1
- package/dist/index.ssr.js +5905 -571
- package/dist/index.ssr.js.map +1 -1
- package/dist/landmark/index.d.ts +83 -0
- package/dist/landmark/index.d.ts.map +1 -0
- package/dist/link/index.d.ts.map +1 -1
- package/dist/listbox/index.d.ts +47 -0
- package/dist/listbox/index.d.ts.map +1 -0
- package/dist/menu/index.d.ts +74 -0
- package/dist/menu/index.d.ts.map +1 -0
- package/dist/meter/index.d.ts +49 -0
- package/dist/meter/index.d.ts.map +1 -0
- package/dist/numberfield/index.d.ts +50 -0
- package/dist/numberfield/index.d.ts.map +1 -0
- package/dist/popover/index.d.ts +85 -0
- package/dist/popover/index.d.ts.map +1 -0
- package/dist/radio/index.d.ts +7 -4
- package/dist/radio/index.d.ts.map +1 -1
- package/dist/searchfield/index.d.ts +44 -0
- package/dist/searchfield/index.d.ts.map +1 -0
- package/dist/select/index.d.ts +72 -0
- package/dist/select/index.d.ts.map +1 -0
- package/dist/slider/index.d.ts +53 -0
- package/dist/slider/index.d.ts.map +1 -0
- package/dist/switch/ToggleSwitch.d.ts.map +1 -1
- package/dist/table/index.d.ts +140 -0
- package/dist/table/index.d.ts.map +1 -0
- package/dist/tabs/index.d.ts +56 -0
- package/dist/tabs/index.d.ts.map +1 -0
- package/dist/tag-group/index.d.ts +80 -0
- package/dist/tag-group/index.d.ts.map +1 -0
- package/dist/toast/index.d.ts +101 -0
- package/dist/toast/index.d.ts.map +1 -0
- package/dist/toolbar/index.d.ts +42 -0
- package/dist/toolbar/index.d.ts.map +1 -0
- package/dist/tooltip/index.d.ts +66 -5
- package/dist/tooltip/index.d.ts.map +1 -1
- package/dist/tree/index.d.ts +99 -0
- package/dist/tree/index.d.ts.map +1 -0
- package/package.json +66 -58
- package/src/autocomplete/index.tsx +313 -0
- package/src/breadcrumbs/index.tsx +207 -0
- package/src/button/Button.tsx +74 -75
- package/src/calendar/DateField.tsx +200 -0
- package/src/calendar/DatePicker.tsx +298 -0
- package/src/calendar/RangeCalendar.tsx +236 -0
- package/src/calendar/TimeField.tsx +196 -0
- package/src/calendar/index.tsx +223 -0
- package/src/checkbox/index.tsx +3 -4
- package/src/color/index.tsx +687 -0
- package/src/combobox/index.tsx +383 -0
- package/src/components.css +116 -14
- package/src/custom/chip/index.tsx +17 -3
- package/src/custom/event-card/index.tsx +8 -2
- package/src/custom/header/index.tsx +33 -0
- package/src/custom/logo/index.tsx +7 -3
- package/src/custom/page-layout/index.tsx +12 -3
- package/src/custom/profile-card/index.tsx +8 -2
- package/src/custom/timeline-item/index.tsx +28 -4
- package/src/dialog/Dialog.tsx +260 -0
- package/src/dialog/index.tsx +3 -69
- package/src/disclosure/index.tsx +307 -0
- package/src/gridlist/index.tsx +403 -0
- package/src/index.ts +219 -4
- package/src/landmark/index.tsx +231 -0
- package/src/link/index.tsx +1 -2
- package/src/listbox/index.tsx +231 -0
- package/src/menu/index.tsx +297 -0
- package/src/meter/index.tsx +163 -0
- package/src/numberfield/index.tsx +482 -0
- package/src/popover/index.tsx +260 -0
- package/src/radio/index.tsx +36 -82
- package/src/searchfield/index.tsx +453 -0
- package/src/select/index.tsx +349 -0
- package/src/slider/index.tsx +382 -0
- package/src/switch/ToggleSwitch.tsx +1 -2
- package/src/table/index.tsx +531 -0
- package/src/tabs/index.tsx +273 -0
- package/src/tag-group/index.tsx +240 -0
- package/src/toast/index.tsx +324 -0
- package/src/toolbar/index.tsx +108 -0
- package/src/tooltip/index.tsx +171 -5
- package/src/tree/index.tsx +494 -0
|
@@ -9,6 +9,8 @@ export interface LogoProps {
|
|
|
9
9
|
secondWord?: string;
|
|
10
10
|
/** Size variant of the logo */
|
|
11
11
|
size?: LogoSize;
|
|
12
|
+
/** Invert the styles (first word gets 3D effect, second word is muted) */
|
|
13
|
+
inverted?: boolean;
|
|
12
14
|
/** Additional CSS classes */
|
|
13
15
|
class?: string;
|
|
14
16
|
}
|
|
@@ -44,9 +46,11 @@ export function Logo(props: LogoProps): JSX.Element {
|
|
|
44
46
|
const secondWord = () => props.secondWord ?? "Viviana";
|
|
45
47
|
|
|
46
48
|
return (
|
|
47
|
-
<span class={`vui-logo ${sizeClass()} ${props.class ?? ""}`}>
|
|
48
|
-
<span class="vui-logo__first"
|
|
49
|
-
|
|
49
|
+
<span class={`vui-logo ${sizeClass()} ${props.inverted ? "vui-logo--inverted" : ""} ${props.class ?? ""}`}>
|
|
50
|
+
<span class="vui-logo__first" data-text={props.inverted ? firstWord() : undefined}>
|
|
51
|
+
{firstWord()}
|
|
52
|
+
</span>
|
|
53
|
+
<span class="vui-logo__second" data-text={props.inverted ? undefined : secondWord()}>
|
|
50
54
|
{secondWord()}
|
|
51
55
|
</span>
|
|
52
56
|
</span>
|
|
@@ -3,6 +3,8 @@ import { JSX, splitProps } from 'solid-js'
|
|
|
3
3
|
export interface PageLayoutProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
4
4
|
/** Content of the page */
|
|
5
5
|
children: JSX.Element
|
|
6
|
+
/** Add padding-top to account for fixed header (use for non-landing pages) */
|
|
7
|
+
withHeader?: boolean
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -10,11 +12,18 @@ export interface PageLayoutProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
|
10
12
|
* Use this as the root wrapper for all pages.
|
|
11
13
|
*/
|
|
12
14
|
export function PageLayout(props: PageLayoutProps) {
|
|
13
|
-
const [local, rest] = splitProps(props, ['
|
|
15
|
+
const [local, rest] = splitProps(props, ['class', 'withHeader'])
|
|
16
|
+
|
|
17
|
+
const classes = () => {
|
|
18
|
+
const base = 'vui-page'
|
|
19
|
+
const header = local.withHeader ? 'vui-page--with-header' : ''
|
|
20
|
+
const custom = local.class ?? ''
|
|
21
|
+
return [base, header, custom].filter(Boolean).join(' ')
|
|
22
|
+
}
|
|
14
23
|
|
|
15
24
|
return (
|
|
16
|
-
<div class={
|
|
17
|
-
{
|
|
25
|
+
<div class={classes()} {...rest}>
|
|
26
|
+
{props.children}
|
|
18
27
|
</div>
|
|
19
28
|
)
|
|
20
29
|
}
|
|
@@ -8,7 +8,11 @@ export interface ProfileCardProps {
|
|
|
8
8
|
bio?: string
|
|
9
9
|
followers?: number
|
|
10
10
|
following?: number
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Actions to display below the profile.
|
|
13
|
+
* Use a function returning JSX for SSR compatibility: `actions={() => <Button>...</Button>}`
|
|
14
|
+
*/
|
|
15
|
+
actions?: JSX.Element | (() => JSX.Element)
|
|
12
16
|
class?: string
|
|
13
17
|
}
|
|
14
18
|
|
|
@@ -51,7 +55,9 @@ export function ProfileCard(props: ProfileCardProps) {
|
|
|
51
55
|
</div>
|
|
52
56
|
</div>
|
|
53
57
|
<Show when={props.actions}>
|
|
54
|
-
<div class="mt-4 flex gap-2">
|
|
58
|
+
<div class="mt-4 flex gap-2">
|
|
59
|
+
{typeof props.actions === 'function' ? props.actions() : props.actions}
|
|
60
|
+
</div>
|
|
55
61
|
</Show>
|
|
56
62
|
</div>
|
|
57
63
|
)
|
|
@@ -6,7 +6,12 @@ export type TimelineEventType = 'follow' | 'like' | 'comment' | 'event' | 'custo
|
|
|
6
6
|
|
|
7
7
|
export interface TimelineItemProps {
|
|
8
8
|
type?: TimelineEventType
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Icon to display between the two avatars.
|
|
11
|
+
* Use a function returning JSX for SSR compatibility: `icon={() => <MyIcon />}`
|
|
12
|
+
* Or pass a simple string for text-based icons: `icon="👋"`
|
|
13
|
+
*/
|
|
14
|
+
icon?: string | (() => JSX.Element)
|
|
10
15
|
leftUser?: {
|
|
11
16
|
name: string
|
|
12
17
|
avatar?: string
|
|
@@ -15,7 +20,12 @@ export interface TimelineItemProps {
|
|
|
15
20
|
name: string
|
|
16
21
|
avatar?: string
|
|
17
22
|
}
|
|
18
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Custom message content.
|
|
25
|
+
* Use a function returning JSX for SSR compatibility: `message={() => <span>...</span>}`
|
|
26
|
+
* Or pass a simple string.
|
|
27
|
+
*/
|
|
28
|
+
message?: string | (() => JSX.Element)
|
|
19
29
|
class?: string
|
|
20
30
|
}
|
|
21
31
|
|
|
@@ -56,6 +66,20 @@ export function TimelineItem(props: TimelineItemProps) {
|
|
|
56
66
|
const leftName = () => props.leftUser?.name ?? ''
|
|
57
67
|
const rightName = () => props.rightUser?.name ?? ''
|
|
58
68
|
|
|
69
|
+
const renderIcon = () => {
|
|
70
|
+
const icon = props.icon
|
|
71
|
+
if (!icon) return null
|
|
72
|
+
if (typeof icon === 'string') return icon
|
|
73
|
+
return icon()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const renderMessage = () => {
|
|
77
|
+
const message = props.message
|
|
78
|
+
if (!message) return null
|
|
79
|
+
if (typeof message === 'string') return message
|
|
80
|
+
return message()
|
|
81
|
+
}
|
|
82
|
+
|
|
59
83
|
return (
|
|
60
84
|
<div class={`inline-flex w-auto flex-col gap-5 rounded-2xl border border-primary-700 bg-bg-200 p-5 hover:bg-bg-300 transition-colors ${props.class ?? ''}`}>
|
|
61
85
|
<div class="flex items-center justify-around gap-3">
|
|
@@ -63,7 +87,7 @@ export function TimelineItem(props: TimelineItemProps) {
|
|
|
63
87
|
<Avatar src={props.leftUser!.avatar} alt={props.leftUser!.name} />
|
|
64
88
|
</Show>
|
|
65
89
|
<Show when={props.icon}>
|
|
66
|
-
{
|
|
90
|
+
{renderIcon()}
|
|
67
91
|
</Show>
|
|
68
92
|
<Show when={props.rightUser}>
|
|
69
93
|
<Avatar src={props.rightUser!.avatar} alt={props.rightUser!.name} />
|
|
@@ -72,7 +96,7 @@ export function TimelineItem(props: TimelineItemProps) {
|
|
|
72
96
|
<div class="flex items-center justify-center gap-3 text-center">
|
|
73
97
|
<span class="font-light text-primary-300">
|
|
74
98
|
<Show when={props.message} fallback={eventMessages[type()](leftName(), rightName())}>
|
|
75
|
-
{
|
|
99
|
+
{renderMessage()}
|
|
76
100
|
</Show>
|
|
77
101
|
</span>
|
|
78
102
|
</div>
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialog component for proyecto-viviana-ui
|
|
3
|
+
*
|
|
4
|
+
* Styled dialog component with overlay and backdrop.
|
|
5
|
+
* Follows Spectrum 2 design patterns.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type JSX, splitProps, Show, createSignal, createContext, useContext, createUniqueId, onMount, onCleanup, createEffect } from 'solid-js'
|
|
9
|
+
import { Portal } from 'solid-js/web'
|
|
10
|
+
import { createInteractOutside } from '@proyecto-viviana/solidaria'
|
|
11
|
+
|
|
12
|
+
// ============================================
|
|
13
|
+
// TYPES
|
|
14
|
+
// ============================================
|
|
15
|
+
|
|
16
|
+
export type DialogSize = 'sm' | 'md' | 'lg' | 'fullscreen'
|
|
17
|
+
|
|
18
|
+
export interface DialogProps {
|
|
19
|
+
/** The size of the dialog. */
|
|
20
|
+
size?: DialogSize
|
|
21
|
+
/** Whether the dialog can be dismissed by clicking the X button. */
|
|
22
|
+
isDismissable?: boolean
|
|
23
|
+
/** Additional CSS class name. */
|
|
24
|
+
class?: string
|
|
25
|
+
/** The title of the dialog. */
|
|
26
|
+
title?: string
|
|
27
|
+
/** The children content. */
|
|
28
|
+
children: JSX.Element
|
|
29
|
+
/** Callback when dialog should close */
|
|
30
|
+
onClose?: () => void
|
|
31
|
+
/** ARIA role - defaults to 'dialog' */
|
|
32
|
+
role?: 'dialog' | 'alertdialog'
|
|
33
|
+
/** ARIA label */
|
|
34
|
+
'aria-label'?: string
|
|
35
|
+
/** ARIA labelledby */
|
|
36
|
+
'aria-labelledby'?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DialogTriggerProps {
|
|
40
|
+
/** Button to trigger the dialog. */
|
|
41
|
+
trigger: JSX.Element
|
|
42
|
+
/** The dialog content - receives close function. */
|
|
43
|
+
content: (close: () => void) => JSX.Element
|
|
44
|
+
/** Whether the dialog is controlled. */
|
|
45
|
+
isOpen?: boolean
|
|
46
|
+
/** Callback when open state changes. */
|
|
47
|
+
onOpenChange?: (isOpen: boolean) => void
|
|
48
|
+
/** Whether clicking outside the dialog closes it. Defaults to true. */
|
|
49
|
+
isDismissable?: boolean
|
|
50
|
+
/** Whether pressing Escape closes the dialog. Defaults to true. */
|
|
51
|
+
isKeyboardDismissDisabled?: boolean
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ============================================
|
|
55
|
+
// CONTEXT
|
|
56
|
+
// ============================================
|
|
57
|
+
|
|
58
|
+
interface DialogContextValue {
|
|
59
|
+
close: () => void
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const DialogContext = createContext<DialogContextValue | null>(null)
|
|
63
|
+
|
|
64
|
+
export function useDialogContext(): DialogContextValue | null {
|
|
65
|
+
return useContext(DialogContext)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================
|
|
69
|
+
// STYLES
|
|
70
|
+
// ============================================
|
|
71
|
+
|
|
72
|
+
const sizeStyles: Record<DialogSize, string> = {
|
|
73
|
+
sm: 'max-w-sm',
|
|
74
|
+
md: 'max-w-md',
|
|
75
|
+
lg: 'max-w-2xl',
|
|
76
|
+
fullscreen: 'max-w-full w-full h-full',
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ============================================
|
|
80
|
+
// DIALOG COMPONENT
|
|
81
|
+
// ============================================
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A dialog is an overlay shown above other content in an application.
|
|
85
|
+
*/
|
|
86
|
+
export function Dialog(props: DialogProps): JSX.Element {
|
|
87
|
+
const [local, rest] = splitProps(props, [
|
|
88
|
+
'size',
|
|
89
|
+
'isDismissable',
|
|
90
|
+
'class',
|
|
91
|
+
'title',
|
|
92
|
+
'onClose',
|
|
93
|
+
'role',
|
|
94
|
+
'aria-label',
|
|
95
|
+
'aria-labelledby',
|
|
96
|
+
])
|
|
97
|
+
|
|
98
|
+
const size = local.size ?? 'md'
|
|
99
|
+
const customClass = local.class ?? ''
|
|
100
|
+
const role = local.role ?? 'dialog'
|
|
101
|
+
|
|
102
|
+
// Generate a unique ID for the title if one is present
|
|
103
|
+
const titleId = createUniqueId()
|
|
104
|
+
const ariaLabelledBy = local['aria-labelledby'] ?? (local.title ? titleId : undefined)
|
|
105
|
+
|
|
106
|
+
const close = () => local.onClose?.()
|
|
107
|
+
|
|
108
|
+
const baseClass = 'bg-bg-300 rounded-lg shadow-xl border border-primary-700'
|
|
109
|
+
const sizeClass = sizeStyles[size]
|
|
110
|
+
const padding = 'p-6'
|
|
111
|
+
const className = [baseClass, sizeClass, padding, customClass].filter(Boolean).join(' ')
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<DialogContext.Provider value={{ close }}>
|
|
115
|
+
<div
|
|
116
|
+
role={role}
|
|
117
|
+
tabIndex={-1}
|
|
118
|
+
aria-label={local['aria-label']}
|
|
119
|
+
aria-labelledby={ariaLabelledBy}
|
|
120
|
+
class={className}
|
|
121
|
+
{...rest}
|
|
122
|
+
>
|
|
123
|
+
<Show when={local.title}>
|
|
124
|
+
<div class="flex items-center justify-between mb-4">
|
|
125
|
+
<h2 id={titleId} class="text-xl font-semibold text-primary-100">
|
|
126
|
+
{local.title}
|
|
127
|
+
</h2>
|
|
128
|
+
<Show when={local.isDismissable}>
|
|
129
|
+
<button
|
|
130
|
+
onClick={close}
|
|
131
|
+
class="text-primary-400 hover:text-primary-200 transition-colors"
|
|
132
|
+
aria-label="Close dialog"
|
|
133
|
+
>
|
|
134
|
+
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
135
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
136
|
+
</svg>
|
|
137
|
+
</button>
|
|
138
|
+
</Show>
|
|
139
|
+
</div>
|
|
140
|
+
</Show>
|
|
141
|
+
<div class="text-primary-200">
|
|
142
|
+
{props.children}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</DialogContext.Provider>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ============================================
|
|
150
|
+
// DIALOG TRIGGER COMPONENT
|
|
151
|
+
// ============================================
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* DialogTrigger wraps a trigger button and dialog content.
|
|
155
|
+
* Handles opening/closing the dialog with overlay and backdrop.
|
|
156
|
+
*/
|
|
157
|
+
export function DialogTrigger(props: DialogTriggerProps): JSX.Element {
|
|
158
|
+
const [isOpen, setIsOpen] = createSignal(props.isOpen ?? false)
|
|
159
|
+
let dialogRef: HTMLDivElement | undefined
|
|
160
|
+
|
|
161
|
+
const open = () => {
|
|
162
|
+
setIsOpen(true)
|
|
163
|
+
props.onOpenChange?.(true)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const close = () => {
|
|
167
|
+
setIsOpen(false)
|
|
168
|
+
props.onOpenChange?.(false)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Handle controlled state
|
|
172
|
+
const isOpenControlled = () => props.isOpen !== undefined ? props.isOpen : isOpen()
|
|
173
|
+
|
|
174
|
+
// Whether dismissable (defaults to true)
|
|
175
|
+
const isDismissable = () => props.isDismissable !== false
|
|
176
|
+
|
|
177
|
+
// Click outside to close
|
|
178
|
+
createInteractOutside({
|
|
179
|
+
ref: () => dialogRef ?? null,
|
|
180
|
+
onInteractOutside: () => {
|
|
181
|
+
if (isOpenControlled() && isDismissable()) {
|
|
182
|
+
close()
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
isDisabled: !isOpenControlled() || !isDismissable(),
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
// Escape key to close
|
|
189
|
+
onMount(() => {
|
|
190
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
191
|
+
if (e.key === 'Escape' && isOpenControlled() && !props.isKeyboardDismissDisabled) {
|
|
192
|
+
e.preventDefault()
|
|
193
|
+
e.stopPropagation()
|
|
194
|
+
close()
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
document.addEventListener('keydown', handleKeyDown)
|
|
198
|
+
onCleanup(() => document.removeEventListener('keydown', handleKeyDown))
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// Prevent background scroll when dialog is open
|
|
202
|
+
createEffect(() => {
|
|
203
|
+
if (!isOpenControlled()) return
|
|
204
|
+
|
|
205
|
+
const prevOverflow = document.documentElement.style.overflow
|
|
206
|
+
document.documentElement.style.overflow = 'hidden'
|
|
207
|
+
|
|
208
|
+
onCleanup(() => {
|
|
209
|
+
document.documentElement.style.overflow = prevOverflow
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<>
|
|
215
|
+
<div onClick={open}>
|
|
216
|
+
{props.trigger}
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<Show when={isOpenControlled()}>
|
|
220
|
+
<Portal>
|
|
221
|
+
{/* Backdrop */}
|
|
222
|
+
<div
|
|
223
|
+
class="fixed inset-0 bg-black/50 backdrop-blur-sm z-40"
|
|
224
|
+
aria-hidden="true"
|
|
225
|
+
/>
|
|
226
|
+
|
|
227
|
+
{/* Dialog container - pointer-events-none so clicks pass through to backdrop detection */}
|
|
228
|
+
<div class="fixed inset-0 z-50 flex items-center justify-center p-4 pointer-events-none">
|
|
229
|
+
{/* Dialog wrapper - pointer-events-auto to capture clicks on the dialog itself */}
|
|
230
|
+
<div ref={dialogRef} class="pointer-events-auto">
|
|
231
|
+
{props.content(close)}
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
</Portal>
|
|
235
|
+
</Show>
|
|
236
|
+
</>
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ============================================
|
|
241
|
+
// DIALOG FOOTER COMPONENT
|
|
242
|
+
// ============================================
|
|
243
|
+
|
|
244
|
+
export interface DialogFooterProps {
|
|
245
|
+
/** Footer content, typically buttons. */
|
|
246
|
+
children: JSX.Element
|
|
247
|
+
/** Additional CSS class. */
|
|
248
|
+
class?: string
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Footer section for dialog actions.
|
|
253
|
+
*/
|
|
254
|
+
export function DialogFooter(props: DialogFooterProps): JSX.Element {
|
|
255
|
+
return (
|
|
256
|
+
<div class={`flex gap-3 justify-end mt-6 pt-4 border-t border-primary-700 ${props.class ?? ''}`}>
|
|
257
|
+
{props.children}
|
|
258
|
+
</div>
|
|
259
|
+
)
|
|
260
|
+
}
|
package/src/dialog/index.tsx
CHANGED
|
@@ -1,69 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { Chip } from '../custom/chip'
|
|
5
|
-
|
|
6
|
-
export interface DialogProps {
|
|
7
|
-
title: string
|
|
8
|
-
subtitle?: string
|
|
9
|
-
children: JSX.Element
|
|
10
|
-
tags?: string[]
|
|
11
|
-
primaryAction?: {
|
|
12
|
-
label: string
|
|
13
|
-
onClick: () => void
|
|
14
|
-
}
|
|
15
|
-
secondaryAction?: {
|
|
16
|
-
label: string
|
|
17
|
-
onClick: () => void
|
|
18
|
-
}
|
|
19
|
-
class?: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function Dialog(props: DialogProps) {
|
|
23
|
-
return (
|
|
24
|
-
<div class={`flex flex-col w-full max-w-[540px] ${props.class ?? ''}`}>
|
|
25
|
-
<span class="relative left-8 top-4 bg-gradient-to-b from-primary-100 to-primary-500 bg-clip-text text-4xl font-extrabold text-transparent">
|
|
26
|
-
{props.title}
|
|
27
|
-
</span>
|
|
28
|
-
<div class="flex h-[50px] flex-col justify-end rounded-t-[26px] bg-primary-800">
|
|
29
|
-
<div class="flex h-[40px] flex-col justify-end rounded-t-[30px] bg-primary-600">
|
|
30
|
-
<div class="flex h-[20px] items-center justify-end rounded-t-[100px] bg-primary-700 pr-4">
|
|
31
|
-
<Show when={props.subtitle}>
|
|
32
|
-
<span class="text-base font-normal text-primary-300">
|
|
33
|
-
{props.subtitle}
|
|
34
|
-
</span>
|
|
35
|
-
</Show>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
<div class="mt-1 flex min-h-[200px] flex-col rounded-b-[26px] rounded-t-lg border-b border-accent-500 bg-bg-300">
|
|
40
|
-
<div class="flex flex-1 flex-col p-4">
|
|
41
|
-
<div class="text-lg text-gray-100">{props.children}</div>
|
|
42
|
-
<Show when={props.primaryAction || props.secondaryAction}>
|
|
43
|
-
<div class="flex flex-1 flex-row items-end justify-center gap-5 py-5">
|
|
44
|
-
<Show when={props.secondaryAction}>
|
|
45
|
-
<Button variant="primary" buttonStyle="outline" onPress={props.secondaryAction!.onClick}>
|
|
46
|
-
{props.secondaryAction!.label}
|
|
47
|
-
</Button>
|
|
48
|
-
</Show>
|
|
49
|
-
<Show when={props.primaryAction}>
|
|
50
|
-
<Button variant="primary" onPress={props.primaryAction!.onClick}>
|
|
51
|
-
{props.primaryAction!.label}
|
|
52
|
-
</Button>
|
|
53
|
-
</Show>
|
|
54
|
-
</div>
|
|
55
|
-
</Show>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
<Show when={props.tags && props.tags.length > 0}>
|
|
59
|
-
<div class="relative bottom-4 right-4 w-full">
|
|
60
|
-
<div class="flex flex-row justify-end gap-2 pr-4">
|
|
61
|
-
{props.tags!.map((tag) => (
|
|
62
|
-
<Chip variant="primary" text={tag} />
|
|
63
|
-
))}
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
</Show>
|
|
67
|
-
</div>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
1
|
+
// Export the new standard Dialog component
|
|
2
|
+
export { Dialog, DialogTrigger, DialogFooter } from './Dialog'
|
|
3
|
+
export type { DialogProps, DialogTriggerProps, DialogFooterProps, DialogSize } from './Dialog'
|