@refraction-ui/react 0.22.0 → 0.23.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/chunk-7P7EJ56S.js +7 -0
- package/dist/{chunk-XWP763SH.js → chunk-V727VZOE.js} +4 -2
- package/dist/{chunk-XWP763SH.js.map → chunk-V727VZOE.js.map} +2 -2
- package/dist/{chunk-3Y2T26QD.js → chunk-Z5IHYNQD.js} +4 -2
- package/dist/{chunk-3Y2T26QD.js.map → chunk-Z5IHYNQD.js.map} +2 -2
- package/dist/faro-engine-VIUXJAPH-FSARIABU.js +3 -0
- package/dist/{faro-engine-47HGRAQH-JKINJPMH.js.map → faro-engine-VIUXJAPH-FSARIABU.js.map} +1 -1
- package/dist/index.cjs +736 -274
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/internal/avatar/index.d.cts +4 -1
- package/dist/internal/avatar/index.d.ts +4 -1
- package/dist/internal/data-table/index.d.cts +48 -3
- package/dist/internal/data-table/index.d.ts +48 -3
- package/dist/internal/line-chart/index.d.ts +17 -3
- package/dist/internal/native-select/index.d.ts +21 -0
- package/dist/internal/react-avatar/index.d.cts +4 -2
- package/dist/internal/react-avatar/index.d.ts +4 -2
- package/dist/internal/react-command/index.d.cts +13 -2
- package/dist/internal/react-command/index.d.ts +13 -2
- package/dist/internal/react-data-table/index.d.cts +27 -9
- package/dist/internal/react-data-table/index.d.ts +27 -9
- package/dist/internal/react-dialog/index.d.cts +5 -0
- package/dist/internal/react-dialog/index.d.ts +5 -0
- package/dist/internal/react-dropdown-menu/index.d.cts +19 -1
- package/dist/internal/react-dropdown-menu/index.d.ts +19 -1
- package/dist/internal/react-line-chart/index.d.ts +7 -2
- package/dist/internal/react-native-select/index.d.ts +19 -0
- package/dist/internal/react-stat-grid/index.d.ts +22 -11
- package/dist/internal/react-table/index.d.ts +39 -0
- package/dist/internal/stat-grid/index.d.ts +49 -7
- package/dist/internal/table/index.d.ts +46 -0
- package/dist/react-analytics.js +15 -3
- package/dist/react-analytics.js.map +2 -2
- package/dist/react-avatar.js +12 -8
- package/dist/react-avatar.js.map +2 -2
- package/dist/react-button.js +1 -1
- package/dist/react-command.js +114 -69
- package/dist/react-command.js.map +3 -3
- package/dist/react-data-table.js +124 -113
- package/dist/react-data-table.js.map +2 -2
- package/dist/react-dialog.js +31 -7
- package/dist/react-dialog.js.map +2 -2
- package/dist/react-dropdown-menu.js +60 -7
- package/dist/react-dropdown-menu.js.map +3 -3
- package/dist/react-line-chart.js +48 -10
- package/dist/react-line-chart.js.map +2 -2
- package/dist/react-logger.js +2 -2
- package/dist/react-native-select.js +52 -0
- package/dist/react-native-select.js.map +7 -0
- package/dist/react-social-auth-button.js +1 -1
- package/dist/react-stat-grid.js +90 -21
- package/dist/react-stat-grid.js.map +2 -2
- package/dist/react-table.js +148 -0
- package/dist/react-table.js.map +7 -0
- package/dist/react-tabs.js +34 -5
- package/dist/react-tabs.js.map +2 -2
- package/package.json +3 -1
- package/dist/chunk-SDZ3LROZ.js +0 -7
- package/dist/faro-engine-47HGRAQH-JKINJPMH.js +0 -3
- /package/dist/{chunk-SDZ3LROZ.js.map → chunk-7P7EJ56S.js.map} +0 -0
|
@@ -138,11 +138,13 @@ function DropdownMenu({
|
|
|
138
138
|
apiRef.current = createDropdownMenu({ open });
|
|
139
139
|
}
|
|
140
140
|
const api = apiRef.current;
|
|
141
|
+
const triggerRef = React.useRef(null);
|
|
141
142
|
const ctx = React.useMemo(
|
|
142
143
|
() => ({
|
|
143
144
|
open,
|
|
144
145
|
onOpenChange: handleOpenChange,
|
|
145
|
-
contentId: api.ids.content
|
|
146
|
+
contentId: api.ids.content,
|
|
147
|
+
triggerRef
|
|
146
148
|
}),
|
|
147
149
|
[open, handleOpenChange, api.ids.content]
|
|
148
150
|
);
|
|
@@ -150,7 +152,12 @@ function DropdownMenu({
|
|
|
150
152
|
}
|
|
151
153
|
var DropdownMenuTrigger = React.forwardRef(
|
|
152
154
|
function DropdownMenuTrigger2({ asChild = false, onClick, children, ...props }, ref) {
|
|
153
|
-
const { open, onOpenChange, contentId } = useDropdownMenuContext();
|
|
155
|
+
const { open, onOpenChange, contentId, triggerRef } = useDropdownMenuContext();
|
|
156
|
+
const setRefs = (node) => {
|
|
157
|
+
triggerRef.current = node;
|
|
158
|
+
if (typeof ref === "function") ref(node);
|
|
159
|
+
else if (ref) ref.current = node;
|
|
160
|
+
};
|
|
154
161
|
const handleClick = (e) => {
|
|
155
162
|
onOpenChange(!open);
|
|
156
163
|
onClick?.(e);
|
|
@@ -181,20 +188,63 @@ var DropdownMenuTrigger = React.forwardRef(
|
|
|
181
188
|
if (!e.defaultPrevented) handleClick(e);
|
|
182
189
|
},
|
|
183
190
|
ref: (node) => {
|
|
184
|
-
|
|
185
|
-
else if (ref) ref.current = node;
|
|
191
|
+
setRefs(node);
|
|
186
192
|
if (typeof childRef === "function") childRef(node);
|
|
187
193
|
else if (childRef) childRef.current = node;
|
|
188
194
|
}
|
|
189
195
|
};
|
|
190
196
|
return React.cloneElement(child, mergedProps);
|
|
191
197
|
}
|
|
192
|
-
return React.createElement("button", { ref, ...triggerProps }, children);
|
|
198
|
+
return React.createElement("button", { ref: setRefs, ...triggerProps }, children);
|
|
193
199
|
}
|
|
194
200
|
);
|
|
201
|
+
var DEFAULT_SIDE_OFFSET = 4;
|
|
202
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
|
|
203
|
+
function computeMenuPosition(trigger, contentWidth, align, sideOffset) {
|
|
204
|
+
const left = align === "end" ? trigger.right - contentWidth : align === "center" ? trigger.left + trigger.width / 2 - contentWidth / 2 : trigger.left;
|
|
205
|
+
return { position: "fixed", top: trigger.bottom + sideOffset, left: Math.max(0, left), minWidth: trigger.width };
|
|
206
|
+
}
|
|
195
207
|
var DropdownMenuContent = React.forwardRef(
|
|
196
|
-
function DropdownMenuContent2({ className, children, onKeyDown, ...props },
|
|
197
|
-
const { open, onOpenChange, contentId } = useDropdownMenuContext();
|
|
208
|
+
function DropdownMenuContent2({ className, children, onKeyDown, align = "start", sideOffset = DEFAULT_SIDE_OFFSET, style, ...props }, forwardedRef) {
|
|
209
|
+
const { open, onOpenChange, contentId, triggerRef } = useDropdownMenuContext();
|
|
210
|
+
const contentRef = React.useRef(null);
|
|
211
|
+
const ref = React.useCallback(
|
|
212
|
+
(node) => {
|
|
213
|
+
contentRef.current = node;
|
|
214
|
+
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
215
|
+
else if (forwardedRef) forwardedRef.current = node;
|
|
216
|
+
},
|
|
217
|
+
[forwardedRef]
|
|
218
|
+
);
|
|
219
|
+
const [position, setPosition] = React.useState(null);
|
|
220
|
+
useIsomorphicLayoutEffect(() => {
|
|
221
|
+
if (!open) return;
|
|
222
|
+
const update = () => {
|
|
223
|
+
const trigger = triggerRef.current;
|
|
224
|
+
const content2 = contentRef.current;
|
|
225
|
+
if (!trigger) return;
|
|
226
|
+
setPosition(
|
|
227
|
+
computeMenuPosition(trigger.getBoundingClientRect(), content2?.offsetWidth ?? 0, align, sideOffset)
|
|
228
|
+
);
|
|
229
|
+
};
|
|
230
|
+
update();
|
|
231
|
+
window.addEventListener("resize", update);
|
|
232
|
+
window.addEventListener("scroll", update, true);
|
|
233
|
+
return () => {
|
|
234
|
+
window.removeEventListener("resize", update);
|
|
235
|
+
window.removeEventListener("scroll", update, true);
|
|
236
|
+
};
|
|
237
|
+
}, [open, align, sideOffset, triggerRef]);
|
|
238
|
+
React.useEffect(() => {
|
|
239
|
+
if (!open) return;
|
|
240
|
+
const onPointerDown = (event) => {
|
|
241
|
+
const target = event.target;
|
|
242
|
+
if (contentRef.current?.contains(target) || triggerRef.current?.contains(target)) return;
|
|
243
|
+
onOpenChange(false);
|
|
244
|
+
};
|
|
245
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
246
|
+
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
247
|
+
}, [open, onOpenChange, triggerRef]);
|
|
198
248
|
const handleKeyDown = (e) => {
|
|
199
249
|
const handler = createKeyboardHandler({
|
|
200
250
|
Escape: (ev) => {
|
|
@@ -214,6 +264,8 @@ var DropdownMenuContent = React.forwardRef(
|
|
|
214
264
|
id: contentId,
|
|
215
265
|
"data-state": open ? "open" : "closed",
|
|
216
266
|
className: cn(menuContentVariants(), className),
|
|
267
|
+
style: position ? { ...position, ...style } : style,
|
|
268
|
+
"data-align": align,
|
|
217
269
|
onKeyDown: handleKeyDown,
|
|
218
270
|
tabIndex: -1,
|
|
219
271
|
...props
|
|
@@ -286,6 +338,7 @@ export {
|
|
|
286
338
|
DropdownMenuLabel,
|
|
287
339
|
DropdownMenuSeparator,
|
|
288
340
|
DropdownMenuTrigger,
|
|
341
|
+
computeMenuPosition,
|
|
289
342
|
menuContentVariants,
|
|
290
343
|
menuItemVariants
|
|
291
344
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../dropdown-menu/src/dropdown-menu.ts", "../../dropdown-menu/src/dropdown-menu.styles.ts", "../../react-dropdown-menu/src/dropdown-menu.tsx"],
|
|
4
|
-
"sourcesContent": ["import type { AccessibilityProps, KeyboardHandlerMap } from '@refraction-ui/shared'\nimport { createMachine, Keys, generateId } from '@refraction-ui/shared'\n\nexport interface DropdownMenuProps {\n /** Whether the menu is open (controlled) */\n open?: boolean\n /** Whether the menu starts open (uncontrolled) */\n defaultOpen?: boolean\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void\n}\n\nexport interface MenuItemProps {\n /** Whether the item is disabled */\n disabled?: boolean\n /** Callback when the item is selected */\n onSelect?: () => void\n}\n\nexport interface DropdownMenuState {\n open: boolean\n}\n\nexport interface DropdownMenuAPI {\n /** Current menu state */\n state: DropdownMenuState\n /** ARIA attributes for the trigger button */\n triggerProps: Partial<AccessibilityProps> & Record<string, unknown>\n /** ARIA attributes for the menu content */\n contentProps: Partial<AccessibilityProps> & Record<string, unknown>\n /** Factory for menu item props */\n getItemProps(item: MenuItemProps): Partial<AccessibilityProps> & Record<string, unknown>\n /** Open the menu */\n open(): void\n /** Close the menu */\n close(): void\n /** Toggle the menu */\n toggle(): void\n /** Keyboard handlers for the menu */\n keyboardHandlers: KeyboardHandlerMap\n /** Generated IDs for aria linking */\n ids: {\n content: string\n }\n}\n\nexport function createDropdownMenu(props: DropdownMenuProps = {}): DropdownMenuAPI {\n const { open: controlledOpen, defaultOpen = false, onOpenChange } = props\n\n const machine = createMachine<'open' | 'closed', 'OPEN' | 'CLOSE'>({\n initial: controlledOpen ?? defaultOpen ? 'open' : 'closed',\n states: {\n closed: { on: { OPEN: 'open' } },\n open: { on: { CLOSE: 'closed' } },\n },\n })\n\n const contentId = generateId('rfr-dropdown-menu')\n\n function isOpen(): boolean {\n if (controlledOpen !== undefined) return controlledOpen\n return machine.matches('open')\n }\n\n function openMenu(): void {\n machine.send('OPEN')\n onOpenChange?.(true)\n }\n\n function closeMenu(): void {\n machine.send('CLOSE')\n onOpenChange?.(false)\n }\n\n function toggleMenu(): void {\n if (isOpen()) {\n closeMenu()\n } else {\n openMenu()\n }\n }\n\n function getItemProps(item: MenuItemProps): Partial<AccessibilityProps> & Record<string, unknown> {\n return {\n role: 'menuitem',\n tabIndex: item.disabled ? -1 : 0,\n 'data-disabled': item.disabled ? '' : undefined,\n 'aria-disabled': item.disabled || undefined,\n }\n }\n\n const keyboardHandlers: KeyboardHandlerMap = {\n [Keys.Escape]: (e: KeyboardEvent) => {\n e.preventDefault()\n closeMenu()\n },\n [Keys.ArrowDown]: (e: KeyboardEvent) => {\n e.preventDefault()\n },\n [Keys.ArrowUp]: (e: KeyboardEvent) => {\n e.preventDefault()\n },\n [Keys.Enter]: (e: KeyboardEvent) => {\n e.preventDefault()\n },\n [Keys.Home]: (e: KeyboardEvent) => {\n e.preventDefault()\n },\n [Keys.End]: (e: KeyboardEvent) => {\n e.preventDefault()\n },\n }\n\n const triggerProps: Partial<AccessibilityProps> & Record<string, unknown> = {\n 'aria-expanded': isOpen(),\n 'aria-controls': contentId,\n 'aria-haspopup': 'menu',\n }\n\n const contentProps: Partial<AccessibilityProps> & Record<string, unknown> = {\n role: 'menu',\n id: contentId,\n 'data-state': isOpen() ? 'open' : 'closed',\n }\n\n return {\n state: { open: isOpen() },\n triggerProps,\n contentProps,\n getItemProps,\n open: openMenu,\n close: closeMenu,\n toggle: toggleMenu,\n keyboardHandlers,\n ids: {\n content: contentId,\n },\n }\n}\n", "import { cva } from '@refraction-ui/shared'\n\nexport const menuContentVariants = cva({\n base: 'z-50 rounded-md border bg-popover shadow-md',\n})\n\nexport const menuItemVariants = cva({\n base: 'relative flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n})\n", "import * as React from 'react'\nimport * as ReactDOM from 'react-dom'\nimport {\n createDropdownMenu,\n menuContentVariants,\n menuItemVariants,\n type DropdownMenuProps as CoreDropdownMenuProps,\n type MenuItemProps as CoreMenuItemProps,\n} from '@refraction-ui/dropdown-menu'\nimport { cn, createKeyboardHandler, devWarn } from '@refraction-ui/shared'\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ninterface DropdownMenuContextValue {\n open: boolean\n onOpenChange: (open: boolean) => void\n contentId: string\n}\n\nconst DropdownMenuContext = React.createContext<DropdownMenuContextValue | null>(null)\n\nfunction useDropdownMenuContext(): DropdownMenuContextValue {\n const ctx = React.useContext(DropdownMenuContext)\n if (!ctx) {\n devWarn(\n 'react-dropdown-menu/context-outside-provider',\n 'DropdownMenu compound components (DropdownMenuTrigger/DropdownMenuContent/DropdownMenuItem/etc.) must be rendered inside a <DropdownMenu>. The missing DropdownMenuContext makes this throw.',\n )\n throw new Error('DropdownMenu compound components must be used within <DropdownMenu>')\n }\n return ctx\n}\n\n// ---------------------------------------------------------------------------\n// DropdownMenu (root provider)\n// ---------------------------------------------------------------------------\n\nexport interface DropdownMenuProps {\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n children?: React.ReactNode\n}\n\nexport function DropdownMenu({\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n children,\n}: DropdownMenuProps) {\n const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)\n const isControlled = controlledOpen !== undefined\n const open = isControlled ? controlledOpen : uncontrolledOpen\n\n const handleOpenChange = React.useCallback(\n (next: boolean) => {\n if (!isControlled) {\n setUncontrolledOpen(next)\n }\n onOpenChange?.(next)\n },\n [isControlled, onOpenChange],\n )\n\n // Use the headless core to get stable IDs\n const apiRef = React.useRef<ReturnType<typeof createDropdownMenu> | null>(null)\n if (apiRef.current === null) {\n apiRef.current = createDropdownMenu({ open })\n }\n const api = apiRef.current\n\n const ctx = React.useMemo<DropdownMenuContextValue>(\n () => ({\n open,\n onOpenChange: handleOpenChange,\n contentId: api.ids.content,\n }),\n [open, handleOpenChange, api.ids.content],\n )\n\n return React.createElement(DropdownMenuContext.Provider, { value: ctx }, children)\n}\n\n// ---------------------------------------------------------------------------\n// DropdownMenuTrigger\n// ---------------------------------------------------------------------------\n\nexport interface DropdownMenuTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Merge the trigger's props/behavior onto the child element instead of\n * rendering a `<button>` around it (Radix-style `asChild`). Use it with\n * styled triggers like `<Button>` — otherwise you'd nest a button in a\n * button (invalid HTML and a hydration error). */\n asChild?: boolean\n}\n\nexport const DropdownMenuTrigger = React.forwardRef<HTMLButtonElement, DropdownMenuTriggerProps>(\n function DropdownMenuTrigger({ asChild = false, onClick, children, ...props }, ref) {\n const { open, onOpenChange, contentId } = useDropdownMenuContext()\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n onOpenChange(!open)\n onClick?.(e)\n }\n\n const triggerProps = {\n type: 'button' as const,\n 'aria-expanded': open,\n 'aria-controls': contentId,\n 'aria-haspopup': 'menu' as const,\n onClick: handleClick,\n ...props,\n }\n\n if (asChild) {\n if (!React.isValidElement(children)) {\n devWarn(\n 'dropdown-menu-trigger-asChild-child',\n 'DropdownMenuTrigger: `asChild` expects a single React element child; rendering nothing.',\n )\n return null\n }\n const child = children as React.ReactElement<\n Record<string, unknown>\n > & { ref?: React.Ref<HTMLButtonElement> }\n const childOnClick = child.props.onClick as\n | ((e: React.MouseEvent<HTMLButtonElement>) => void)\n | undefined\n const childRef = child.ref\n const mergedProps = {\n ...triggerProps,\n onClick: (e: React.MouseEvent<HTMLButtonElement>) => {\n childOnClick?.(e)\n if (!e.defaultPrevented) handleClick(e)\n },\n ref: (node: HTMLButtonElement | null) => {\n if (typeof ref === 'function') ref(node)\n else if (ref) ref.current = node\n if (typeof childRef === 'function') childRef(node)\n else if (childRef) childRef.current = node\n },\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return React.cloneElement(child, mergedProps as any)\n }\n\n return React.createElement('button', { ref, ...triggerProps }, children)\n },\n)\n\n// ---------------------------------------------------------------------------\n// DropdownMenuContent\n// ---------------------------------------------------------------------------\n\nexport interface DropdownMenuContentProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nexport const DropdownMenuContent = React.forwardRef<HTMLDivElement, DropdownMenuContentProps>(\n function DropdownMenuContent({ className, children, onKeyDown, ...props }, ref) {\n const { open, onOpenChange, contentId } = useDropdownMenuContext()\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n const handler = createKeyboardHandler({\n Escape: (ev) => {\n ev.preventDefault()\n onOpenChange(false)\n },\n })\n handler(e.nativeEvent)\n onKeyDown?.(e)\n }\n\n if (!open) return null\n\n const content = React.createElement(\n 'div',\n {\n ref,\n role: 'menu',\n id: contentId,\n 'data-state': open ? 'open' : 'closed',\n className: cn(menuContentVariants(), className),\n onKeyDown: handleKeyDown,\n tabIndex: -1,\n ...props,\n },\n children,\n )\n\n // Render via portal if document is available (client-side)\n if (typeof document !== 'undefined') {\n return ReactDOM.createPortal(content, document.body)\n }\n\n // SSR fallback: render inline\n return content\n },\n)\n\n// ---------------------------------------------------------------------------\n// DropdownMenuItem\n// ---------------------------------------------------------------------------\n\nexport interface DropdownMenuItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {\n disabled?: boolean\n onSelect?: () => void\n}\n\nexport const DropdownMenuItem = React.forwardRef<HTMLDivElement, DropdownMenuItemProps>(\n function DropdownMenuItem({ className, disabled, onSelect, onClick, children, ...props }, ref) {\n const { onOpenChange } = useDropdownMenuContext()\n\n const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {\n if (disabled) return\n onSelect?.()\n onOpenChange(false)\n onClick?.(e)\n }\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onSelect?.()\n onOpenChange(false)\n }\n }\n\n return React.createElement(\n 'div',\n {\n ref,\n role: 'menuitem',\n tabIndex: disabled ? -1 : 0,\n 'data-disabled': disabled ? '' : undefined,\n 'aria-disabled': disabled || undefined,\n className: cn(menuItemVariants(), className),\n onClick: handleClick,\n onKeyDown: handleKeyDown,\n ...props,\n },\n children,\n )\n },\n)\n\n// ---------------------------------------------------------------------------\n// DropdownMenuSeparator\n// ---------------------------------------------------------------------------\n\nexport interface DropdownMenuSeparatorProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nexport const DropdownMenuSeparator = React.forwardRef<HTMLDivElement, DropdownMenuSeparatorProps>(\n function DropdownMenuSeparator({ className, ...props }, ref) {\n return React.createElement('div', {\n ref,\n role: 'separator',\n className: cn('-mx-1 my-1 h-px bg-muted', className),\n ...props,\n })\n },\n)\n\n// ---------------------------------------------------------------------------\n// DropdownMenuLabel\n// ---------------------------------------------------------------------------\n\nexport interface DropdownMenuLabelProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nexport const DropdownMenuLabel = React.forwardRef<HTMLDivElement, DropdownMenuLabelProps>(\n function DropdownMenuLabel({ className, ...props }, ref) {\n return React.createElement('div', {\n ref,\n className: cn('px-2 py-1.5 text-sm font-semibold', className),\n ...props,\n })\n },\n)\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;AA8CO,SAAS,mBAAmB,QAA2B,CAAA,GAAqB;AACjF,QAAM,EAAE,MAAM,gBAAgB,cAAc,OAAO,aAAA,IAAiB;AAEpE,QAAM,UAAU,cAAmD;IACjE,SAAS,kBAAkB,cAAc,SAAS;IAClD,QAAQ;MACN,QAAQ,EAAE,IAAI,EAAE,MAAM,OAAA,EAAO;MAC7B,MAAM,EAAE,IAAI,EAAE,OAAO,SAAA,EAAS;IAAE;EAClC,CACD;AAED,QAAM,YAAY,WAAW,mBAAmB;AAEhD,WAAS,SAAkB;AACzB,QAAI,mBAAmB,OAAW,QAAO;AACzC,WAAO,QAAQ,QAAQ,MAAM;EAC/B;AAEA,WAAS,WAAiB;AACxB,YAAQ,KAAK,MAAM;AACnB,mBAAe,IAAI;EACrB;AAEA,WAAS,YAAkB;AACzB,YAAQ,KAAK,OAAO;AACpB,mBAAe,KAAK;EACtB;AAEA,WAAS,aAAmB;AAC1B,QAAI,OAAA,GAAU;AACZ,gBAAA;IACF,OAAO;AACL,eAAA;IACF;EACF;AAEA,WAAS,aAAa,MAA4E;AAChG,WAAO;MACL,MAAM;MACN,UAAU,KAAK,WAAW,KAAK;MAC/B,iBAAiB,KAAK,WAAW,KAAK;MACtC,iBAAiB,KAAK,YAAY;IAAA;EAEtC;AAEA,QAAM,mBAAuC;IAC3C,CAAC,KAAK,MAAM,GAAG,CAAC,MAAqB;AACnC,QAAE,eAAA;AACF,gBAAA;IACF;IACA,CAAC,KAAK,SAAS,GAAG,CAAC,MAAqB;AACtC,QAAE,eAAA;IACJ;IACA,CAAC,KAAK,OAAO,GAAG,CAAC,MAAqB;AACpC,QAAE,eAAA;IACJ;IACA,CAAC,KAAK,KAAK,GAAG,CAAC,MAAqB;AAClC,QAAE,eAAA;IACJ;IACA,CAAC,KAAK,IAAI,GAAG,CAAC,MAAqB;AACjC,QAAE,eAAA;IACJ;IACA,CAAC,KAAK,GAAG,GAAG,CAAC,MAAqB;AAChC,QAAE,eAAA;IACJ;EAAA;AAGF,QAAM,eAAsE;IAC1E,iBAAiB,OAAA;IACjB,iBAAiB;IACjB,iBAAiB;EAAA;AAGnB,QAAM,eAAsE;IAC1E,MAAM;IACN,IAAI;IACJ,cAAc,OAAA,IAAW,SAAS;EAAA;AAGpC,SAAO;IACL,OAAO,EAAE,MAAM,OAAA,EAAO;IACtB;IACA;IACA;IACA,MAAM;IACN,OAAO;IACP,QAAQ;IACR;IACA,KAAK;MACH,SAAS;IAAA;EACX;AAEJ;ACxIO,IAAM,sBAAsB,IAAI;EACrC,MAAM;AACR,CAAC;AAEM,IAAM,mBAAmB,IAAI;EAClC,MAAM;AACR,CAAC;;;
|
|
6
|
-
"names": ["DropdownMenuTrigger", "DropdownMenuContent", "DropdownMenuItem", "DropdownMenuSeparator", "DropdownMenuLabel"]
|
|
4
|
+
"sourcesContent": ["import type { AccessibilityProps, KeyboardHandlerMap } from '@refraction-ui/shared'\nimport { createMachine, Keys, generateId } from '@refraction-ui/shared'\n\nexport interface DropdownMenuProps {\n /** Whether the menu is open (controlled) */\n open?: boolean\n /** Whether the menu starts open (uncontrolled) */\n defaultOpen?: boolean\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void\n}\n\nexport interface MenuItemProps {\n /** Whether the item is disabled */\n disabled?: boolean\n /** Callback when the item is selected */\n onSelect?: () => void\n}\n\nexport interface DropdownMenuState {\n open: boolean\n}\n\nexport interface DropdownMenuAPI {\n /** Current menu state */\n state: DropdownMenuState\n /** ARIA attributes for the trigger button */\n triggerProps: Partial<AccessibilityProps> & Record<string, unknown>\n /** ARIA attributes for the menu content */\n contentProps: Partial<AccessibilityProps> & Record<string, unknown>\n /** Factory for menu item props */\n getItemProps(item: MenuItemProps): Partial<AccessibilityProps> & Record<string, unknown>\n /** Open the menu */\n open(): void\n /** Close the menu */\n close(): void\n /** Toggle the menu */\n toggle(): void\n /** Keyboard handlers for the menu */\n keyboardHandlers: KeyboardHandlerMap\n /** Generated IDs for aria linking */\n ids: {\n content: string\n }\n}\n\nexport function createDropdownMenu(props: DropdownMenuProps = {}): DropdownMenuAPI {\n const { open: controlledOpen, defaultOpen = false, onOpenChange } = props\n\n const machine = createMachine<'open' | 'closed', 'OPEN' | 'CLOSE'>({\n initial: controlledOpen ?? defaultOpen ? 'open' : 'closed',\n states: {\n closed: { on: { OPEN: 'open' } },\n open: { on: { CLOSE: 'closed' } },\n },\n })\n\n const contentId = generateId('rfr-dropdown-menu')\n\n function isOpen(): boolean {\n if (controlledOpen !== undefined) return controlledOpen\n return machine.matches('open')\n }\n\n function openMenu(): void {\n machine.send('OPEN')\n onOpenChange?.(true)\n }\n\n function closeMenu(): void {\n machine.send('CLOSE')\n onOpenChange?.(false)\n }\n\n function toggleMenu(): void {\n if (isOpen()) {\n closeMenu()\n } else {\n openMenu()\n }\n }\n\n function getItemProps(item: MenuItemProps): Partial<AccessibilityProps> & Record<string, unknown> {\n return {\n role: 'menuitem',\n tabIndex: item.disabled ? -1 : 0,\n 'data-disabled': item.disabled ? '' : undefined,\n 'aria-disabled': item.disabled || undefined,\n }\n }\n\n const keyboardHandlers: KeyboardHandlerMap = {\n [Keys.Escape]: (e: KeyboardEvent) => {\n e.preventDefault()\n closeMenu()\n },\n [Keys.ArrowDown]: (e: KeyboardEvent) => {\n e.preventDefault()\n },\n [Keys.ArrowUp]: (e: KeyboardEvent) => {\n e.preventDefault()\n },\n [Keys.Enter]: (e: KeyboardEvent) => {\n e.preventDefault()\n },\n [Keys.Home]: (e: KeyboardEvent) => {\n e.preventDefault()\n },\n [Keys.End]: (e: KeyboardEvent) => {\n e.preventDefault()\n },\n }\n\n const triggerProps: Partial<AccessibilityProps> & Record<string, unknown> = {\n 'aria-expanded': isOpen(),\n 'aria-controls': contentId,\n 'aria-haspopup': 'menu',\n }\n\n const contentProps: Partial<AccessibilityProps> & Record<string, unknown> = {\n role: 'menu',\n id: contentId,\n 'data-state': isOpen() ? 'open' : 'closed',\n }\n\n return {\n state: { open: isOpen() },\n triggerProps,\n contentProps,\n getItemProps,\n open: openMenu,\n close: closeMenu,\n toggle: toggleMenu,\n keyboardHandlers,\n ids: {\n content: contentId,\n },\n }\n}\n", "import { cva } from '@refraction-ui/shared'\n\nexport const menuContentVariants = cva({\n base: 'z-50 rounded-md border bg-popover shadow-md',\n})\n\nexport const menuItemVariants = cva({\n base: 'relative flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n})\n", "import * as React from 'react'\nimport * as ReactDOM from 'react-dom'\nimport {\n createDropdownMenu,\n menuContentVariants,\n menuItemVariants,\n type DropdownMenuProps as CoreDropdownMenuProps,\n type MenuItemProps as CoreMenuItemProps,\n} from '@refraction-ui/dropdown-menu'\nimport { cn, createKeyboardHandler, devWarn } from '@refraction-ui/shared'\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ninterface DropdownMenuContextValue {\n open: boolean\n onOpenChange: (open: boolean) => void\n contentId: string\n /** The trigger element, so the portalled content can anchor to it. */\n triggerRef: React.MutableRefObject<HTMLElement | null>\n}\n\nconst DropdownMenuContext = React.createContext<DropdownMenuContextValue | null>(null)\n\nfunction useDropdownMenuContext(): DropdownMenuContextValue {\n const ctx = React.useContext(DropdownMenuContext)\n if (!ctx) {\n devWarn(\n 'react-dropdown-menu/context-outside-provider',\n 'DropdownMenu compound components (DropdownMenuTrigger/DropdownMenuContent/DropdownMenuItem/etc.) must be rendered inside a <DropdownMenu>. The missing DropdownMenuContext makes this throw.',\n )\n throw new Error('DropdownMenu compound components must be used within <DropdownMenu>')\n }\n return ctx\n}\n\n// ---------------------------------------------------------------------------\n// DropdownMenu (root provider)\n// ---------------------------------------------------------------------------\n\nexport interface DropdownMenuProps {\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n children?: React.ReactNode\n}\n\nexport function DropdownMenu({\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n children,\n}: DropdownMenuProps) {\n const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)\n const isControlled = controlledOpen !== undefined\n const open = isControlled ? controlledOpen : uncontrolledOpen\n\n const handleOpenChange = React.useCallback(\n (next: boolean) => {\n if (!isControlled) {\n setUncontrolledOpen(next)\n }\n onOpenChange?.(next)\n },\n [isControlled, onOpenChange],\n )\n\n // Use the headless core to get stable IDs\n const apiRef = React.useRef<ReturnType<typeof createDropdownMenu> | null>(null)\n if (apiRef.current === null) {\n apiRef.current = createDropdownMenu({ open })\n }\n const api = apiRef.current\n\n const triggerRef = React.useRef<HTMLElement | null>(null)\n\n const ctx = React.useMemo<DropdownMenuContextValue>(\n () => ({\n open,\n onOpenChange: handleOpenChange,\n contentId: api.ids.content,\n triggerRef,\n }),\n [open, handleOpenChange, api.ids.content],\n )\n\n return React.createElement(DropdownMenuContext.Provider, { value: ctx }, children)\n}\n\n// ---------------------------------------------------------------------------\n// DropdownMenuTrigger\n// ---------------------------------------------------------------------------\n\nexport interface DropdownMenuTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Merge the trigger's props/behavior onto the child element instead of\n * rendering a `<button>` around it (Radix-style `asChild`). Use it with\n * styled triggers like `<Button>` — otherwise you'd nest a button in a\n * button (invalid HTML and a hydration error). */\n asChild?: boolean\n}\n\nexport const DropdownMenuTrigger = React.forwardRef<HTMLButtonElement, DropdownMenuTriggerProps>(\n function DropdownMenuTrigger({ asChild = false, onClick, children, ...props }, ref) {\n const { open, onOpenChange, contentId, triggerRef } = useDropdownMenuContext()\n const setRefs = (node: HTMLButtonElement | null) => {\n triggerRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) ref.current = node\n }\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n onOpenChange(!open)\n onClick?.(e)\n }\n\n const triggerProps = {\n type: 'button' as const,\n 'aria-expanded': open,\n 'aria-controls': contentId,\n 'aria-haspopup': 'menu' as const,\n onClick: handleClick,\n ...props,\n }\n\n if (asChild) {\n if (!React.isValidElement(children)) {\n devWarn(\n 'dropdown-menu-trigger-asChild-child',\n 'DropdownMenuTrigger: `asChild` expects a single React element child; rendering nothing.',\n )\n return null\n }\n const child = children as React.ReactElement<\n Record<string, unknown>\n > & { ref?: React.Ref<HTMLButtonElement> }\n const childOnClick = child.props.onClick as\n | ((e: React.MouseEvent<HTMLButtonElement>) => void)\n | undefined\n const childRef = child.ref\n const mergedProps = {\n ...triggerProps,\n onClick: (e: React.MouseEvent<HTMLButtonElement>) => {\n childOnClick?.(e)\n if (!e.defaultPrevented) handleClick(e)\n },\n ref: (node: HTMLButtonElement | null) => {\n setRefs(node)\n if (typeof childRef === 'function') childRef(node)\n else if (childRef) childRef.current = node\n },\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return React.cloneElement(child, mergedProps as any)\n }\n\n return React.createElement('button', { ref: setRefs, ...triggerProps }, children)\n },\n)\n\n// ---------------------------------------------------------------------------\n// Positioning\n// ---------------------------------------------------------------------------\n\nexport type DropdownMenuAlign = 'start' | 'center' | 'end'\n\nconst DEFAULT_SIDE_OFFSET = 4\n\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect\n\n/**\n * Fixed-position coordinates that place a menu under its trigger, aligned to\n * the trigger's start, centre or end edge. Pure, so it is unit-testable.\n */\nexport function computeMenuPosition(\n trigger: { top: number; bottom: number; left: number; right: number; width: number },\n contentWidth: number,\n align: DropdownMenuAlign,\n sideOffset: number,\n): React.CSSProperties {\n const left =\n align === 'end'\n ? trigger.right - contentWidth\n : align === 'center'\n ? trigger.left + trigger.width / 2 - contentWidth / 2\n : trigger.left\n return { position: 'fixed', top: trigger.bottom + sideOffset, left: Math.max(0, left), minWidth: trigger.width }\n}\n\n// ---------------------------------------------------------------------------\n// DropdownMenuContent\n// ---------------------------------------------------------------------------\n\nexport interface DropdownMenuContentProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Horizontal alignment to the trigger. Defaults to `start`. */\n align?: DropdownMenuAlign\n /** Gap between trigger and menu in px. Defaults to 4. */\n sideOffset?: number\n}\n\nexport const DropdownMenuContent = React.forwardRef<HTMLDivElement, DropdownMenuContentProps>(\n function DropdownMenuContent(\n { className, children, onKeyDown, align = 'start', sideOffset = DEFAULT_SIDE_OFFSET, style, ...props },\n forwardedRef,\n ) {\n const { open, onOpenChange, contentId, triggerRef } = useDropdownMenuContext()\n const contentRef = React.useRef<HTMLDivElement | null>(null)\n const ref = React.useCallback(\n (node: HTMLDivElement | null) => {\n contentRef.current = node\n if (typeof forwardedRef === 'function') forwardedRef(node)\n else if (forwardedRef) forwardedRef.current = node\n },\n [forwardedRef],\n )\n const [position, setPosition] = React.useState<React.CSSProperties | null>(null)\n\n // The content is portalled to <body>, so anchor it to the trigger with\n // fixed coordinates, and follow the trigger on scroll / resize.\n useIsomorphicLayoutEffect(() => {\n if (!open) return\n const update = () => {\n const trigger = triggerRef.current\n const content = contentRef.current\n if (!trigger) return\n setPosition(\n computeMenuPosition(trigger.getBoundingClientRect(), content?.offsetWidth ?? 0, align, sideOffset),\n )\n }\n update()\n window.addEventListener('resize', update)\n window.addEventListener('scroll', update, true)\n return () => {\n window.removeEventListener('resize', update)\n window.removeEventListener('scroll', update, true)\n }\n }, [open, align, sideOffset, triggerRef])\n\n // Close on a pointer press outside the menu and its trigger.\n React.useEffect(() => {\n if (!open) return\n const onPointerDown = (event: PointerEvent) => {\n const target = event.target as Node\n if (contentRef.current?.contains(target) || triggerRef.current?.contains(target)) return\n onOpenChange(false)\n }\n document.addEventListener('pointerdown', onPointerDown)\n return () => document.removeEventListener('pointerdown', onPointerDown)\n }, [open, onOpenChange, triggerRef])\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n const handler = createKeyboardHandler({\n Escape: (ev) => {\n ev.preventDefault()\n onOpenChange(false)\n },\n })\n handler(e.nativeEvent)\n onKeyDown?.(e)\n }\n\n if (!open) return null\n\n const content = React.createElement(\n 'div',\n {\n ref,\n role: 'menu',\n id: contentId,\n 'data-state': open ? 'open' : 'closed',\n className: cn(menuContentVariants(), className),\n style: position ? { ...position, ...style } : style,\n 'data-align': align,\n onKeyDown: handleKeyDown,\n tabIndex: -1,\n ...props,\n },\n children,\n )\n\n // Render via portal if document is available (client-side)\n if (typeof document !== 'undefined') {\n return ReactDOM.createPortal(content, document.body)\n }\n\n // SSR fallback: render inline\n return content\n },\n)\n\n// ---------------------------------------------------------------------------\n// DropdownMenuItem\n// ---------------------------------------------------------------------------\n\nexport interface DropdownMenuItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {\n disabled?: boolean\n onSelect?: () => void\n}\n\nexport const DropdownMenuItem = React.forwardRef<HTMLDivElement, DropdownMenuItemProps>(\n function DropdownMenuItem({ className, disabled, onSelect, onClick, children, ...props }, ref) {\n const { onOpenChange } = useDropdownMenuContext()\n\n const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {\n if (disabled) return\n onSelect?.()\n onOpenChange(false)\n onClick?.(e)\n }\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onSelect?.()\n onOpenChange(false)\n }\n }\n\n return React.createElement(\n 'div',\n {\n ref,\n role: 'menuitem',\n tabIndex: disabled ? -1 : 0,\n 'data-disabled': disabled ? '' : undefined,\n 'aria-disabled': disabled || undefined,\n className: cn(menuItemVariants(), className),\n onClick: handleClick,\n onKeyDown: handleKeyDown,\n ...props,\n },\n children,\n )\n },\n)\n\n// ---------------------------------------------------------------------------\n// DropdownMenuSeparator\n// ---------------------------------------------------------------------------\n\nexport interface DropdownMenuSeparatorProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nexport const DropdownMenuSeparator = React.forwardRef<HTMLDivElement, DropdownMenuSeparatorProps>(\n function DropdownMenuSeparator({ className, ...props }, ref) {\n return React.createElement('div', {\n ref,\n role: 'separator',\n className: cn('-mx-1 my-1 h-px bg-muted', className),\n ...props,\n })\n },\n)\n\n// ---------------------------------------------------------------------------\n// DropdownMenuLabel\n// ---------------------------------------------------------------------------\n\nexport interface DropdownMenuLabelProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nexport const DropdownMenuLabel = React.forwardRef<HTMLDivElement, DropdownMenuLabelProps>(\n function DropdownMenuLabel({ className, ...props }, ref) {\n return React.createElement('div', {\n ref,\n className: cn('px-2 py-1.5 text-sm font-semibold', className),\n ...props,\n })\n },\n)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["DropdownMenuTrigger", "DropdownMenuContent", "content", "DropdownMenuItem", "DropdownMenuSeparator", "DropdownMenuLabel"]
|
|
7
7
|
}
|
package/dist/react-line-chart.js
CHANGED
|
@@ -16,11 +16,24 @@ var DEFAULT_LINE_CHART_WIDTH = 760;
|
|
|
16
16
|
var DEFAULT_LINE_CHART_HEIGHT = 230;
|
|
17
17
|
var DEFAULT_TICK_COUNT = 4;
|
|
18
18
|
var DEFAULT_HEADROOM = 1.08;
|
|
19
|
-
var
|
|
19
|
+
var MIN_LABEL_SPACING = 48;
|
|
20
20
|
var CHART_TOKEN_COUNT = 5;
|
|
21
21
|
function lineChartColor(index) {
|
|
22
22
|
return `hsl(var(--chart-${index % CHART_TOKEN_COUNT + 1}))`;
|
|
23
23
|
}
|
|
24
|
+
var NICE_STEPS = [1, 2, 5, 10];
|
|
25
|
+
function niceScale(rawMax, tickCount) {
|
|
26
|
+
const intervals = Math.max(1, Math.floor(tickCount));
|
|
27
|
+
if (!(rawMax > 0)) return { max: 1, step: 1 / intervals, ticks: Array.from({ length: intervals + 1 }, (_, i) => i / intervals) };
|
|
28
|
+
const target = rawMax / intervals;
|
|
29
|
+
const magnitude = 10 ** Math.floor(Math.log10(target));
|
|
30
|
+
const step = NICE_STEPS.map((m) => m * magnitude).find((candidate) => candidate >= target) ?? 10 * magnitude;
|
|
31
|
+
const count = Math.ceil(rawMax / step - 1e-9);
|
|
32
|
+
const decimals = Math.max(0, -Math.floor(Math.log10(step)));
|
|
33
|
+
const clean = (value) => Number(value.toFixed(decimals));
|
|
34
|
+
const ticks = Array.from({ length: count + 1 }, (_, i) => clean(i * step));
|
|
35
|
+
return { max: ticks[ticks.length - 1], step: clean(step), ticks };
|
|
36
|
+
}
|
|
24
37
|
function pathFrom(points) {
|
|
25
38
|
return points.map(([px, py], i) => (i ? "L" : "M") + px.toFixed(1) + " " + py.toFixed(1)).join(" ");
|
|
26
39
|
}
|
|
@@ -31,24 +44,22 @@ function computeLineChart(options) {
|
|
|
31
44
|
width,
|
|
32
45
|
height,
|
|
33
46
|
tickCount = DEFAULT_TICK_COUNT,
|
|
34
|
-
headroom = DEFAULT_HEADROOM
|
|
35
|
-
labelEvery = DEFAULT_LABEL_EVERY
|
|
47
|
+
headroom = DEFAULT_HEADROOM
|
|
36
48
|
} = options;
|
|
37
49
|
const padding = { ...DEFAULT_LINE_CHART_PADDING, ...options.padding };
|
|
38
50
|
const n = labels.length;
|
|
39
51
|
const values = series.flatMap((s) => s.data);
|
|
40
52
|
const peak = values.length ? Math.max(...values) : 0;
|
|
41
|
-
const
|
|
53
|
+
const scale = niceScale(peak > 0 ? peak * headroom : 0, tickCount);
|
|
54
|
+
const max = scale.max;
|
|
42
55
|
const plotW = width - padding.left - padding.right;
|
|
43
56
|
const plotTop = padding.top;
|
|
44
57
|
const plotBottom = height - padding.bottom;
|
|
45
58
|
const plotH = plotBottom - plotTop;
|
|
59
|
+
const labelEvery = options.labelEvery ?? Math.max(1, Math.ceil(n / Math.max(1, Math.floor(plotW / MIN_LABEL_SPACING))));
|
|
46
60
|
const x = (index) => padding.left + (n > 1 ? index / (n - 1) * plotW : plotW / 2);
|
|
47
61
|
const y = (value) => plotTop + (1 - value / max) * plotH;
|
|
48
|
-
const ticks =
|
|
49
|
-
const value = max / tickCount * t;
|
|
50
|
-
return { value, y: y(value) };
|
|
51
|
-
});
|
|
62
|
+
const ticks = scale.ticks.map((value) => ({ value, y: y(value) }));
|
|
52
63
|
const xLabels = labels.map((label, index) => ({ index, label, x: x(index) })).filter(({ index }) => index % labelEvery === 0 || index === n - 1);
|
|
53
64
|
const paths = series.map((s, si) => {
|
|
54
65
|
const points = s.data.map((v, i) => [x(i), y(v)]);
|
|
@@ -107,19 +118,46 @@ var defaultFormat = (value) => String(Math.round(value));
|
|
|
107
118
|
var MARKER_RADIUS = 3.5;
|
|
108
119
|
var STROKE_WIDTH = 2.2;
|
|
109
120
|
var TOOLTIP_TOP_PX = 8;
|
|
121
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
|
|
122
|
+
function useMeasuredWidth(ref) {
|
|
123
|
+
const [measured, setMeasured] = React.useState(null);
|
|
124
|
+
useIsomorphicLayoutEffect(() => {
|
|
125
|
+
const node = ref.current;
|
|
126
|
+
if (!node || typeof ResizeObserver === "undefined") return;
|
|
127
|
+
const apply = (w) => {
|
|
128
|
+
const rounded = Math.round(w);
|
|
129
|
+
if (rounded > 0) setMeasured((prev) => prev === rounded ? prev : rounded);
|
|
130
|
+
};
|
|
131
|
+
apply(node.getBoundingClientRect().width);
|
|
132
|
+
const observer = new ResizeObserver((entries) => apply(entries[0]?.contentRect.width ?? 0));
|
|
133
|
+
observer.observe(node);
|
|
134
|
+
return () => observer.disconnect();
|
|
135
|
+
}, [ref]);
|
|
136
|
+
return measured;
|
|
137
|
+
}
|
|
110
138
|
var LineChart = React.forwardRef(function LineChart2({
|
|
111
139
|
series,
|
|
112
140
|
labels,
|
|
113
141
|
ariaLabel,
|
|
114
142
|
height = DEFAULT_LINE_CHART_HEIGHT,
|
|
115
|
-
width = DEFAULT_LINE_CHART_WIDTH,
|
|
143
|
+
width: fallbackWidth = DEFAULT_LINE_CHART_WIDTH,
|
|
116
144
|
formatValue = defaultFormat,
|
|
117
145
|
formatTick,
|
|
118
146
|
area,
|
|
119
147
|
showLegend = true,
|
|
120
148
|
className,
|
|
121
149
|
...rest
|
|
122
|
-
},
|
|
150
|
+
}, forwardedRef) {
|
|
151
|
+
const rootRef = React.useRef(null);
|
|
152
|
+
const ref = React.useCallback(
|
|
153
|
+
(node) => {
|
|
154
|
+
rootRef.current = node;
|
|
155
|
+
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
156
|
+
else if (forwardedRef) forwardedRef.current = node;
|
|
157
|
+
},
|
|
158
|
+
[forwardedRef]
|
|
159
|
+
);
|
|
160
|
+
const width = useMeasuredWidth(rootRef) ?? fallbackWidth;
|
|
123
161
|
const [active, setActive] = React.useState(null);
|
|
124
162
|
const gradientBase = React.useId().replace(/:/g, "");
|
|
125
163
|
const geo = React.useMemo(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../line-chart/src/line-chart.ts", "../../line-chart/src/line-chart.styles.ts", "../../react-line-chart/src/line-chart.tsx"],
|
|
4
|
-
"sourcesContent": ["export interface LineChartSeries {\n /** Stable key for the series. */\n id: string\n /** Legend / tooltip name. */\n name: string\n /**\n * Any CSS colour (a token `hsl(var(--…))` or a data colour). Defaults to the\n * theme's chart palette (`--chart-1` … `--chart-5`) by series position.\n */\n color?: string\n /** One value per label, in label order. */\n data: number[]\n}\n\nexport interface LineChartPadding {\n top: number\n right: number\n bottom: number\n left: number\n}\n\nexport interface LineChartOptions {\n series: LineChartSeries[]\n labels: string[]\n /** viewBox width — the SVG scales to its container's width. */\n width: number\n /** viewBox height. */\n height: number\n padding?: Partial<LineChartPadding>\n /** Number of gridline intervals on the y-axis. */\n tickCount?: number\n /** Headroom multiplier above the largest value. */\n headroom?: number\n /** Show every n-th x label (the last label is always shown). */\n labelEvery?: number\n}\n\nexport interface LineChartTick {\n value: number\n y: number\n}\n\nexport interface LineChartXLabel {\n index: number\n label: string\n x: number\n}\n\nexport interface LineChartPath {\n id: string\n /** Resolved stroke colour. */\n color: string\n /** The stroke path. */\n line: string\n /** The line closed down to the zero baseline, for an area fill. */\n area: string\n}\n\nexport interface LineChartColumn {\n index: number\n x: number\n width: number\n}\n\nexport interface LineChartGeometry {\n width: number\n height: number\n padding: LineChartPadding\n /** Top of the shared y-scale. */\n max: number\n ticks: LineChartTick[]\n xLabels: LineChartXLabel[]\n paths: LineChartPath[]\n /** One hover/focus column per category. */\n columns: LineChartColumn[]\n x: (index: number) => number\n y: (value: number) => number\n /** Top and bottom of the plot area, for a crosshair. */\n plotTop: number\n plotBottom: number\n}\n\nexport const DEFAULT_LINE_CHART_PADDING: LineChartPadding = {\n top: 14,\n right: 14,\n bottom: 24,\n left: 40,\n}\n\nexport const DEFAULT_LINE_CHART_WIDTH = 760\nexport const DEFAULT_LINE_CHART_HEIGHT = 230\nconst DEFAULT_TICK_COUNT = 4\nconst DEFAULT_HEADROOM = 1.08\nconst DEFAULT_LABEL_EVERY = 2\n/** Number of `--chart-N` tokens the theme defines. */\nconst CHART_TOKEN_COUNT = 5\n\n/** The theme chart colour for the series at `index` (wraps around). */\nexport function lineChartColor(index: number): string {\n return `hsl(var(--chart-${(index % CHART_TOKEN_COUNT) + 1}))`\n}\n\nfunction pathFrom(points: Array<[number, number]>): string {\n return points.map(([px, py], i) => (i ? 'L' : 'M') + px.toFixed(1) + ' ' + py.toFixed(1)).join(' ')\n}\n\n/**\n * Pure geometry for a responsive multi-series line chart. Every series shares\n * ONE y-scale (zero baseline to the largest value plus headroom), so series are\n * comparable. Coordinates are in viewBox units; the SVG scales to its container.\n */\nexport function computeLineChart(options: LineChartOptions): LineChartGeometry {\n const {\n series,\n labels,\n width,\n height,\n tickCount = DEFAULT_TICK_COUNT,\n headroom = DEFAULT_HEADROOM,\n labelEvery = DEFAULT_LABEL_EVERY,\n } = options\n const padding = { ...DEFAULT_LINE_CHART_PADDING, ...options.padding }\n const n = labels.length\n const values = series.flatMap((s) => s.data)\n const peak = values.length ? Math.max(...values) : 0\n const max = peak > 0 ? peak * headroom : 1\n const plotW = width - padding.left - padding.right\n const plotTop = padding.top\n const plotBottom = height - padding.bottom\n const plotH = plotBottom - plotTop\n\n const x = (index: number) => padding.left + (n > 1 ? (index / (n - 1)) * plotW : plotW / 2)\n const y = (value: number) => plotTop + (1 - value / max) * plotH\n\n const ticks: LineChartTick[] = Array.from({ length: tickCount + 1 }, (_, t) => {\n const value = (max / tickCount) * t\n return { value, y: y(value) }\n })\n\n const xLabels: LineChartXLabel[] = labels\n .map((label, index) => ({ index, label, x: x(index) }))\n .filter(({ index }) => index % labelEvery === 0 || index === n - 1)\n\n const paths: LineChartPath[] = series.map((s, si) => {\n const points = s.data.map((v, i): [number, number] => [x(i), y(v)])\n const line = pathFrom(points)\n const first = points.length ? points[0][0] : x(0)\n const last = points.length ? points[points.length - 1][0] : x(0)\n const base = y(0).toFixed(1)\n const area = `${line} L ${last.toFixed(1)} ${base} L ${first.toFixed(1)} ${base} Z`\n return { id: s.id, color: s.color ?? lineChartColor(si), line, area }\n })\n\n const colW = n > 1 ? plotW / (n - 1) : plotW\n const columns: LineChartColumn[] = labels.map((_, index) => ({\n index,\n x: x(index) - colW / 2,\n width: colW,\n }))\n\n return { width, height, padding, max, ticks, xLabels, paths, columns, x, y, plotTop, plotBottom }\n}\n\n/** Where a tooltip for `index` sits horizontally, as a percent of the chart width. */\nexport function tooltipLeftPercent(geometry: LineChartGeometry, index: number): number {\n return (geometry.x(index) / geometry.width) * 100\n}\n\n/**\n * The category index a keyboard key moves to from `current` (null = nothing\n * active yet). Returns `current` unchanged for keys the chart doesn't handle.\n */\nexport function nextLineChartIndex(current: number | null, key: string, count: number): number | null {\n if (count === 0) return null\n const last = count - 1\n switch (key) {\n case 'ArrowRight':\n return current == null ? 0 : Math.min(current + 1, last)\n case 'ArrowLeft':\n return current == null ? last : Math.max(current - 1, 0)\n case 'Home':\n return 0\n case 'End':\n return last\n default:\n return current\n }\n}\n", "/** Token-class styles for LineChart; no colour literals. */\nexport const lineChartClasses = {\n root: 'relative',\n svg: 'block w-full rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n gridline: 'stroke-border',\n axis: 'fill-muted-foreground text-[9px]',\n crosshair: 'stroke-border',\n marker: 'stroke-card',\n tooltip:\n 'pointer-events-none absolute z-10 -translate-x-1/2 whitespace-nowrap rounded-md bg-tertiary px-2.5 py-2 text-[11px] text-tertiary-foreground shadow-lg',\n tooltipLabel: 'mb-1 opacity-60',\n tooltipRow: 'flex justify-between gap-2',\n legend: 'mt-2.5 flex flex-wrap gap-3.5',\n legendItem: 'inline-flex items-center gap-1.5 text-[11px] text-muted-foreground',\n legendSwatch: 'h-[3px] w-2.5 rounded-sm',\n} as const\n", "import * as React from 'react'\nimport {\n computeLineChart,\n lineChartClasses as c,\n nextLineChartIndex,\n tooltipLeftPercent,\n DEFAULT_LINE_CHART_WIDTH,\n DEFAULT_LINE_CHART_HEIGHT,\n type LineChartSeries,\n} from '@refraction-ui/line-chart'\nimport { cn } from '@refraction-ui/shared'\n\nexport type { LineChartSeries }\n\nexport interface LineChartProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {\n /** The series to plot, all on one shared y-scale. */\n series: LineChartSeries[]\n /** One category label per data point. */\n labels: string[]\n /** Accessible name of the chart image (required). */\n ariaLabel: string\n /** Rendered height in px (also the viewBox height). Defaults to 230. */\n height?: number\n /** viewBox width; the chart always stretches to its container. Defaults to 760. */\n width?: number\n /** Formats a value in the tooltip. */\n formatValue?: (value: number) => string\n /** Formats a y-axis tick. Defaults to `formatValue`. */\n formatTick?: (value: number) => string\n /** Fill the area under each line. Defaults to on for a single series only. */\n area?: boolean\n /** Show the series legend under the chart. Defaults to true. */\n showLegend?: boolean\n}\n\nconst defaultFormat = (value: number) => String(Math.round(value))\nconst MARKER_RADIUS = 3.5\nconst STROKE_WIDTH = 2.2\nconst TOOLTIP_TOP_PX = 8\n\n/**\n * LineChart — a responsive multi-series line chart on one shared y-scale, with\n * gridlines, category x-labels, a hover/keyboard crosshair + tooltip and a\n * legend. The SVG uses a viewBox, so it stretches to its container's width.\n * Keyboard: focus the chart, then ←/→/Home/End move between categories.\n */\nexport const LineChart = React.forwardRef<HTMLDivElement, LineChartProps>(function LineChart(\n {\n series,\n labels,\n ariaLabel,\n height = DEFAULT_LINE_CHART_HEIGHT,\n width = DEFAULT_LINE_CHART_WIDTH,\n formatValue = defaultFormat,\n formatTick,\n area,\n showLegend = true,\n className,\n ...rest\n },\n ref,\n) {\n const [active, setActive] = React.useState<number | null>(null)\n const gradientBase = React.useId().replace(/:/g, '')\n const geo = React.useMemo(\n () => computeLineChart({ series, labels, width, height }),\n [series, labels, width, height],\n )\n const fillArea = area ?? series.length === 1\n const tick = formatTick ?? formatValue\n\n const handleKeyDown = (event: React.KeyboardEvent<SVGSVGElement>) => {\n const next = nextLineChartIndex(active, event.key, labels.length)\n if (next === active) return\n event.preventDefault()\n setActive(next)\n }\n\n return (\n <div ref={ref} className={cn(c.root, className)} data-slot=\"line-chart\" {...rest}>\n <svg\n className={c.svg}\n viewBox={`0 0 ${width} ${height}`}\n style={{ height }}\n role=\"img\"\n aria-label={ariaLabel}\n tabIndex={0}\n onKeyDown={handleKeyDown}\n onBlur={() => setActive(null)}\n onMouseLeave={() => setActive(null)}\n >\n {geo.ticks.map((t, i) => (\n <g key={i}>\n <line className={c.gridline} x1={geo.padding.left} y1={t.y} x2={width - geo.padding.right} y2={t.y} />\n <text className={c.axis} x={geo.padding.left - 8} y={t.y + 3} textAnchor=\"end\">\n {tick(t.value)}\n </text>\n </g>\n ))}\n {geo.xLabels.map((l) => (\n <text key={l.index} className={c.axis} x={l.x} y={height - 6} textAnchor=\"middle\">\n {l.label}\n </text>\n ))}\n {geo.paths.map((p, si) => {\n const gid = `${gradientBase}-${si}`\n const value = active != null ? series[si].data[active] : undefined\n return (\n <g key={p.id}>\n {fillArea && (\n <>\n <defs>\n <linearGradient id={gid} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor={p.color} stopOpacity=\"0.22\" />\n <stop offset=\"100%\" stopColor={p.color} stopOpacity=\"0\" />\n </linearGradient>\n </defs>\n <path d={p.area} fill={`url(#${gid})`} />\n </>\n )}\n <path\n d={p.line}\n fill=\"none\"\n stroke={p.color}\n strokeWidth={STROKE_WIDTH}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n {active != null && value != null && (\n <circle\n className={c.marker}\n cx={geo.x(active)}\n cy={geo.y(value)}\n r={MARKER_RADIUS}\n fill={p.color}\n strokeWidth=\"1.5\"\n />\n )}\n </g>\n )\n })}\n {active != null && (\n <line\n className={c.crosshair}\n x1={geo.x(active)}\n y1={geo.plotTop}\n x2={geo.x(active)}\n y2={geo.plotBottom}\n strokeDasharray=\"3 3\"\n />\n )}\n {geo.columns.map((col) => (\n <rect\n key={col.index}\n data-index={col.index}\n x={col.x}\n y={0}\n width={col.width}\n height={height}\n fill=\"transparent\"\n onMouseEnter={() => setActive(col.index)}\n />\n ))}\n </svg>\n {active != null && (\n <div\n className={c.tooltip}\n role=\"status\"\n style={{ left: `${tooltipLeftPercent(geo, active)}%`, top: TOOLTIP_TOP_PX }}\n >\n <div className={c.tooltipLabel}>{labels[active]}</div>\n {series.map((s, si) => (\n <div key={s.id} className={c.tooltipRow}>\n <span style={{ color: geo.paths[si].color }}>{s.name}</span>\n <b>{s.data[active] != null ? formatValue(s.data[active]) : '—'}</b>\n </div>\n ))}\n </div>\n )}\n {showLegend && (\n <div className={c.legend}>\n {series.map((s, si) => (\n <span key={s.id} className={c.legendItem}>\n <i className={c.legendSwatch} style={{ background: geo.paths[si].color }} aria-hidden=\"true\" />\n {s.name}\n </span>\n ))}\n </div>\n )}\n </div>\n )\n})\n"],
|
|
5
|
-
"mappings": ";;;;;;;;
|
|
4
|
+
"sourcesContent": ["export interface LineChartSeries {\n /** Stable key for the series. */\n id: string\n /** Legend / tooltip name. */\n name: string\n /**\n * Any CSS colour (a token `hsl(var(--…))` or a data colour). Defaults to the\n * theme's chart palette (`--chart-1` … `--chart-5`) by series position.\n */\n color?: string\n /** One value per label, in label order. */\n data: number[]\n}\n\nexport interface LineChartPadding {\n top: number\n right: number\n bottom: number\n left: number\n}\n\nexport interface LineChartOptions {\n series: LineChartSeries[]\n labels: string[]\n /** viewBox width — the SVG scales to its container's width. */\n width: number\n /** viewBox height. */\n height: number\n padding?: Partial<LineChartPadding>\n /** Target number of gridline intervals on the y-axis (the nice scale may use fewer). */\n tickCount?: number\n /** Headroom multiplier above the largest value. */\n headroom?: number\n /**\n * Show every n-th x label (the last label is always shown). Defaults to\n * thinning so labels are at least ~48 viewBox units apart.\n */\n labelEvery?: number\n}\n\nexport interface LineChartTick {\n value: number\n y: number\n}\n\nexport interface LineChartXLabel {\n index: number\n label: string\n x: number\n}\n\nexport interface LineChartPath {\n id: string\n /** Resolved stroke colour. */\n color: string\n /** The stroke path. */\n line: string\n /** The line closed down to the zero baseline, for an area fill. */\n area: string\n}\n\nexport interface LineChartColumn {\n index: number\n x: number\n width: number\n}\n\nexport interface LineChartGeometry {\n width: number\n height: number\n padding: LineChartPadding\n /** Top of the shared y-scale. */\n max: number\n ticks: LineChartTick[]\n xLabels: LineChartXLabel[]\n paths: LineChartPath[]\n /** One hover/focus column per category. */\n columns: LineChartColumn[]\n x: (index: number) => number\n y: (value: number) => number\n /** Top and bottom of the plot area, for a crosshair. */\n plotTop: number\n plotBottom: number\n}\n\nexport const DEFAULT_LINE_CHART_PADDING: LineChartPadding = {\n top: 14,\n right: 14,\n bottom: 24,\n left: 40,\n}\n\nexport const DEFAULT_LINE_CHART_WIDTH = 760\nexport const DEFAULT_LINE_CHART_HEIGHT = 230\nconst DEFAULT_TICK_COUNT = 4\nconst DEFAULT_HEADROOM = 1.08\n/** Minimum horizontal room per x label when thinning automatically. */\nconst MIN_LABEL_SPACING = 48\n/** Number of `--chart-N` tokens the theme defines. */\nconst CHART_TOKEN_COUNT = 5\n\n/** The theme chart colour for the series at `index` (wraps around). */\nexport function lineChartColor(index: number): string {\n return `hsl(var(--chart-${(index % CHART_TOKEN_COUNT) + 1}))`\n}\n\nconst NICE_STEPS = [1, 2, 5, 10]\n\n/**\n * A \"nice\" y-scale: a 1-2-5 × 10^k step so there are at most `tickCount`\n * intervals up to `rawMax`, and the max rounded up to a whole step. Tick\n * values are therefore round numbers by construction (0, 2000, 4000, …) —\n * never 5999.9999 — so any tick formatter prints clean labels.\n */\nexport function niceScale(rawMax: number, tickCount: number): { max: number; step: number; ticks: number[] } {\n const intervals = Math.max(1, Math.floor(tickCount))\n if (!(rawMax > 0)) return { max: 1, step: 1 / intervals, ticks: Array.from({ length: intervals + 1 }, (_, i) => i / intervals) }\n const target = rawMax / intervals\n const magnitude = 10 ** Math.floor(Math.log10(target))\n const step = NICE_STEPS.map((m) => m * magnitude).find((candidate) => candidate >= target) ?? 10 * magnitude\n const count = Math.ceil(rawMax / step - 1e-9)\n // Strip binary floating-point noise (e.g. 0.1 * 3) to the step's precision.\n const decimals = Math.max(0, -Math.floor(Math.log10(step)))\n const clean = (value: number) => Number(value.toFixed(decimals))\n const ticks = Array.from({ length: count + 1 }, (_, i) => clean(i * step))\n return { max: ticks[ticks.length - 1], step: clean(step), ticks }\n}\n\nfunction pathFrom(points: Array<[number, number]>): string {\n return points.map(([px, py], i) => (i ? 'L' : 'M') + px.toFixed(1) + ' ' + py.toFixed(1)).join(' ')\n}\n\n/**\n * Pure geometry for a responsive multi-series line chart. Every series shares\n * ONE y-scale (zero baseline to the largest value plus headroom), so series are\n * comparable. Coordinates are in viewBox units; the SVG scales to its container.\n */\nexport function computeLineChart(options: LineChartOptions): LineChartGeometry {\n const {\n series,\n labels,\n width,\n height,\n tickCount = DEFAULT_TICK_COUNT,\n headroom = DEFAULT_HEADROOM,\n } = options\n const padding = { ...DEFAULT_LINE_CHART_PADDING, ...options.padding }\n const n = labels.length\n const values = series.flatMap((s) => s.data)\n const peak = values.length ? Math.max(...values) : 0\n const scale = niceScale(peak > 0 ? peak * headroom : 0, tickCount)\n const max = scale.max\n const plotW = width - padding.left - padding.right\n const plotTop = padding.top\n const plotBottom = height - padding.bottom\n const plotH = plotBottom - plotTop\n\n const labelEvery =\n options.labelEvery ??\n Math.max(1, Math.ceil(n / Math.max(1, Math.floor(plotW / MIN_LABEL_SPACING))))\n\n const x = (index: number) => padding.left + (n > 1 ? (index / (n - 1)) * plotW : plotW / 2)\n const y = (value: number) => plotTop + (1 - value / max) * plotH\n\n const ticks: LineChartTick[] = scale.ticks.map((value) => ({ value, y: y(value) }))\n\n const xLabels: LineChartXLabel[] = labels\n .map((label, index) => ({ index, label, x: x(index) }))\n .filter(({ index }) => index % labelEvery === 0 || index === n - 1)\n\n const paths: LineChartPath[] = series.map((s, si) => {\n const points = s.data.map((v, i): [number, number] => [x(i), y(v)])\n const line = pathFrom(points)\n const first = points.length ? points[0][0] : x(0)\n const last = points.length ? points[points.length - 1][0] : x(0)\n const base = y(0).toFixed(1)\n const area = `${line} L ${last.toFixed(1)} ${base} L ${first.toFixed(1)} ${base} Z`\n return { id: s.id, color: s.color ?? lineChartColor(si), line, area }\n })\n\n const colW = n > 1 ? plotW / (n - 1) : plotW\n const columns: LineChartColumn[] = labels.map((_, index) => ({\n index,\n x: x(index) - colW / 2,\n width: colW,\n }))\n\n return { width, height, padding, max, ticks, xLabels, paths, columns, x, y, plotTop, plotBottom }\n}\n\n/** Where a tooltip for `index` sits horizontally, as a percent of the chart width. */\nexport function tooltipLeftPercent(geometry: LineChartGeometry, index: number): number {\n return (geometry.x(index) / geometry.width) * 100\n}\n\n/**\n * The category index a keyboard key moves to from `current` (null = nothing\n * active yet). Returns `current` unchanged for keys the chart doesn't handle.\n */\nexport function nextLineChartIndex(current: number | null, key: string, count: number): number | null {\n if (count === 0) return null\n const last = count - 1\n switch (key) {\n case 'ArrowRight':\n return current == null ? 0 : Math.min(current + 1, last)\n case 'ArrowLeft':\n return current == null ? last : Math.max(current - 1, 0)\n case 'Home':\n return 0\n case 'End':\n return last\n default:\n return current\n }\n}\n", "/** Token-class styles for LineChart; no colour literals. */\nexport const lineChartClasses = {\n root: 'relative',\n svg: 'block w-full rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n gridline: 'stroke-border',\n axis: 'fill-muted-foreground text-[9px]',\n crosshair: 'stroke-border',\n marker: 'stroke-card',\n tooltip:\n 'pointer-events-none absolute z-10 -translate-x-1/2 whitespace-nowrap rounded-md bg-tertiary px-2.5 py-2 text-[11px] text-tertiary-foreground shadow-lg',\n tooltipLabel: 'mb-1 opacity-60',\n tooltipRow: 'flex justify-between gap-2',\n legend: 'mt-2.5 flex flex-wrap gap-3.5',\n legendItem: 'inline-flex items-center gap-1.5 text-[11px] text-muted-foreground',\n legendSwatch: 'h-[3px] w-2.5 rounded-sm',\n} as const\n", "import * as React from 'react'\nimport {\n computeLineChart,\n lineChartClasses as c,\n nextLineChartIndex,\n tooltipLeftPercent,\n DEFAULT_LINE_CHART_WIDTH,\n DEFAULT_LINE_CHART_HEIGHT,\n type LineChartSeries,\n} from '@refraction-ui/line-chart'\nimport { cn } from '@refraction-ui/shared'\n\nexport type { LineChartSeries }\n\nexport interface LineChartProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {\n /** The series to plot, all on one shared y-scale. */\n series: LineChartSeries[]\n /** One category label per data point. */\n labels: string[]\n /** Accessible name of the chart image (required). */\n ariaLabel: string\n /** Rendered height in px (also the viewBox height). Defaults to 230. */\n height?: number\n /**\n * Fallback width, used for the server render and before the container is\n * measured. On the client the chart measures its container and draws at that\n * width, so text and strokes stay at their authored size. Defaults to 760.\n */\n width?: number\n /** Formats a value in the tooltip. */\n formatValue?: (value: number) => string\n /** Formats a y-axis tick. Defaults to `formatValue`. */\n formatTick?: (value: number) => string\n /** Fill the area under each line. Defaults to on for a single series only. */\n area?: boolean\n /** Show the series legend under the chart. Defaults to true. */\n showLegend?: boolean\n}\n\nconst defaultFormat = (value: number) => String(Math.round(value))\nconst MARKER_RADIUS = 3.5\nconst STROKE_WIDTH = 2.2\nconst TOOLTIP_TOP_PX = 8\n\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect\n\n/**\n * Width of `ref`'s element in CSS px, tracked with a ResizeObserver; `null`\n * until measured (server render, or no ResizeObserver).\n */\nfunction useMeasuredWidth(ref: React.RefObject<HTMLElement | null>): number | null {\n const [measured, setMeasured] = React.useState<number | null>(null)\n useIsomorphicLayoutEffect(() => {\n const node = ref.current\n if (!node || typeof ResizeObserver === 'undefined') return\n const apply = (w: number) => {\n const rounded = Math.round(w)\n if (rounded > 0) setMeasured((prev) => (prev === rounded ? prev : rounded))\n }\n apply(node.getBoundingClientRect().width)\n const observer = new ResizeObserver((entries) => apply(entries[0]?.contentRect.width ?? 0))\n observer.observe(node)\n return () => observer.disconnect()\n }, [ref])\n return measured\n}\n\n/**\n * LineChart — a responsive multi-series line chart on one shared y-scale, with\n * gridlines, category x-labels, a hover/keyboard crosshair + tooltip and a\n * legend. It measures its container and draws at that width (1:1 viewBox), so\n * axis text keeps its authored size in narrow panels.\n * Keyboard: focus the chart, then ←/→/Home/End move between categories.\n */\nexport const LineChart = React.forwardRef<HTMLDivElement, LineChartProps>(function LineChart(\n {\n series,\n labels,\n ariaLabel,\n height = DEFAULT_LINE_CHART_HEIGHT,\n width: fallbackWidth = DEFAULT_LINE_CHART_WIDTH,\n formatValue = defaultFormat,\n formatTick,\n area,\n showLegend = true,\n className,\n ...rest\n },\n forwardedRef,\n) {\n const rootRef = React.useRef<HTMLDivElement | null>(null)\n const ref = React.useCallback(\n (node: HTMLDivElement | null) => {\n rootRef.current = node\n if (typeof forwardedRef === 'function') forwardedRef(node)\n else if (forwardedRef) forwardedRef.current = node\n },\n [forwardedRef],\n )\n // Draw at the container's real width: a fixed viewBox stretched to a narrow\n // panel would shrink the axis text and the chart height with it.\n const width = useMeasuredWidth(rootRef) ?? fallbackWidth\n const [active, setActive] = React.useState<number | null>(null)\n const gradientBase = React.useId().replace(/:/g, '')\n const geo = React.useMemo(\n () => computeLineChart({ series, labels, width, height }),\n [series, labels, width, height],\n )\n const fillArea = area ?? series.length === 1\n const tick = formatTick ?? formatValue\n\n const handleKeyDown = (event: React.KeyboardEvent<SVGSVGElement>) => {\n const next = nextLineChartIndex(active, event.key, labels.length)\n if (next === active) return\n event.preventDefault()\n setActive(next)\n }\n\n return (\n <div ref={ref} className={cn(c.root, className)} data-slot=\"line-chart\" {...rest}>\n <svg\n className={c.svg}\n viewBox={`0 0 ${width} ${height}`}\n style={{ height }}\n role=\"img\"\n aria-label={ariaLabel}\n tabIndex={0}\n onKeyDown={handleKeyDown}\n onBlur={() => setActive(null)}\n onMouseLeave={() => setActive(null)}\n >\n {geo.ticks.map((t, i) => (\n <g key={i}>\n <line className={c.gridline} x1={geo.padding.left} y1={t.y} x2={width - geo.padding.right} y2={t.y} />\n <text className={c.axis} x={geo.padding.left - 8} y={t.y + 3} textAnchor=\"end\">\n {tick(t.value)}\n </text>\n </g>\n ))}\n {geo.xLabels.map((l) => (\n <text key={l.index} className={c.axis} x={l.x} y={height - 6} textAnchor=\"middle\">\n {l.label}\n </text>\n ))}\n {geo.paths.map((p, si) => {\n const gid = `${gradientBase}-${si}`\n const value = active != null ? series[si].data[active] : undefined\n return (\n <g key={p.id}>\n {fillArea && (\n <>\n <defs>\n <linearGradient id={gid} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor={p.color} stopOpacity=\"0.22\" />\n <stop offset=\"100%\" stopColor={p.color} stopOpacity=\"0\" />\n </linearGradient>\n </defs>\n <path d={p.area} fill={`url(#${gid})`} />\n </>\n )}\n <path\n d={p.line}\n fill=\"none\"\n stroke={p.color}\n strokeWidth={STROKE_WIDTH}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n {active != null && value != null && (\n <circle\n className={c.marker}\n cx={geo.x(active)}\n cy={geo.y(value)}\n r={MARKER_RADIUS}\n fill={p.color}\n strokeWidth=\"1.5\"\n />\n )}\n </g>\n )\n })}\n {active != null && (\n <line\n className={c.crosshair}\n x1={geo.x(active)}\n y1={geo.plotTop}\n x2={geo.x(active)}\n y2={geo.plotBottom}\n strokeDasharray=\"3 3\"\n />\n )}\n {geo.columns.map((col) => (\n <rect\n key={col.index}\n data-index={col.index}\n x={col.x}\n y={0}\n width={col.width}\n height={height}\n fill=\"transparent\"\n onMouseEnter={() => setActive(col.index)}\n />\n ))}\n </svg>\n {active != null && (\n <div\n className={c.tooltip}\n role=\"status\"\n style={{ left: `${tooltipLeftPercent(geo, active)}%`, top: TOOLTIP_TOP_PX }}\n >\n <div className={c.tooltipLabel}>{labels[active]}</div>\n {series.map((s, si) => (\n <div key={s.id} className={c.tooltipRow}>\n <span style={{ color: geo.paths[si].color }}>{s.name}</span>\n <b>{s.data[active] != null ? formatValue(s.data[active]) : '—'}</b>\n </div>\n ))}\n </div>\n )}\n {showLegend && (\n <div className={c.legend}>\n {series.map((s, si) => (\n <span key={s.id} className={c.legendItem}>\n <i className={c.legendSwatch} style={{ background: geo.paths[si].color }} aria-hidden=\"true\" />\n {s.name}\n </span>\n ))}\n </div>\n )}\n </div>\n )\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["LineChart", "c"]
|
|
7
7
|
}
|
package/dist/react-logger.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createFaroSink
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-V727VZOE.js";
|
|
4
4
|
import {
|
|
5
5
|
devWarn
|
|
6
6
|
} from "./chunk-2Q6HHFAN.js";
|
|
@@ -159,7 +159,7 @@ function createTelemetry(config) {
|
|
|
159
159
|
addSinkInternal(createConsoleSink({ pretty: preset.pretty }));
|
|
160
160
|
if (config.endpoint) {
|
|
161
161
|
const endpoint = config.endpoint;
|
|
162
|
-
void import("./chunk-
|
|
162
|
+
void import("./chunk-7P7EJ56S.js").then(({ createFaroSink: createFaroSink2 }) => createFaroSink2({ app: config.app, endpoint })).then((faro) => {
|
|
163
163
|
if (faro) addSinkInternal(faro);
|
|
164
164
|
}).catch(() => {
|
|
165
165
|
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn,
|
|
3
|
+
cva
|
|
4
|
+
} from "./chunk-2Q6HHFAN.js";
|
|
5
|
+
|
|
6
|
+
// ../react-native-select/dist/index.js
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
|
|
9
|
+
// ../native-select/dist/index.js
|
|
10
|
+
var nativeSelectChevronPath = "m6 9 6 6 6-6";
|
|
11
|
+
var nativeSelectWrapperClass = "relative inline-flex min-w-0 items-center";
|
|
12
|
+
var nativeSelectVariants = cva({
|
|
13
|
+
base: "w-full cursor-pointer appearance-none rounded-md border border-input bg-background text-foreground shadow-sm ring-offset-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
14
|
+
variants: {
|
|
15
|
+
size: {
|
|
16
|
+
sm: "h-8 pl-2.5 pr-8 text-xs",
|
|
17
|
+
default: "h-9 pl-3 pr-9 text-sm",
|
|
18
|
+
lg: "h-10 pl-3 pr-10 text-base"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
size: "default"
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
var nativeSelectIconClass = "pointer-events-none absolute right-2.5 h-4 w-4 shrink-0 text-muted-foreground";
|
|
26
|
+
|
|
27
|
+
// ../react-native-select/dist/index.js
|
|
28
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
29
|
+
var NativeSelect = React.forwardRef(function NativeSelect2({ size = "default", className, containerClassName, children, ...props }, ref) {
|
|
30
|
+
return /* @__PURE__ */ jsxs("span", { className: cn(nativeSelectWrapperClass, containerClassName), "data-slot": "native-select", children: [
|
|
31
|
+
/* @__PURE__ */ jsx("select", { ref, className: cn(nativeSelectVariants({ size }), className), ...props, children }),
|
|
32
|
+
/* @__PURE__ */ jsx(
|
|
33
|
+
"svg",
|
|
34
|
+
{
|
|
35
|
+
className: nativeSelectIconClass,
|
|
36
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
37
|
+
viewBox: "0 0 24 24",
|
|
38
|
+
fill: "none",
|
|
39
|
+
stroke: "currentColor",
|
|
40
|
+
strokeWidth: "2",
|
|
41
|
+
strokeLinecap: "round",
|
|
42
|
+
strokeLinejoin: "round",
|
|
43
|
+
"aria-hidden": "true",
|
|
44
|
+
children: /* @__PURE__ */ jsx("path", { d: nativeSelectChevronPath })
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
] });
|
|
48
|
+
});
|
|
49
|
+
export {
|
|
50
|
+
NativeSelect
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=react-native-select.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../native-select/src/native-select.ts", "../../native-select/src/native-select.styles.ts", "../../react-native-select/src/native-select.tsx"],
|
|
4
|
+
"sourcesContent": ["/** Visual size of a NativeSelect. */\nexport type NativeSelectSize = 'sm' | 'default' | 'lg'\n\n/** The chevron's path (a down chevron), so adapters ship no icon dependency. */\nexport const nativeSelectChevronPath = 'm6 9 6 6 6-6'\n", "import { cva } from '@refraction-ui/shared'\n\n/** Wrapper that positions the chevron over the select. */\nexport const nativeSelectWrapperClass = 'relative inline-flex min-w-0 items-center'\n\n/**\n * The `<select>`: the platform's own listbox (mobile pickers, type-to-select,\n * form submission, `change` events) with the input look. Size-owned utilities\n * live only in the size variants so `size` always wins.\n */\nexport const nativeSelectVariants = cva({\n base:\n 'w-full cursor-pointer appearance-none rounded-md border border-input bg-background text-foreground shadow-sm ' +\n 'ring-offset-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring ' +\n 'disabled:cursor-not-allowed disabled:opacity-50',\n variants: {\n size: {\n sm: 'h-8 pl-2.5 pr-8 text-xs',\n default: 'h-9 pl-3 pr-9 text-sm',\n lg: 'h-10 pl-3 pr-10 text-base',\n },\n },\n defaultVariants: {\n size: 'default',\n },\n})\n\n/** The chevron: decorative, and never eats the click meant for the select. */\nexport const nativeSelectIconClass =\n 'pointer-events-none absolute right-2.5 h-4 w-4 shrink-0 text-muted-foreground'\n", "import * as React from 'react'\nimport {\n nativeSelectChevronPath,\n nativeSelectIconClass,\n nativeSelectVariants,\n nativeSelectWrapperClass,\n type NativeSelectSize,\n} from '@refraction-ui/native-select'\nimport { cn } from '@refraction-ui/shared'\n\nexport type { NativeSelectSize }\n\nexport interface NativeSelectProps extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'size'> {\n /** Visual size; shadows the native `size` (visible rows), which a dropdown never wants. */\n size?: NativeSelectSize\n /** Classes for the positioning wrapper (width, margins); `className` styles the select. */\n containerClassName?: string\n}\n\n/**\n * NativeSelect — a native `<select>` with the input look and a chevron.\n * Children are ordinary `<option>`/`<optgroup>` elements; every native\n * attribute and event passes through, and the ref is the select itself. Use it\n * for forms and filter bars; use `Select` for a custom listbox.\n */\nexport const NativeSelect = React.forwardRef<HTMLSelectElement, NativeSelectProps>(function NativeSelect(\n { size = 'default', className, containerClassName, children, ...props },\n ref,\n) {\n return (\n <span className={cn(nativeSelectWrapperClass, containerClassName)} data-slot=\"native-select\">\n <select ref={ref} className={cn(nativeSelectVariants({ size }), className)} {...props}>\n {children}\n </select>\n <svg\n className={nativeSelectIconClass}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d={nativeSelectChevronPath} />\n </svg>\n </span>\n )\n})\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;AAIO,IAAM,0BAA0B;ACDhC,IAAM,2BAA2B;AAOjC,IAAM,uBAAuB,IAAI;EACtC,MACE;EAGF,UAAU;IACR,MAAM;MACJ,IAAI;MACJ,SAAS;MACT,IAAI;IAAA;EACN;EAEF,iBAAiB;IACf,MAAM;EAAA;AAEV,CAAC;AAGM,IAAM,wBACX;;;;ACJK,IAAM,eAAqB,iBAAiD,SAASA,cAC1F,EAAE,OAAO,WAAW,WAAW,oBAAoB,UAAU,GAAG,MAAA,GAChE,KACA;AACA,SACE,qBAAC,QAAA,EAAK,WAAW,GAAG,0BAA0B,kBAAkB,GAAG,aAAU,iBAC3E,UAAA;IAAA,oBAAC,UAAA,EAAO,KAAU,WAAW,GAAG,qBAAqB,EAAE,KAAA,CAAM,GAAG,SAAS,GAAI,GAAG,OAC7E,SAAA,CACH;IACA;MAAC;MAAA;QACC,WAAW;QACX,OAAM;QACN,SAAQ;QACR,MAAK;QACL,QAAO;QACP,aAAY;QACZ,eAAc;QACd,gBAAe;QACf,eAAY;QAEZ,UAAA,oBAAC,QAAA,EAAK,GAAG,wBAAA,CAAyB;MAAA;IAAA;EACpC,EAAA,CACF;AAEJ,CAAC;",
|
|
6
|
+
"names": ["NativeSelect"]
|
|
7
|
+
}
|