@luxfi/ui 5.5.3 → 5.6.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/dist/accordion.cjs +213 -0
- package/dist/accordion.js +186 -0
- package/dist/alert.cjs +553 -0
- package/dist/alert.js +531 -0
- package/dist/avatar.cjs +149 -0
- package/dist/avatar.js +125 -0
- package/dist/badge.cjs +611 -0
- package/dist/badge.js +589 -0
- package/dist/button.cjs +689 -0
- package/dist/button.js +664 -0
- package/dist/checkbox.cjs +265 -0
- package/dist/checkbox.js +241 -0
- package/dist/close-button.cjs +73 -0
- package/dist/close-button.js +51 -0
- package/dist/collapsible.cjs +702 -0
- package/dist/collapsible.js +679 -0
- package/dist/color-mode.cjs +96 -0
- package/dist/color-mode.js +72 -0
- package/dist/dialog.cjs +279 -0
- package/dist/dialog.js +246 -0
- package/dist/drawer.cjs +207 -0
- package/dist/drawer.js +175 -0
- package/dist/empty-state.cjs +93 -0
- package/dist/empty-state.js +71 -0
- package/dist/field.cjs +183 -0
- package/dist/field.js +160 -0
- package/dist/heading.cjs +46 -0
- package/dist/heading.js +40 -0
- package/dist/icon-button.cjs +491 -0
- package/dist/icon-button.js +470 -0
- package/dist/image.cjs +572 -0
- package/dist/image.js +551 -0
- package/dist/index.cjs +5779 -0
- package/dist/index.js +5619 -0
- package/dist/input-group.cjs +155 -0
- package/dist/input-group.js +133 -0
- package/dist/input.cjs +65 -0
- package/dist/input.js +59 -0
- package/dist/link.cjs +630 -0
- package/dist/link.js +606 -0
- package/dist/menu.cjs +305 -0
- package/dist/menu.js +269 -0
- package/dist/pin-input.cjs +182 -0
- package/dist/pin-input.js +160 -0
- package/dist/popover.cjs +327 -0
- package/dist/popover.js +294 -0
- package/dist/progress-circle.cjs +152 -0
- package/dist/progress-circle.js +128 -0
- package/dist/progress.cjs +117 -0
- package/dist/progress.js +94 -0
- package/dist/provider.cjs +62 -0
- package/dist/provider.js +40 -0
- package/dist/radio.cjs +177 -0
- package/dist/radio.js +153 -0
- package/dist/rating.cjs +80 -0
- package/dist/rating.js +58 -0
- package/dist/select.cjs +791 -0
- package/dist/select.js +757 -0
- package/dist/separator.cjs +57 -0
- package/dist/separator.js +51 -0
- package/dist/skeleton.cjs +370 -0
- package/dist/skeleton.js +346 -0
- package/dist/slider.cjs +138 -0
- package/dist/slider.js +115 -0
- package/dist/switch.cjs +163 -0
- package/dist/switch.js +140 -0
- package/dist/table.cjs +1044 -0
- package/dist/table.js +1013 -0
- package/dist/tabs.cjs +240 -0
- package/dist/tabs.js +213 -0
- package/dist/tag.cjs +651 -0
- package/dist/tag.js +628 -0
- package/dist/textarea.cjs +65 -0
- package/dist/textarea.js +59 -0
- package/dist/toaster.cjs +99 -0
- package/dist/toaster.js +96 -0
- package/dist/tooltip.cjs +171 -0
- package/dist/tooltip.js +148 -0
- package/dist/utils.cjs +11 -0
- package/dist/utils.js +9 -0
- package/package.json +270 -65
- package/src/accordion.tsx +285 -0
- package/src/alert.tsx +221 -0
- package/src/avatar.tsx +174 -0
- package/src/badge.tsx +158 -0
- package/src/button.tsx +411 -0
- package/src/checkbox.tsx +307 -0
- package/src/close-button.tsx +51 -0
- package/src/collapsible.tsx +126 -0
- package/src/color-mode.tsx +125 -0
- package/src/dialog.tsx +356 -0
- package/src/drawer.tsx +186 -0
- package/src/empty-state.tsx +97 -0
- package/src/field.tsx +202 -0
- package/src/heading.tsx +55 -0
- package/src/icon-button.tsx +192 -0
- package/src/image.tsx +280 -0
- package/src/index.ts +192 -0
- package/src/input-group.tsx +159 -0
- package/src/input.tsx +60 -0
- package/src/link.tsx +326 -0
- package/src/menu.tsx +471 -0
- package/src/pin-input.tsx +187 -0
- package/src/popover.tsx +400 -0
- package/src/progress-circle.tsx +180 -0
- package/src/progress.tsx +109 -0
- package/src/provider.tsx +12 -0
- package/src/radio.tsx +175 -0
- package/src/rating.tsx +79 -0
- package/src/select.tsx +696 -0
- package/src/separator.tsx +59 -0
- package/src/skeleton.tsx +302 -0
- package/src/slider.tsx +152 -0
- package/src/switch.tsx +158 -0
- package/src/table.tsx +621 -0
- package/src/tabs.tsx +354 -0
- package/src/tag.tsx +159 -0
- package/src/textarea.tsx +60 -0
- package/src/toaster.tsx +117 -0
- package/src/tokens.css +438 -0
- package/src/tooltip.tsx +184 -0
- package/src/utils/cn.ts +7 -0
- package/src/utils.ts +6 -0
- package/tokens.css +438 -0
- package/commerce/ui/conf.ts +0 -13
- package/commerce/ui/context.tsx +0 -123
- package/commerce/ui/store.ts +0 -295
- package/components/access-code-input.tsx +0 -71
- package/components/analytics.tsx +0 -23
- package/components/auth/auth-listener.tsx +0 -29
- package/components/auth/auth-token/clear-auth-token.tsx +0 -12
- package/components/auth/auth-token/set-auth-token.tsx +0 -16
- package/components/auth/common-auth-domains.ts +0 -17
- package/components/auth/login-panel.tsx +0 -111
- package/components/auth/mobile-login-button.tsx +0 -107
- package/components/auth/signup-panel.tsx +0 -113
- package/components/back-button.tsx +0 -49
- package/components/chat-widget.tsx +0 -85
- package/components/commerce/bag-button.tsx +0 -98
- package/components/commerce/buy-button.tsx +0 -34
- package/components/commerce/checkout-button.tsx +0 -129
- package/components/commerce/checkout-panel/cart-accordian.tsx +0 -66
- package/components/commerce/checkout-panel/checkout-panel-props.ts +0 -10
- package/components/commerce/checkout-panel/desktop-bag-carousel.tsx +0 -36
- package/components/commerce/checkout-panel/desktop-cp.tsx +0 -83
- package/components/commerce/checkout-panel/index.tsx +0 -126
- package/components/commerce/checkout-panel/mobile-cp.tsx +0 -67
- package/components/commerce/checkout-panel/policy-links.tsx +0 -29
- package/components/commerce/checkout-panel/steps-indicator.tsx +0 -39
- package/components/commerce/checkout-panel/thank-you.tsx +0 -18
- package/components/commerce/desktop-bag-popup.tsx +0 -78
- package/components/commerce/drawer/index.tsx +0 -88
- package/components/commerce/drawer/micro.tsx +0 -145
- package/components/commerce/drawer/shell.tsx +0 -85
- package/components/contact-dialog/contact-form.tsx +0 -116
- package/components/contact-dialog/disclaimer.tsx +0 -13
- package/components/contact-dialog/index.tsx +0 -64
- package/components/copyright.tsx +0 -21
- package/components/drawer-margin.tsx +0 -28
- package/components/footer.tsx +0 -78
- package/components/header/desktop-nav-menu.tsx +0 -204
- package/components/header/desktop.tsx +0 -65
- package/components/header/index.tsx +0 -50
- package/components/header/mobile-bag-drawer.tsx +0 -51
- package/components/header/mobile-menu-toggle-button.tsx +0 -35
- package/components/header/mobile-nav-menu-ai.tsx +0 -51
- package/components/header/mobile-nav-menu-item.tsx +0 -47
- package/components/header/mobile-nav-menu.tsx +0 -102
- package/components/header/mobile.tsx +0 -170
- package/components/header/theme-toggle.tsx +0 -26
- package/components/icons/avatar.tsx +0 -11
- package/components/icons/bag-icon.tsx +0 -10
- package/components/icons/index.ts +0 -6
- package/components/icons/left-arrow.tsx +0 -11
- package/components/icons/lux-logo.tsx +0 -10
- package/components/icons/right-arrow.tsx +0 -10
- package/components/icons/social-icon.tsx +0 -35
- package/components/icons/social-svg.css +0 -3
- package/components/index.ts +0 -26
- package/components/logo.tsx +0 -92
- package/components/main.tsx +0 -27
- package/components/mini-chart/index.tsx +0 -8
- package/components/mini-chart/mini-chart-props.ts +0 -44
- package/components/mini-chart/mini-chart.tsx +0 -85
- package/components/mini-chart/wrapper.tsx +0 -23
- package/components/not-found/index.tsx +0 -28
- package/components/not-found/not-found-content.mdx +0 -5
- package/components/tooltip.tsx +0 -31
- package/environment.d.ts +0 -6
- package/next/analytics/fpixel.ts +0 -16
- package/next/analytics/google-analytics.ts +0 -14
- package/next/analytics/index.ts +0 -3
- package/next/analytics/pixel-analytics.tsx +0 -55
- package/next/font/get-app-router-font-classes.ts +0 -17
- package/next/font/load-and-return-lux-next-fonts-on-import.ts +0 -68
- package/next/font/local/Druk-Wide-Bold.ttf +0 -0
- package/next/font/local/Druk-Wide-Medium.ttf +0 -0
- package/next/font/local/InterVariable-Italic.ttf +0 -0
- package/next/font/local/InterVariable-Italic.woff2 +0 -0
- package/next/font/local/InterVariable.ttf +0 -0
- package/next/font/local/InterVariable.woff2 +0 -0
- package/next/font/next-font-desc.ts +0 -28
- package/next/font/pages-router-font-vars.tsx +0 -18
- package/next/head-metadata/from-next/metadata-types.ts +0 -158
- package/next/head-metadata/from-next/opengraph-types.ts +0 -267
- package/next/head-metadata/from-next/twitter-types.ts +0 -92
- package/next/head-metadata/index.tsx +0 -208
- package/next/index.ts +0 -2
- package/next/middleware/determine-device-mw.ts +0 -29
- package/root-layout/WHY_THIS_IS_SEPARATE.txt +0 -2
- package/root-layout/index.tsx +0 -112
- package/site-def/footer/community.tsx +0 -61
- package/site-def/footer/company.ts +0 -37
- package/site-def/footer/ecosystem.ts +0 -37
- package/site-def/footer/index.tsx +0 -26
- package/site-def/footer/legal.ts +0 -28
- package/site-def/footer/network.ts +0 -45
- package/site-def/footer/svg/warpcast-logo.svg +0 -12
- package/site-def/index.ts +0 -4
- package/site-def/main-nav.tsx +0 -460
- package/style/cart-animation.css +0 -29
- package/style/checkout-animation.css +0 -23
- package/style/drawer-handle-overrides.css +0 -160
- package/style/fonts/COPY_TO_PUBLIC_FOR_NON_NEXT.txt +0 -0
- package/style/fonts/Druk-Wide-Bold.ttf +0 -0
- package/style/fonts/Druk-Wide-Medium.ttf +0 -0
- package/style/fonts/InterVariable-Italic.ttf +0 -0
- package/style/fonts/InterVariable-Italic.woff2 +0 -0
- package/style/fonts/InterVariable.ttf +0 -0
- package/style/fonts/InterVariable.woff2 +0 -0
- package/style/lux-colors.css +0 -85
- package/style/lux-fonts.css +0 -30
- package/style/lux-global-non-next.css +0 -52
- package/style/lux-global.css +0 -51
- package/tailwind/fontFamily.tailwind.lux.ts +0 -18
- package/tailwind/index.ts +0 -2
- package/tailwind/lux-tw-fonts.ts +0 -40
- package/tailwind/tailwind.config.lux-preset.ts +0 -10
- package/tsconfig.json +0 -15
- package/types/chatbot-config.ts +0 -7
- package/types/chatbot-suggested-question.ts +0 -7
- package/types/contact-info.ts +0 -11
- package/types/index.ts +0 -4
- package/types/site-def.ts +0 -46
package/src/dialog.tsx
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import * as RadixDialog from '@radix-ui/react-dialog';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { cn } from './utils';
|
|
5
|
+
|
|
6
|
+
import { CloseButton } from './close-button';
|
|
7
|
+
|
|
8
|
+
// Inline back button for dialog header
|
|
9
|
+
function BackToButton({ onClick }: { readonly onClick?: () => void }) {
|
|
10
|
+
return (
|
|
11
|
+
<button
|
|
12
|
+
type="button"
|
|
13
|
+
onClick={ onClick }
|
|
14
|
+
className="inline-flex items-center justify-center size-8 rounded-md bg-transparent text-current hover:bg-[var(--color-button-subtle-bg)] cursor-pointer"
|
|
15
|
+
aria-label="Go back"
|
|
16
|
+
>
|
|
17
|
+
<svg className="size-5" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
18
|
+
<path d="M12.5 15L7.5 10L12.5 5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
19
|
+
</svg>
|
|
20
|
+
</button>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Size / context
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
type DialogSize = 'sm' | 'md' | 'full' | 'cover';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Responsive size expressed as a plain value or a breakpoint map.
|
|
32
|
+
* Mirrors the Chakra `size={{ lgDown: 'full', lg: 'md' }}` pattern.
|
|
33
|
+
* Responsive behavior is implemented via Tailwind responsive variants at
|
|
34
|
+
* the CSS-class level (see `sizeClasses` helper below).
|
|
35
|
+
*/
|
|
36
|
+
type ResponsiveSize = DialogSize | { base?: DialogSize; lgDown?: DialogSize; lg?: DialogSize };
|
|
37
|
+
|
|
38
|
+
interface DialogContextValue {
|
|
39
|
+
size: ResponsiveSize;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const DialogContext = React.createContext<DialogContextValue>({ size: 'md' });
|
|
43
|
+
|
|
44
|
+
function useDialogContext(): DialogContextValue {
|
|
45
|
+
return React.useContext(DialogContext);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Tailwind class helpers
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
const CONTENT_SIZE_MAP: Record<DialogSize, string> = {
|
|
53
|
+
sm: 'max-w-[400px]',
|
|
54
|
+
md: 'max-w-[728px]',
|
|
55
|
+
full: 'max-w-[100vw] min-h-dvh rounded-none [--dialog-margin:0]',
|
|
56
|
+
cover: 'w-full h-full [--dialog-margin:0]',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolve a `ResponsiveSize` into a Tailwind class string.
|
|
61
|
+
*
|
|
62
|
+
* For a plain value we return the matching static class. For a breakpoint
|
|
63
|
+
* object we compose mobile-first responsive classes. The Tailwind config in
|
|
64
|
+
* this project defines `lg: 1000px` as breakpoint — that aligns with Chakra's
|
|
65
|
+
* `lgDown` / `lg` pattern.
|
|
66
|
+
*/
|
|
67
|
+
function sizeClasses(size: ResponsiveSize): string {
|
|
68
|
+
if (typeof size === 'string') {
|
|
69
|
+
return CONTENT_SIZE_MAP[size];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const parts: Array<string> = [];
|
|
73
|
+
|
|
74
|
+
// "base" or "lgDown" both apply below lg
|
|
75
|
+
const small = size.base ?? size.lgDown;
|
|
76
|
+
if (small) {
|
|
77
|
+
parts.push(CONTENT_SIZE_MAP[small]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (size.lg) {
|
|
81
|
+
// Prefix each class with `lg:` for the large breakpoint
|
|
82
|
+
const lgClasses = CONTENT_SIZE_MAP[size.lg]
|
|
83
|
+
.split(' ')
|
|
84
|
+
.map((c) => `lg:${ c }`)
|
|
85
|
+
.join(' ');
|
|
86
|
+
parts.push(lgClasses);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return parts.join(' ');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// DialogRoot
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
export interface DialogRootProps {
|
|
97
|
+
children?: React.ReactNode;
|
|
98
|
+
open?: boolean;
|
|
99
|
+
defaultOpen?: boolean;
|
|
100
|
+
onOpenChange?: (details: { open: boolean }) => void;
|
|
101
|
+
size?: ResponsiveSize;
|
|
102
|
+
|
|
103
|
+
/** Accepted for API compat but not implemented — animations are CSS-only. */
|
|
104
|
+
motionPreset?: string;
|
|
105
|
+
modal?: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const DialogRoot: React.FC<DialogRootProps> = ({
|
|
109
|
+
children,
|
|
110
|
+
open,
|
|
111
|
+
defaultOpen,
|
|
112
|
+
onOpenChange,
|
|
113
|
+
size = 'md',
|
|
114
|
+
modal = true,
|
|
115
|
+
// motionPreset is intentionally unused — kept for API compat
|
|
116
|
+
}) => {
|
|
117
|
+
const handleOpenChange = React.useCallback(
|
|
118
|
+
(nextOpen: boolean) => {
|
|
119
|
+
onOpenChange?.({ open: nextOpen });
|
|
120
|
+
},
|
|
121
|
+
[ onOpenChange ],
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const ctx = React.useMemo<DialogContextValue>(() => ({ size }), [ size ]);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<DialogContext.Provider value={ ctx }>
|
|
128
|
+
<RadixDialog.Root
|
|
129
|
+
open={ open }
|
|
130
|
+
defaultOpen={ defaultOpen }
|
|
131
|
+
onOpenChange={ handleOpenChange }
|
|
132
|
+
modal={ modal }
|
|
133
|
+
>
|
|
134
|
+
{ children }
|
|
135
|
+
</RadixDialog.Root>
|
|
136
|
+
</DialogContext.Provider>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// DialogContent
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
export interface DialogContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
145
|
+
portalled?: boolean;
|
|
146
|
+
portalRef?: React.RefObject<HTMLElement>;
|
|
147
|
+
backdrop?: boolean;
|
|
148
|
+
// Legacy Chakra style-prop shims
|
|
149
|
+
paddingTop?: number | string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export const DialogContent = React.forwardRef<
|
|
153
|
+
HTMLDivElement,
|
|
154
|
+
DialogContentProps
|
|
155
|
+
>(function DialogContent(props, ref) {
|
|
156
|
+
const {
|
|
157
|
+
children,
|
|
158
|
+
portalled = true,
|
|
159
|
+
portalRef,
|
|
160
|
+
backdrop = true,
|
|
161
|
+
className,
|
|
162
|
+
paddingTop: _paddingTop,
|
|
163
|
+
style: styleProp,
|
|
164
|
+
...rest
|
|
165
|
+
} = props;
|
|
166
|
+
|
|
167
|
+
const contentInlineStyle: React.CSSProperties = {
|
|
168
|
+
...styleProp,
|
|
169
|
+
...(_paddingTop !== undefined ? { paddingTop: typeof _paddingTop === 'number' ? `${ _paddingTop * 4 }px` : _paddingTop } : {}),
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const { size } = useDialogContext();
|
|
173
|
+
|
|
174
|
+
let portalProps: RadixDialog.DialogPortalProps;
|
|
175
|
+
if (!portalled) {
|
|
176
|
+
portalProps = { container: undefined };
|
|
177
|
+
} else if (portalRef) {
|
|
178
|
+
portalProps = { container: portalRef.current };
|
|
179
|
+
} else {
|
|
180
|
+
portalProps = {};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// When portalled=false we skip the Radix Portal wrapper entirely
|
|
184
|
+
const Wrapper = portalled ? RadixDialog.Portal : React.Fragment;
|
|
185
|
+
const wrapperProps = portalled ? portalProps : {};
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<Wrapper { ...wrapperProps }>
|
|
189
|
+
{ backdrop && (
|
|
190
|
+
<RadixDialog.Overlay
|
|
191
|
+
className="fixed inset-0 z-[1400] bg-black/80"
|
|
192
|
+
/>
|
|
193
|
+
) }
|
|
194
|
+
{ /* Positioner — centers the content panel */ }
|
|
195
|
+
<div
|
|
196
|
+
className={ cn(
|
|
197
|
+
'fixed inset-0 z-[1400] flex w-screen h-dvh',
|
|
198
|
+
'items-start lg:items-center justify-center',
|
|
199
|
+
'overflow-hidden',
|
|
200
|
+
) }
|
|
201
|
+
>
|
|
202
|
+
<RadixDialog.Content
|
|
203
|
+
ref={ ref }
|
|
204
|
+
className={ cn(
|
|
205
|
+
// Base content styles
|
|
206
|
+
'relative flex flex-col w-full p-6 outline-none text-base',
|
|
207
|
+
'bg-dialog-bg text-dialog-fg shadow-lg rounded-xl',
|
|
208
|
+
'my-[var(--dialog-margin,var(--dialog-base-margin))]',
|
|
209
|
+
'mx-auto [--dialog-base-margin:4rem] lg:[--dialog-base-margin:auto]',
|
|
210
|
+
'max-h-[calc(100%-7.5rem)] overflow-hidden',
|
|
211
|
+
// Size variant
|
|
212
|
+
sizeClasses(size),
|
|
213
|
+
className,
|
|
214
|
+
) }
|
|
215
|
+
style={ Object.keys(contentInlineStyle).length > 0 ? contentInlineStyle : undefined }
|
|
216
|
+
{ ...rest }
|
|
217
|
+
>
|
|
218
|
+
{ children }
|
|
219
|
+
</RadixDialog.Content>
|
|
220
|
+
</div>
|
|
221
|
+
</Wrapper>
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// DialogCloseTrigger
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
export interface DialogCloseTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
|
|
230
|
+
|
|
231
|
+
export const DialogCloseTrigger = React.forwardRef<
|
|
232
|
+
HTMLButtonElement,
|
|
233
|
+
DialogCloseTriggerProps
|
|
234
|
+
>(function DialogCloseTrigger(props, ref) {
|
|
235
|
+
const { className, ...rest } = props;
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<RadixDialog.Close asChild>
|
|
239
|
+
<CloseButton ref={ ref } className={ className } { ...rest }>
|
|
240
|
+
{ props.children }
|
|
241
|
+
</CloseButton>
|
|
242
|
+
</RadixDialog.Close>
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
// DialogHeader
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
export interface DialogHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
251
|
+
startElement?: React.ReactNode;
|
|
252
|
+
onBackToClick?: () => void;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export const DialogHeader = React.forwardRef<
|
|
256
|
+
HTMLDivElement,
|
|
257
|
+
DialogHeaderProps
|
|
258
|
+
>(function DialogHeader(props, ref) {
|
|
259
|
+
const { startElement: startElementProp, onBackToClick, className, children, ...rest } = props;
|
|
260
|
+
|
|
261
|
+
const startElement =
|
|
262
|
+
startElementProp ?? (onBackToClick ? <BackToButton onClick={ onBackToClick }/> : undefined);
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<div
|
|
266
|
+
ref={ ref }
|
|
267
|
+
className={ cn(
|
|
268
|
+
'flex-none p-0 mb-2 flex items-center gap-x-2 min-h-[40px]',
|
|
269
|
+
className,
|
|
270
|
+
) }
|
|
271
|
+
{ ...rest }
|
|
272
|
+
>
|
|
273
|
+
{ startElement }
|
|
274
|
+
<RadixDialog.Title
|
|
275
|
+
className={ cn(
|
|
276
|
+
'text-base lg:text-lg font-medium',
|
|
277
|
+
'whitespace-nowrap overflow-hidden text-ellipsis',
|
|
278
|
+
) }
|
|
279
|
+
>
|
|
280
|
+
{ children }
|
|
281
|
+
</RadixDialog.Title>
|
|
282
|
+
<DialogCloseTrigger className="ml-auto"/>
|
|
283
|
+
</div>
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
// DialogBody
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
export interface DialogBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
292
|
+
// Legacy Chakra style-prop shims
|
|
293
|
+
pt?: number | string;
|
|
294
|
+
display?: string;
|
|
295
|
+
flexDir?: string;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export const DialogBody = React.forwardRef<
|
|
299
|
+
HTMLDivElement,
|
|
300
|
+
DialogBodyProps
|
|
301
|
+
>(function DialogBody({ className, pt, display, flexDir, style: styleProp, ...props }, ref) {
|
|
302
|
+
const bodyStyle: React.CSSProperties = {
|
|
303
|
+
...styleProp,
|
|
304
|
+
...(pt !== undefined ? { paddingTop: typeof pt === 'number' ? `${ pt * 4 }px` : pt } : {}),
|
|
305
|
+
...(display ? { display } : {}),
|
|
306
|
+
...(flexDir ? { flexDirection: flexDir as React.CSSProperties['flexDirection'] } : {}),
|
|
307
|
+
};
|
|
308
|
+
return (
|
|
309
|
+
<div
|
|
310
|
+
ref={ ref }
|
|
311
|
+
className={ cn('flex-1 p-0 overflow-auto', className) }
|
|
312
|
+
style={ Object.keys(bodyStyle).length > 0 ? bodyStyle : undefined }
|
|
313
|
+
{ ...props }
|
|
314
|
+
/>
|
|
315
|
+
);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
// DialogFooter
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
export interface DialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
323
|
+
|
|
324
|
+
export const DialogFooter = React.forwardRef<
|
|
325
|
+
HTMLDivElement,
|
|
326
|
+
DialogFooterProps
|
|
327
|
+
>(function DialogFooter({ className, ...props }, ref) {
|
|
328
|
+
return (
|
|
329
|
+
<div
|
|
330
|
+
ref={ ref }
|
|
331
|
+
className={ cn('flex items-center justify-start gap-6 p-0 mt-6', className) }
|
|
332
|
+
{ ...props }
|
|
333
|
+
/>
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
// Simple wrappers that re-export Radix primitives under the old names
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
|
|
341
|
+
export const DialogBackdrop = RadixDialog.Overlay;
|
|
342
|
+
|
|
343
|
+
export const DialogTitle = RadixDialog.Title;
|
|
344
|
+
|
|
345
|
+
export const DialogDescription = RadixDialog.Description;
|
|
346
|
+
|
|
347
|
+
export interface DialogTriggerProps extends React.ComponentPropsWithoutRef<typeof RadixDialog.Trigger> {}
|
|
348
|
+
|
|
349
|
+
export const DialogTrigger = RadixDialog.Trigger;
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* `DialogActionTrigger` renders its child and closes the dialog on click.
|
|
353
|
+
* This mirrors the Chakra `Dialog.ActionTrigger` behavior which wraps
|
|
354
|
+
* children in a close action.
|
|
355
|
+
*/
|
|
356
|
+
export const DialogActionTrigger = RadixDialog.Close;
|
package/src/drawer.tsx
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import * as RadixDialog from '@radix-ui/react-dialog';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { cn } from './utils';
|
|
5
|
+
|
|
6
|
+
import { CloseButton } from './close-button';
|
|
7
|
+
|
|
8
|
+
// ─── DrawerRoot ─────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
export interface DrawerRootProps {
|
|
11
|
+
children?: React.ReactNode;
|
|
12
|
+
open?: boolean;
|
|
13
|
+
defaultOpen?: boolean;
|
|
14
|
+
onOpenChange?: (details: { open: boolean }) => void;
|
|
15
|
+
placement?: 'left' | 'right' | 'top' | 'bottom';
|
|
16
|
+
initialFocusEl?: (() => HTMLElement | null) | React.RefObject<HTMLElement>;
|
|
17
|
+
lazyMount?: boolean;
|
|
18
|
+
unmountOnExit?: boolean;
|
|
19
|
+
modal?: boolean;
|
|
20
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const DrawerPlacementContext = React.createContext<string>('right');
|
|
24
|
+
const DrawerSizeContext = React.createContext<string>('md');
|
|
25
|
+
|
|
26
|
+
export const DrawerRoot = (props: DrawerRootProps) => {
|
|
27
|
+
const {
|
|
28
|
+
children,
|
|
29
|
+
open,
|
|
30
|
+
defaultOpen,
|
|
31
|
+
onOpenChange,
|
|
32
|
+
placement = 'right',
|
|
33
|
+
modal = true,
|
|
34
|
+
size = 'md',
|
|
35
|
+
} = props;
|
|
36
|
+
|
|
37
|
+
const handleOpenChange = React.useCallback((nextOpen: boolean) => {
|
|
38
|
+
onOpenChange?.({ open: nextOpen });
|
|
39
|
+
}, [ onOpenChange ]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<DrawerPlacementContext.Provider value={ placement }>
|
|
43
|
+
<DrawerSizeContext.Provider value={ size }>
|
|
44
|
+
<RadixDialog.Root
|
|
45
|
+
open={ open }
|
|
46
|
+
defaultOpen={ defaultOpen }
|
|
47
|
+
onOpenChange={ handleOpenChange }
|
|
48
|
+
modal={ modal }
|
|
49
|
+
>
|
|
50
|
+
{ children }
|
|
51
|
+
</RadixDialog.Root>
|
|
52
|
+
</DrawerSizeContext.Provider>
|
|
53
|
+
</DrawerPlacementContext.Provider>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ─── DrawerContent ──────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
export interface DrawerContentProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
60
|
+
portalled?: boolean;
|
|
61
|
+
portalRef?: React.RefObject<HTMLElement>;
|
|
62
|
+
offset?: string | number;
|
|
63
|
+
backdrop?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const PLACEMENT_CLASSES: Record<string, string> = {
|
|
67
|
+
right: [
|
|
68
|
+
'inset-y-0 right-0',
|
|
69
|
+
'data-[state=open]:animate-in data-[state=open]:slide-in-from-right',
|
|
70
|
+
'data-[state=closed]:animate-out data-[state=closed]:slide-out-to-right',
|
|
71
|
+
].join(' '),
|
|
72
|
+
left: [
|
|
73
|
+
'inset-y-0 left-0',
|
|
74
|
+
'data-[state=open]:animate-in data-[state=open]:slide-in-from-left',
|
|
75
|
+
'data-[state=closed]:animate-out data-[state=closed]:slide-out-to-left',
|
|
76
|
+
].join(' '),
|
|
77
|
+
top: [
|
|
78
|
+
'inset-x-0 top-0',
|
|
79
|
+
'data-[state=open]:animate-in data-[state=open]:slide-in-from-top',
|
|
80
|
+
'data-[state=closed]:animate-out data-[state=closed]:slide-out-to-top',
|
|
81
|
+
].join(' '),
|
|
82
|
+
bottom: [
|
|
83
|
+
'inset-x-0 bottom-0',
|
|
84
|
+
'data-[state=open]:animate-in data-[state=open]:slide-in-from-bottom',
|
|
85
|
+
'data-[state=closed]:animate-out data-[state=closed]:slide-out-to-bottom',
|
|
86
|
+
].join(' '),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const SIZE_CLASSES: Record<string, Record<string, string>> = {
|
|
90
|
+
right: { xs: 'w-60', sm: 'w-80', md: 'w-96', lg: 'w-[480px]', xl: 'w-[640px]', full: 'w-screen' },
|
|
91
|
+
left: { xs: 'w-60', sm: 'w-80', md: 'w-96', lg: 'w-[480px]', xl: 'w-[640px]', full: 'w-screen' },
|
|
92
|
+
top: { xs: 'h-40', sm: 'h-60', md: 'h-80', lg: 'h-96', xl: 'h-[480px]', full: 'h-screen' },
|
|
93
|
+
bottom: { xs: 'h-40', sm: 'h-60', md: 'h-80', lg: 'h-96', xl: 'h-[480px]', full: 'h-screen' },
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const DrawerContent = React.forwardRef<HTMLDivElement, DrawerContentProps>(
|
|
97
|
+
function DrawerContent(props, ref) {
|
|
98
|
+
const { children, portalled = true, portalRef, offset: _offset, backdrop = true, className, ...rest } = props;
|
|
99
|
+
const placement = React.useContext(DrawerPlacementContext);
|
|
100
|
+
const size = React.useContext(DrawerSizeContext);
|
|
101
|
+
|
|
102
|
+
const content = (
|
|
103
|
+
<>
|
|
104
|
+
{ backdrop && (
|
|
105
|
+
<RadixDialog.Overlay
|
|
106
|
+
className={ cn(
|
|
107
|
+
'fixed inset-0 z-50 bg-black/50',
|
|
108
|
+
'data-[state=open]:animate-in data-[state=open]:fade-in-0',
|
|
109
|
+
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0',
|
|
110
|
+
) }
|
|
111
|
+
/>
|
|
112
|
+
) }
|
|
113
|
+
<RadixDialog.Content
|
|
114
|
+
ref={ ref }
|
|
115
|
+
className={ cn(
|
|
116
|
+
'fixed z-50 flex flex-col bg-[var(--color-drawer-bg)] shadow-[var(--shadow-drawer)] duration-300',
|
|
117
|
+
PLACEMENT_CLASSES[placement] ?? PLACEMENT_CLASSES.right,
|
|
118
|
+
SIZE_CLASSES[placement]?.[size] ?? SIZE_CLASSES.right?.md,
|
|
119
|
+
className,
|
|
120
|
+
) }
|
|
121
|
+
{ ...rest }
|
|
122
|
+
>
|
|
123
|
+
{ children }
|
|
124
|
+
</RadixDialog.Content>
|
|
125
|
+
</>
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
if (portalled) {
|
|
129
|
+
return (
|
|
130
|
+
<RadixDialog.Portal container={ portalRef?.current ?? undefined }>
|
|
131
|
+
{ content }
|
|
132
|
+
</RadixDialog.Portal>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return content;
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// ─── DrawerCloseTrigger ─────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
export const DrawerCloseTrigger = React.forwardRef<
|
|
143
|
+
HTMLButtonElement,
|
|
144
|
+
React.ComponentPropsWithoutRef<'button'>
|
|
145
|
+
>(function DrawerCloseTrigger(props, ref) {
|
|
146
|
+
return (
|
|
147
|
+
<RadixDialog.Close asChild>
|
|
148
|
+
<CloseButton
|
|
149
|
+
ref={ ref }
|
|
150
|
+
className={ cn('absolute top-7 right-5', props.className) }
|
|
151
|
+
{ ...props }
|
|
152
|
+
/>
|
|
153
|
+
</RadixDialog.Close>
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// ─── DrawerTrigger ──────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
export const DrawerTrigger = (props: React.ComponentPropsWithoutRef<typeof RadixDialog.Trigger>) => {
|
|
160
|
+
const { asChild = true, ...rest } = props;
|
|
161
|
+
return <RadixDialog.Trigger asChild={ asChild } { ...rest }/>;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// ─── Sub-components ─────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
export const DrawerHeader = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<'div'>>(
|
|
167
|
+
function DrawerHeader({ className, ...props }, ref) {
|
|
168
|
+
return <div ref={ ref } className={ cn('flex flex-col gap-1.5 p-6 pb-0', className) } { ...props }/>;
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
export const DrawerBody = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<'div'>>(
|
|
173
|
+
function DrawerBody({ className, ...props }, ref) {
|
|
174
|
+
return <div ref={ ref } className={ cn('flex-1 overflow-auto p-6', className) } { ...props }/>;
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
export const DrawerFooter = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<'div'>>(
|
|
179
|
+
function DrawerFooter({ className, ...props }, ref) {
|
|
180
|
+
return <div ref={ ref } className={ cn('flex items-center justify-end gap-2 p-6 pt-0', className) } { ...props }/>;
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
export const DrawerTitle = RadixDialog.Title;
|
|
185
|
+
export const DrawerDescription = RadixDialog.Description;
|
|
186
|
+
export const DrawerActionTrigger = RadixDialog.Close;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from './utils';
|
|
4
|
+
|
|
5
|
+
// Inline constants
|
|
6
|
+
const apos = '\u2019'; // right single quotation mark (typographic apostrophe)
|
|
7
|
+
|
|
8
|
+
function upperFirst(str: string): string {
|
|
9
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type EmptyStateType = 'query' | 'stats' | 'coming_soon';
|
|
13
|
+
|
|
14
|
+
// Empty state icons are intentionally omitted in the shared package.
|
|
15
|
+
// Consumers can pass a custom `icon` prop instead.
|
|
16
|
+
const ICONS: Partial<Record<EmptyStateType, React.FunctionComponent>> = {};
|
|
17
|
+
|
|
18
|
+
export interface EmptyStateProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
19
|
+
title?: string;
|
|
20
|
+
description?: React.ReactNode;
|
|
21
|
+
term?: string;
|
|
22
|
+
type?: EmptyStateType;
|
|
23
|
+
icon?: React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const EmptyState = React.forwardRef<HTMLDivElement, EmptyStateProps>(
|
|
27
|
+
function EmptyState(props, ref) {
|
|
28
|
+
const { title, description, term, type = 'query', icon, children, className, ...rest } = props;
|
|
29
|
+
|
|
30
|
+
const titleContent = (() => {
|
|
31
|
+
if (title) {
|
|
32
|
+
return title;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (type === 'stats') {
|
|
36
|
+
return 'Collecting data';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (type === 'coming_soon') {
|
|
40
|
+
return 'Coming soon';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return 'No results';
|
|
44
|
+
})();
|
|
45
|
+
|
|
46
|
+
const descriptionContent = (() => {
|
|
47
|
+
if (description) {
|
|
48
|
+
return description;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (term && type === 'query') {
|
|
52
|
+
return `Couldn${ apos }t find any ${ term } that matches your query.`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (type === 'stats') {
|
|
56
|
+
return term ? `${ upperFirst(term) } stats will be added soon` : 'Charts and statistics will be available soon';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (type === 'coming_soon') {
|
|
60
|
+
return 'The information will be available soon. Stay tuned!';
|
|
61
|
+
}
|
|
62
|
+
})();
|
|
63
|
+
|
|
64
|
+
const iconContent = (() => {
|
|
65
|
+
const Icon = ICONS[type];
|
|
66
|
+
if (Icon) {
|
|
67
|
+
return <Icon/>;
|
|
68
|
+
}
|
|
69
|
+
return icon;
|
|
70
|
+
})();
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div
|
|
74
|
+
ref={ ref }
|
|
75
|
+
className={ cn('flex items-center justify-center py-10', className) }
|
|
76
|
+
{ ...rest }
|
|
77
|
+
>
|
|
78
|
+
<div className="flex flex-col items-center gap-5">
|
|
79
|
+
{ iconContent && (
|
|
80
|
+
<div className="flex items-center justify-center">{ iconContent }</div>
|
|
81
|
+
) }
|
|
82
|
+
{ descriptionContent ? (
|
|
83
|
+
<div className="flex flex-col items-center gap-2 text-center">
|
|
84
|
+
<span className="text-lg font-semibold text-text-secondary">{ titleContent }</span>
|
|
85
|
+
<span className="text-sm text-text-secondary">
|
|
86
|
+
{ descriptionContent }
|
|
87
|
+
</span>
|
|
88
|
+
</div>
|
|
89
|
+
) : (
|
|
90
|
+
<span className="text-lg font-semibold text-text-secondary">{ titleContent }</span>
|
|
91
|
+
) }
|
|
92
|
+
{ children }
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
},
|
|
97
|
+
);
|