@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/popover.tsx
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import * as RadixPopover from '@radix-ui/react-popover';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { cn } from './utils';
|
|
5
|
+
|
|
6
|
+
import { CloseButton } from './close-button';
|
|
7
|
+
|
|
8
|
+
// --- Utility: map Chakra-style placement string to Radix side + align ---
|
|
9
|
+
|
|
10
|
+
type Side = 'top' | 'right' | 'bottom' | 'left';
|
|
11
|
+
type Align = 'start' | 'center' | 'end';
|
|
12
|
+
|
|
13
|
+
interface PlacementMapping {
|
|
14
|
+
readonly side: Side;
|
|
15
|
+
readonly align: Align;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parsePlacement(placement: string | undefined): PlacementMapping {
|
|
19
|
+
if (!placement) {
|
|
20
|
+
return { side: 'bottom', align: 'start' };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const parts = placement.split('-');
|
|
24
|
+
const side = (parts[0] as Side) ?? 'bottom';
|
|
25
|
+
const alignPart = parts[1];
|
|
26
|
+
|
|
27
|
+
let align: Align = 'center';
|
|
28
|
+
if (alignPart === 'start') {
|
|
29
|
+
align = 'start';
|
|
30
|
+
} else if (alignPart === 'end') {
|
|
31
|
+
align = 'end';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { side, align };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// --- PopoverRoot ---
|
|
38
|
+
|
|
39
|
+
interface Positioning {
|
|
40
|
+
readonly placement?: string;
|
|
41
|
+
readonly offset?: {
|
|
42
|
+
readonly mainAxis?: number;
|
|
43
|
+
readonly crossAxis?: number;
|
|
44
|
+
};
|
|
45
|
+
readonly overflowPadding?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface PopoverRootProps {
|
|
49
|
+
readonly children?: React.ReactNode;
|
|
50
|
+
readonly open?: boolean;
|
|
51
|
+
readonly defaultOpen?: boolean;
|
|
52
|
+
|
|
53
|
+
/** Chakra-style callback: receives `{ open: boolean }` */
|
|
54
|
+
readonly onOpenChange?: (details: { open: boolean }) => void;
|
|
55
|
+
readonly positioning?: Positioning;
|
|
56
|
+
readonly lazyMount?: boolean;
|
|
57
|
+
readonly unmountOnExit?: boolean;
|
|
58
|
+
readonly autoFocus?: boolean;
|
|
59
|
+
readonly closeOnInteractOutside?: boolean;
|
|
60
|
+
readonly modal?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Stash positioning info in context so PopoverContent can read it.
|
|
64
|
+
interface PopoverPositioning {
|
|
65
|
+
readonly side: Side;
|
|
66
|
+
readonly align: Align;
|
|
67
|
+
readonly sideOffset: number;
|
|
68
|
+
readonly alignOffset: number;
|
|
69
|
+
readonly collisionPadding: number;
|
|
70
|
+
readonly autoFocus: boolean;
|
|
71
|
+
readonly closeOnInteractOutside: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const PositioningContext = React.createContext<PopoverPositioning>({
|
|
75
|
+
side: 'bottom',
|
|
76
|
+
align: 'start',
|
|
77
|
+
sideOffset: 4,
|
|
78
|
+
alignOffset: 0,
|
|
79
|
+
collisionPadding: 4,
|
|
80
|
+
autoFocus: false,
|
|
81
|
+
closeOnInteractOutside: true,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export const PopoverRoot = (props: PopoverRootProps): React.ReactElement => {
|
|
85
|
+
const {
|
|
86
|
+
children,
|
|
87
|
+
open,
|
|
88
|
+
defaultOpen,
|
|
89
|
+
onOpenChange,
|
|
90
|
+
positioning,
|
|
91
|
+
autoFocus = false,
|
|
92
|
+
closeOnInteractOutside = true,
|
|
93
|
+
modal = false,
|
|
94
|
+
// lazyMount and unmountOnExit are handled via forceMount on Portal/Content
|
|
95
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
96
|
+
lazyMount: _lazyMount,
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
98
|
+
unmountOnExit: _unmountOnExit,
|
|
99
|
+
} = props;
|
|
100
|
+
|
|
101
|
+
// Merge default positioning with user positioning
|
|
102
|
+
const mergedPositioning: Positioning = {
|
|
103
|
+
placement: 'bottom-start',
|
|
104
|
+
overflowPadding: 4,
|
|
105
|
+
...positioning,
|
|
106
|
+
offset: {
|
|
107
|
+
mainAxis: 4,
|
|
108
|
+
...positioning?.offset,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const { side, align } = parsePlacement(mergedPositioning.placement);
|
|
113
|
+
|
|
114
|
+
const positioningValue = React.useMemo<PopoverPositioning>(() => ({
|
|
115
|
+
side,
|
|
116
|
+
align,
|
|
117
|
+
sideOffset: mergedPositioning.offset?.mainAxis ?? 4,
|
|
118
|
+
alignOffset: mergedPositioning.offset?.crossAxis ?? 0,
|
|
119
|
+
collisionPadding: mergedPositioning.overflowPadding ?? 4,
|
|
120
|
+
autoFocus,
|
|
121
|
+
closeOnInteractOutside,
|
|
122
|
+
}), [
|
|
123
|
+
side, align,
|
|
124
|
+
mergedPositioning.offset?.mainAxis, mergedPositioning.offset?.crossAxis,
|
|
125
|
+
mergedPositioning.overflowPadding, autoFocus, closeOnInteractOutside,
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
// Bridge Chakra-style onOpenChange ({ open }) to Radix (open)
|
|
129
|
+
const handleOpenChange = React.useCallback((isOpen: boolean) => {
|
|
130
|
+
onOpenChange?.({ open: isOpen });
|
|
131
|
+
}, [ onOpenChange ]);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<PositioningContext.Provider value={ positioningValue }>
|
|
135
|
+
<RadixPopover.Root
|
|
136
|
+
open={ open }
|
|
137
|
+
defaultOpen={ defaultOpen }
|
|
138
|
+
onOpenChange={ handleOpenChange }
|
|
139
|
+
modal={ modal }
|
|
140
|
+
>
|
|
141
|
+
{ children }
|
|
142
|
+
</RadixPopover.Root>
|
|
143
|
+
</PositioningContext.Provider>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// --- PopoverTrigger ---
|
|
148
|
+
|
|
149
|
+
export interface PopoverTriggerProps extends React.ComponentPropsWithoutRef<'button'> {
|
|
150
|
+
readonly asChild?: boolean;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const PopoverTrigger = React.forwardRef<
|
|
154
|
+
HTMLButtonElement,
|
|
155
|
+
PopoverTriggerProps
|
|
156
|
+
>(function PopoverTrigger(props, ref) {
|
|
157
|
+
const { asChild = true, ...rest } = props;
|
|
158
|
+
return <RadixPopover.Trigger asChild={ asChild } ref={ ref } { ...rest }/>;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// --- PopoverContent ---
|
|
162
|
+
|
|
163
|
+
export interface PopoverContentProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
164
|
+
readonly portalled?: boolean;
|
|
165
|
+
readonly portalRef?: React.RefObject<HTMLElement>;
|
|
166
|
+
// Legacy Chakra style-prop shims
|
|
167
|
+
readonly w?: string | Record<string, string>;
|
|
168
|
+
readonly minW?: string;
|
|
169
|
+
readonly maxW?: string;
|
|
170
|
+
readonly paddingTop?: number | string;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export const PopoverContent = React.forwardRef<
|
|
174
|
+
HTMLDivElement,
|
|
175
|
+
PopoverContentProps
|
|
176
|
+
>(function PopoverContent(props, ref) {
|
|
177
|
+
const { portalled = true, portalRef, className, w, minW, maxW, paddingTop, style: styleProp, ...rest } = props;
|
|
178
|
+
const resolvedW = typeof w === 'object' ? (w as Record<string, string>).base ?? (w as Record<string, string>).lg : w;
|
|
179
|
+
const contentStyle: React.CSSProperties = {
|
|
180
|
+
...styleProp,
|
|
181
|
+
...(resolvedW ? { width: resolvedW } : {}),
|
|
182
|
+
...(minW ? { minWidth: minW } : {}),
|
|
183
|
+
...(maxW ? { maxWidth: maxW } : {}),
|
|
184
|
+
...(paddingTop !== undefined ? { paddingTop: typeof paddingTop === 'number' ? `${ paddingTop * 4 }px` : paddingTop } : {}),
|
|
185
|
+
};
|
|
186
|
+
const positioning = React.useContext(PositioningContext);
|
|
187
|
+
|
|
188
|
+
const preventFocus = React.useCallback((e: Event) => e.preventDefault(), []);
|
|
189
|
+
const preventInteract = React.useCallback((e: Event) => e.preventDefault(), []);
|
|
190
|
+
|
|
191
|
+
const content = (
|
|
192
|
+
<RadixPopover.Content
|
|
193
|
+
ref={ ref }
|
|
194
|
+
side={ positioning.side }
|
|
195
|
+
align={ positioning.align }
|
|
196
|
+
sideOffset={ positioning.sideOffset }
|
|
197
|
+
alignOffset={ positioning.alignOffset }
|
|
198
|
+
collisionPadding={ positioning.collisionPadding }
|
|
199
|
+
onOpenAutoFocus={ positioning.autoFocus ? undefined : preventFocus }
|
|
200
|
+
onInteractOutside={ positioning.closeOnInteractOutside ? undefined : preventInteract }
|
|
201
|
+
className={ cn(
|
|
202
|
+
'z-50 rounded-lg border border-[var(--color-popover-border,var(--color-border-divider))]',
|
|
203
|
+
'bg-[var(--color-popover-bg,var(--color-dialog-bg))]',
|
|
204
|
+
'shadow-[var(--shadow-popover,var(--shadow-lg))]',
|
|
205
|
+
'outline-none',
|
|
206
|
+
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
207
|
+
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
|
208
|
+
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',
|
|
209
|
+
'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
210
|
+
className,
|
|
211
|
+
) }
|
|
212
|
+
style={ Object.keys(contentStyle).length > 0 ? contentStyle : undefined }
|
|
213
|
+
{ ...rest }
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
if (!portalled) {
|
|
218
|
+
return content;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<RadixPopover.Portal container={ portalRef?.current ?? undefined }>
|
|
223
|
+
{ content }
|
|
224
|
+
</RadixPopover.Portal>
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// --- PopoverArrow ---
|
|
229
|
+
|
|
230
|
+
export interface PopoverArrowProps extends React.ComponentPropsWithoutRef<'svg'> {}
|
|
231
|
+
|
|
232
|
+
export const PopoverArrow = React.forwardRef<
|
|
233
|
+
SVGSVGElement,
|
|
234
|
+
PopoverArrowProps
|
|
235
|
+
>(function PopoverArrow(props, ref) {
|
|
236
|
+
const { className, ...rest } = props;
|
|
237
|
+
return (
|
|
238
|
+
<RadixPopover.Arrow
|
|
239
|
+
ref={ ref }
|
|
240
|
+
className={ cn('fill-[var(--color-popover-bg,var(--color-dialog-bg))]', className) }
|
|
241
|
+
{ ...rest }
|
|
242
|
+
/>
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// --- PopoverCloseTrigger ---
|
|
247
|
+
|
|
248
|
+
export interface PopoverCloseTriggerProps extends React.ComponentPropsWithoutRef<'button'> {}
|
|
249
|
+
|
|
250
|
+
export const PopoverCloseTrigger = React.forwardRef<
|
|
251
|
+
HTMLButtonElement,
|
|
252
|
+
PopoverCloseTriggerProps
|
|
253
|
+
>(function PopoverCloseTrigger(props, ref) {
|
|
254
|
+
const { className, ...rest } = props;
|
|
255
|
+
return (
|
|
256
|
+
<RadixPopover.Close
|
|
257
|
+
className={ cn('absolute top-1 right-1', className) }
|
|
258
|
+
{ ...rest }
|
|
259
|
+
asChild
|
|
260
|
+
ref={ ref }
|
|
261
|
+
>
|
|
262
|
+
<CloseButton/>
|
|
263
|
+
</RadixPopover.Close>
|
|
264
|
+
);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// --- PopoverCloseTriggerWrapper ---
|
|
268
|
+
|
|
269
|
+
export interface PopoverCloseTriggerWrapperProps extends React.ComponentPropsWithoutRef<'button'> {
|
|
270
|
+
readonly disabled?: boolean;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export const PopoverCloseTriggerWrapper = React.forwardRef<
|
|
274
|
+
HTMLButtonElement,
|
|
275
|
+
PopoverCloseTriggerWrapperProps
|
|
276
|
+
>(function PopoverCloseTriggerWrapper(props, ref) {
|
|
277
|
+
const { disabled, children, ...rest } = props;
|
|
278
|
+
|
|
279
|
+
if (disabled) {
|
|
280
|
+
return children as React.ReactElement;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return (
|
|
284
|
+
<RadixPopover.Close ref={ ref } { ...rest } asChild>
|
|
285
|
+
{ children }
|
|
286
|
+
</RadixPopover.Close>
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// --- Simple wrapper components ---
|
|
291
|
+
|
|
292
|
+
export interface PopoverBodyProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
293
|
+
// Legacy Chakra style-prop shims
|
|
294
|
+
readonly display?: string;
|
|
295
|
+
readonly flexDir?: string;
|
|
296
|
+
readonly rowGap?: number | string;
|
|
297
|
+
readonly px?: number | string;
|
|
298
|
+
readonly py?: number | string;
|
|
299
|
+
readonly textStyle?: string;
|
|
300
|
+
readonly alignItems?: string;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export const PopoverBody = React.forwardRef<
|
|
304
|
+
HTMLDivElement,
|
|
305
|
+
PopoverBodyProps
|
|
306
|
+
>(function PopoverBody(props, ref) {
|
|
307
|
+
const {
|
|
308
|
+
className, style: styleProp,
|
|
309
|
+
display: _display, flexDir: _flexDir, rowGap: _rowGap, px: _px, py: _py,
|
|
310
|
+
textStyle: _textStyle, alignItems: _alignItems,
|
|
311
|
+
...rest
|
|
312
|
+
} = props;
|
|
313
|
+
const bodyStyle: React.CSSProperties = {
|
|
314
|
+
...styleProp,
|
|
315
|
+
...(_display ? { display: _display } : {}),
|
|
316
|
+
...(_flexDir ? { flexDirection: _flexDir as React.CSSProperties['flexDirection'] } : {}),
|
|
317
|
+
...(_rowGap !== undefined ? { rowGap: typeof _rowGap === 'number' ? `${ _rowGap * 4 }px` : _rowGap } : {}),
|
|
318
|
+
...(_px !== undefined ? {
|
|
319
|
+
paddingLeft: typeof _px === 'number' ? `${ _px * 4 }px` : _px,
|
|
320
|
+
paddingRight: typeof _px === 'number' ? `${ _px * 4 }px` : _px,
|
|
321
|
+
} : {}),
|
|
322
|
+
...(_py !== undefined ? {
|
|
323
|
+
paddingTop: typeof _py === 'number' ? `${ _py * 4 }px` : _py,
|
|
324
|
+
paddingBottom: typeof _py === 'number' ? `${ _py * 4 }px` : _py,
|
|
325
|
+
} : {}),
|
|
326
|
+
...(_alignItems ? { alignItems: _alignItems } : {}),
|
|
327
|
+
};
|
|
328
|
+
return (
|
|
329
|
+
<div
|
|
330
|
+
ref={ ref }
|
|
331
|
+
className={ cn('p-4', className) }
|
|
332
|
+
style={ Object.keys(bodyStyle).length > 0 ? bodyStyle : undefined }
|
|
333
|
+
{ ...rest }
|
|
334
|
+
/>
|
|
335
|
+
);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
export interface PopoverHeaderProps extends React.ComponentPropsWithoutRef<'div'> {}
|
|
339
|
+
|
|
340
|
+
export const PopoverHeader = React.forwardRef<
|
|
341
|
+
HTMLDivElement,
|
|
342
|
+
PopoverHeaderProps
|
|
343
|
+
>(function PopoverHeader(props, ref) {
|
|
344
|
+
const { className, ...rest } = props;
|
|
345
|
+
return (
|
|
346
|
+
<div
|
|
347
|
+
ref={ ref }
|
|
348
|
+
className={ cn('px-4 pt-4 pb-0 font-semibold', className) }
|
|
349
|
+
{ ...rest }
|
|
350
|
+
/>
|
|
351
|
+
);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
export interface PopoverFooterProps extends React.ComponentPropsWithoutRef<'div'> {}
|
|
355
|
+
|
|
356
|
+
export const PopoverFooter = React.forwardRef<
|
|
357
|
+
HTMLDivElement,
|
|
358
|
+
PopoverFooterProps
|
|
359
|
+
>(function PopoverFooter(props, ref) {
|
|
360
|
+
const { className, ...rest } = props;
|
|
361
|
+
return (
|
|
362
|
+
<div
|
|
363
|
+
ref={ ref }
|
|
364
|
+
className={ cn('px-4 pb-4 pt-0', className) }
|
|
365
|
+
{ ...rest }
|
|
366
|
+
/>
|
|
367
|
+
);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
export interface PopoverTitleProps extends React.ComponentPropsWithoutRef<'h3'> {}
|
|
371
|
+
|
|
372
|
+
export const PopoverTitle = React.forwardRef<
|
|
373
|
+
HTMLHeadingElement,
|
|
374
|
+
PopoverTitleProps
|
|
375
|
+
>(function PopoverTitle(props, ref) {
|
|
376
|
+
const { className, ...rest } = props;
|
|
377
|
+
return (
|
|
378
|
+
<h3
|
|
379
|
+
ref={ ref }
|
|
380
|
+
className={ cn('text-base font-semibold', className) }
|
|
381
|
+
{ ...rest }
|
|
382
|
+
/>
|
|
383
|
+
);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
export interface PopoverDescriptionProps extends React.ComponentPropsWithoutRef<'p'> {}
|
|
387
|
+
|
|
388
|
+
export const PopoverDescription = React.forwardRef<
|
|
389
|
+
HTMLParagraphElement,
|
|
390
|
+
PopoverDescriptionProps
|
|
391
|
+
>(function PopoverDescription(props, ref) {
|
|
392
|
+
const { className, ...rest } = props;
|
|
393
|
+
return (
|
|
394
|
+
<p
|
|
395
|
+
ref={ ref }
|
|
396
|
+
className={ cn('text-sm', className) }
|
|
397
|
+
{ ...rest }
|
|
398
|
+
/>
|
|
399
|
+
);
|
|
400
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from './utils';
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Size presets matching the original Chakra progressCircle recipe
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
const SIZE_MAP = {
|
|
10
|
+
xs: { size: 24, thickness: 4, textClass: 'text-[10px]' },
|
|
11
|
+
sm: { size: 32, thickness: 5, textClass: 'text-[10px]' },
|
|
12
|
+
md: { size: 40, thickness: 6, textClass: 'text-xs' },
|
|
13
|
+
lg: { size: 48, thickness: 7, textClass: 'text-sm' },
|
|
14
|
+
xl: { size: 64, thickness: 8, textClass: 'text-sm' },
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
type Size = keyof typeof SIZE_MAP;
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Context
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
interface ProgressCircleCtx {
|
|
24
|
+
readonly value: number | null;
|
|
25
|
+
readonly size: Size;
|
|
26
|
+
readonly colorPalette: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const Ctx = React.createContext<ProgressCircleCtx>({
|
|
30
|
+
value: 0,
|
|
31
|
+
size: 'md',
|
|
32
|
+
colorPalette: 'blue',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Root
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
export interface ProgressCircleRootProps
|
|
40
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color'> {
|
|
41
|
+
readonly value?: number | null;
|
|
42
|
+
readonly size?: Size;
|
|
43
|
+
readonly colorPalette?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const ProgressCircleRoot = React.forwardRef<
|
|
47
|
+
HTMLDivElement,
|
|
48
|
+
ProgressCircleRootProps
|
|
49
|
+
>(function ProgressCircleRoot(props, ref) {
|
|
50
|
+
const {
|
|
51
|
+
value = 0,
|
|
52
|
+
size = 'md',
|
|
53
|
+
colorPalette = 'blue',
|
|
54
|
+
className,
|
|
55
|
+
children,
|
|
56
|
+
...rest
|
|
57
|
+
} = props;
|
|
58
|
+
|
|
59
|
+
const ctx = React.useMemo<ProgressCircleCtx>(
|
|
60
|
+
() => ({ value: value ?? null, size, colorPalette }),
|
|
61
|
+
[ value, size, colorPalette ],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Ctx.Provider value={ ctx }>
|
|
66
|
+
<div
|
|
67
|
+
ref={ ref }
|
|
68
|
+
role="progressbar"
|
|
69
|
+
aria-valuenow={ value ?? undefined }
|
|
70
|
+
aria-valuemin={ 0 }
|
|
71
|
+
aria-valuemax={ 100 }
|
|
72
|
+
className={ cn('relative inline-flex text-sm', className) }
|
|
73
|
+
{ ...rest }
|
|
74
|
+
>
|
|
75
|
+
{ children }
|
|
76
|
+
</div>
|
|
77
|
+
</Ctx.Provider>
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Ring (SVG track + range arc)
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
export interface ProgressCircleRingProps
|
|
86
|
+
extends Omit<React.SVGAttributes<SVGSVGElement>, 'color'> {
|
|
87
|
+
readonly trackColor?: string;
|
|
88
|
+
readonly cap?: React.SVGAttributes<SVGCircleElement>['strokeLinecap'];
|
|
89
|
+
readonly color?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const ProgressCircleRing = React.forwardRef<
|
|
93
|
+
SVGSVGElement,
|
|
94
|
+
ProgressCircleRingProps
|
|
95
|
+
>(function ProgressCircleRing(props, ref) {
|
|
96
|
+
const { trackColor, cap, color, className, ...rest } = props;
|
|
97
|
+
const { value, size: sizeKey } = React.useContext(Ctx);
|
|
98
|
+
|
|
99
|
+
const { size, thickness } = SIZE_MAP[sizeKey];
|
|
100
|
+
const radius = size / 2 - thickness / 2;
|
|
101
|
+
const circumference = 2 * Math.PI * radius;
|
|
102
|
+
const percent = value ?? 0;
|
|
103
|
+
const offset = circumference * ((100 - percent) / 100);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<svg
|
|
107
|
+
ref={ ref }
|
|
108
|
+
width={ size }
|
|
109
|
+
height={ size }
|
|
110
|
+
viewBox={ `0 0 ${ size } ${ size }` }
|
|
111
|
+
className={ cn(
|
|
112
|
+
value === null && 'animate-spin',
|
|
113
|
+
className,
|
|
114
|
+
) }
|
|
115
|
+
{ ...rest }
|
|
116
|
+
>
|
|
117
|
+
{ /* Track */ }
|
|
118
|
+
<circle
|
|
119
|
+
cx={ size / 2 }
|
|
120
|
+
cy={ size / 2 }
|
|
121
|
+
r={ radius }
|
|
122
|
+
fill="transparent"
|
|
123
|
+
strokeWidth={ thickness }
|
|
124
|
+
stroke={ trackColor ?? 'currentColor' }
|
|
125
|
+
className={ !trackColor ? 'opacity-20' : undefined }
|
|
126
|
+
/>
|
|
127
|
+
{ /* Range */ }
|
|
128
|
+
<circle
|
|
129
|
+
cx={ size / 2 }
|
|
130
|
+
cy={ size / 2 }
|
|
131
|
+
r={ radius }
|
|
132
|
+
fill="transparent"
|
|
133
|
+
strokeWidth={ thickness }
|
|
134
|
+
stroke={ color ?? 'currentColor' }
|
|
135
|
+
strokeLinecap={ cap }
|
|
136
|
+
strokeDasharray={ circumference }
|
|
137
|
+
strokeDashoffset={ offset }
|
|
138
|
+
style={{
|
|
139
|
+
transformOrigin: 'center',
|
|
140
|
+
transform: 'rotate(-90deg)',
|
|
141
|
+
transition: 'stroke-dashoffset 0.6s, stroke-dasharray 0.6s',
|
|
142
|
+
opacity: percent === 0 ? 0 : undefined,
|
|
143
|
+
}}
|
|
144
|
+
/>
|
|
145
|
+
</svg>
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// Value text (centered number)
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
export interface ProgressCircleValueTextProps
|
|
154
|
+
extends React.HTMLAttributes<HTMLDivElement> {}
|
|
155
|
+
|
|
156
|
+
export const ProgressCircleValueText = React.forwardRef<
|
|
157
|
+
HTMLDivElement,
|
|
158
|
+
ProgressCircleValueTextProps
|
|
159
|
+
>(function ProgressCircleValueText(props, ref) {
|
|
160
|
+
const { className, children, ...rest } = props;
|
|
161
|
+
const { value, size: sizeKey } = React.useContext(Ctx);
|
|
162
|
+
|
|
163
|
+
const { textClass } = SIZE_MAP[sizeKey];
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div
|
|
167
|
+
ref={ ref }
|
|
168
|
+
aria-live="polite"
|
|
169
|
+
className={ cn(
|
|
170
|
+
'absolute inset-0 flex items-center justify-center',
|
|
171
|
+
'font-medium tracking-tight tabular-nums leading-none',
|
|
172
|
+
textClass,
|
|
173
|
+
className,
|
|
174
|
+
) }
|
|
175
|
+
{ ...rest }
|
|
176
|
+
>
|
|
177
|
+
{ children ?? `${ Math.round(value ?? 0) }%` }
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
});
|
package/src/progress.tsx
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as RadixProgress from '@radix-ui/react-progress';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { cn } from './utils';
|
|
5
|
+
|
|
6
|
+
const SIZE_CLASSES = {
|
|
7
|
+
sm: 'h-1',
|
|
8
|
+
md: 'h-2',
|
|
9
|
+
lg: 'h-3',
|
|
10
|
+
xl: 'h-4',
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
type Size = keyof typeof SIZE_CLASSES;
|
|
14
|
+
|
|
15
|
+
interface TrackProps {
|
|
16
|
+
readonly borderStartRadius?: number | string;
|
|
17
|
+
readonly borderEndRadius?: number | string;
|
|
18
|
+
readonly className?: string;
|
|
19
|
+
readonly style?: React.CSSProperties;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ProgressProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
23
|
+
readonly value?: number | null;
|
|
24
|
+
readonly min?: number;
|
|
25
|
+
readonly max?: number;
|
|
26
|
+
readonly size?: Size;
|
|
27
|
+
readonly color?: string;
|
|
28
|
+
readonly trackProps?: TrackProps;
|
|
29
|
+
|
|
30
|
+
// Common Chakra layout shorthands that consumers currently pass.
|
|
31
|
+
readonly w?: string;
|
|
32
|
+
readonly flex?: string | number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeValue(value: number | null | undefined, min: number, max: number): number {
|
|
36
|
+
if (value == null) return 0;
|
|
37
|
+
if (max <= min) return 0;
|
|
38
|
+
return Math.round(((value - min) / (max - min)) * 100);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
|
|
42
|
+
function Progress(props, ref) {
|
|
43
|
+
const {
|
|
44
|
+
value,
|
|
45
|
+
min = 0,
|
|
46
|
+
max = 100,
|
|
47
|
+
size = 'md',
|
|
48
|
+
color,
|
|
49
|
+
trackProps,
|
|
50
|
+
className,
|
|
51
|
+
style,
|
|
52
|
+
w,
|
|
53
|
+
flex,
|
|
54
|
+
...rest
|
|
55
|
+
} = props;
|
|
56
|
+
|
|
57
|
+
const percent = normalizeValue(value, min, max);
|
|
58
|
+
|
|
59
|
+
const trackStyle: React.CSSProperties = {
|
|
60
|
+
...trackProps?.style,
|
|
61
|
+
...(trackProps?.borderStartRadius !== undefined && {
|
|
62
|
+
borderStartStartRadius: trackProps.borderStartRadius,
|
|
63
|
+
borderEndStartRadius: trackProps.borderStartRadius,
|
|
64
|
+
}),
|
|
65
|
+
...(trackProps?.borderEndRadius !== undefined && {
|
|
66
|
+
borderStartEndRadius: trackProps.borderEndRadius,
|
|
67
|
+
borderEndEndRadius: trackProps.borderEndRadius,
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const rootStyle: React.CSSProperties = {
|
|
72
|
+
...style,
|
|
73
|
+
...(w !== undefined && { width: w === 'full' ? '100%' : w }),
|
|
74
|
+
...(flex !== undefined && { flex }),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div
|
|
79
|
+
ref={ ref }
|
|
80
|
+
className={ cn('relative', className) }
|
|
81
|
+
style={ rootStyle }
|
|
82
|
+
{ ...rest }
|
|
83
|
+
>
|
|
84
|
+
<RadixProgress.Root
|
|
85
|
+
value={ percent }
|
|
86
|
+
max={ 100 }
|
|
87
|
+
className={ cn(
|
|
88
|
+
'w-full overflow-hidden rounded-full',
|
|
89
|
+
'bg-[var(--color-progress-track)]',
|
|
90
|
+
SIZE_CLASSES[size],
|
|
91
|
+
trackProps?.className,
|
|
92
|
+
) }
|
|
93
|
+
style={ trackStyle }
|
|
94
|
+
>
|
|
95
|
+
<RadixProgress.Indicator
|
|
96
|
+
className={ cn(
|
|
97
|
+
'h-full rounded-full transition-transform duration-500 ease-out',
|
|
98
|
+
!color && 'bg-[var(--color-progress-indicator)]',
|
|
99
|
+
) }
|
|
100
|
+
style={{
|
|
101
|
+
width: `${ percent }%`,
|
|
102
|
+
...(color && { backgroundColor: `var(--color-${ color.replace('.', '-') }, ${ color })` }),
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
</RadixProgress.Root>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
},
|
|
109
|
+
);
|
package/src/provider.tsx
ADDED