@mks2508/mks-ui 0.5.4 → 0.5.8
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/react-ui/primitives/waapi/Gooey/Gooey.types.d.ts +23 -4
- package/dist/react-ui/primitives/waapi/Gooey/Gooey.types.d.ts.map +1 -1
- package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.d.ts +2 -2
- package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.d.ts.map +1 -1
- package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.js +292 -31
- package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.d.ts +7 -0
- package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.d.ts.map +1 -1
- package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.js +6 -1
- package/dist/react-ui/ui/DynamicToggle/{DynamicToggle-Cm6-VceQ.css → DynamicToggle-DJLwEkHr.css} +116 -51
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.css +116 -51
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.d.ts +1 -0
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.d.ts.map +1 -1
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.js +9 -3
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.types.d.ts +61 -31
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.types.d.ts.map +1 -1
- package/dist/react-ui/ui/DynamicToggle/index.d.ts +9 -3
- package/dist/react-ui/ui/DynamicToggle/index.d.ts.map +1 -1
- package/dist/react-ui/ui/DynamicToggle/index.js +68 -37
- package/package.json +52 -13
- package/src/assets/react.svg +0 -1
- package/src/core/index.ts +0 -7
- package/src/core/types.ts +0 -82
- package/src/css.d.ts +0 -7
- package/src/index.css +0 -129
- package/src/index.ts +0 -29
- package/src/react-ui/blocks/Terminal/ResttyAdapter.ts +0 -278
- package/src/react-ui/blocks/Terminal/Terminal.adapter.ts +0 -97
- package/src/react-ui/blocks/Terminal/Terminal.theme.restty.ts +0 -155
- package/src/react-ui/blocks/Terminal/Terminal.theme.ts +0 -80
- package/src/react-ui/blocks/Terminal/Terminal.types.ts +0 -438
- package/src/react-ui/blocks/Terminal/TerminalDisplay.styles.ts +0 -38
- package/src/react-ui/blocks/Terminal/TerminalDisplay.tsx +0 -254
- package/src/react-ui/blocks/Terminal/TerminalDisplay.types.ts +0 -73
- package/src/react-ui/blocks/Terminal/TerminalPanel.tsx +0 -269
- package/src/react-ui/blocks/Terminal/TerminalRestty.tsx +0 -326
- package/src/react-ui/blocks/Terminal/TerminalXterm.tsx +0 -230
- package/src/react-ui/blocks/Terminal/XTermAdapter.ts +0 -163
- package/src/react-ui/blocks/Terminal/chrome.ts +0 -25
- package/src/react-ui/blocks/Terminal/components/LogLineBadges.tsx +0 -316
- package/src/react-ui/blocks/Terminal/components/SpecializedSyntaxHighlighter.tsx +0 -218
- package/src/react-ui/blocks/Terminal/components/SyntaxHighlight.tsx +0 -386
- package/src/react-ui/blocks/Terminal/components/TerminalLogBadge.tsx +0 -67
- package/src/react-ui/blocks/Terminal/components/index.ts +0 -10
- package/src/react-ui/blocks/Terminal/display.ts +0 -46
- package/src/react-ui/blocks/Terminal/hooks/index.ts +0 -22
- package/src/react-ui/blocks/Terminal/hooks/useTerminalSettings.ts +0 -229
- package/src/react-ui/blocks/Terminal/hooks/useTerminalWebSocket.ts +0 -292
- package/src/react-ui/blocks/Terminal/index.ts +0 -111
- package/src/react-ui/blocks/Terminal/panel/LogLinesViewer.tsx +0 -330
- package/src/react-ui/blocks/Terminal/panel/TerminalDebugPanel.tsx +0 -242
- package/src/react-ui/blocks/Terminal/panel/TerminalFilterDropdown.tsx +0 -202
- package/src/react-ui/blocks/Terminal/panel/TerminalFilterTabs.tsx +0 -140
- package/src/react-ui/blocks/Terminal/panel/TerminalInteractivePanel.tsx +0 -68
- package/src/react-ui/blocks/Terminal/panel/TerminalInteractivePanel.types.ts +0 -85
- package/src/react-ui/blocks/Terminal/panel/TerminalInteractivePanelRestty.tsx +0 -383
- package/src/react-ui/blocks/Terminal/panel/TerminalInteractivePanelXterm.tsx +0 -439
- package/src/react-ui/blocks/Terminal/panel/TerminalLogsPanel.tsx +0 -550
- package/src/react-ui/blocks/Terminal/panel/TerminalLogsPanel.types.ts +0 -259
- package/src/react-ui/blocks/Terminal/panel/TerminalPanelChrome.styles.ts +0 -75
- package/src/react-ui/blocks/Terminal/panel/TerminalPanelChrome.tsx +0 -266
- package/src/react-ui/blocks/Terminal/panel/TerminalPanelChrome.types.ts +0 -82
- package/src/react-ui/blocks/Terminal/panel/TerminalPanelFooter.tsx +0 -112
- package/src/react-ui/blocks/Terminal/panel/TerminalPanelHeader.tsx +0 -178
- package/src/react-ui/blocks/Terminal/panel/TerminalPanelToolbar.tsx +0 -203
- package/src/react-ui/blocks/Terminal/panel/TerminalSessionControl.tsx +0 -252
- package/src/react-ui/blocks/Terminal/panel/TerminalSessionTabs.tsx +0 -334
- package/src/react-ui/blocks/Terminal/panel/TerminalSettingsPopover.tsx +0 -261
- package/src/react-ui/blocks/Terminal/panel/TerminalThemeSelector.tsx +0 -248
- package/src/react-ui/blocks/Terminal/panel/index.ts +0 -72
- package/src/react-ui/blocks/Terminal/panel/terminal-filter-dropdown.module.css +0 -59
- package/src/react-ui/blocks/Terminal/panel/terminal-session-tabs.module.css +0 -59
- package/src/react-ui/blocks/Terminal/parsing/BadgeFormatter.ts +0 -180
- package/src/react-ui/blocks/Terminal/parsing/HttpLogParser.ts +0 -248
- package/src/react-ui/blocks/Terminal/parsing/LogParser.types.ts +0 -283
- package/src/react-ui/blocks/Terminal/parsing/LogParserService.ts +0 -686
- package/src/react-ui/blocks/Terminal/parsing/MultilineAggregator.ts +0 -466
- package/src/react-ui/blocks/Terminal/parsing/PersistentLogBuffer.ts +0 -343
- package/src/react-ui/blocks/Terminal/parsing/SyntaxHighlighter.ts +0 -167
- package/src/react-ui/blocks/Terminal/parsing/TableParser.ts +0 -348
- package/src/react-ui/blocks/Terminal/parsing/ansi/AnsiColorMapper.ts +0 -251
- package/src/react-ui/blocks/Terminal/parsing/ansi/AnsiParser.ts +0 -390
- package/src/react-ui/blocks/Terminal/parsing/ansi/ansi.constants.ts +0 -320
- package/src/react-ui/blocks/Terminal/parsing/ansi/index.ts +0 -20
- package/src/react-ui/blocks/Terminal/parsing/index.ts +0 -69
- package/src/react-ui/blocks/Terminal/parsing/levels/LogLevel.types.ts +0 -68
- package/src/react-ui/blocks/Terminal/parsing/levels/LogLevelDetector.ts +0 -436
- package/src/react-ui/blocks/Terminal/parsing/levels/index.ts +0 -14
- package/src/react-ui/blocks/index.ts +0 -11
- package/src/react-ui/components/MorphingPopover/MorphingPopover.types.ts +0 -49
- package/src/react-ui/components/MorphingPopover/index.tsx +0 -186
- package/src/react-ui/components/MorphingPopover/morphing-popover.module.css +0 -153
- package/src/react-ui/components/index.ts +0 -9
- package/src/react-ui/hooks/Animation/UseAutoHeight.tsx +0 -123
- package/src/react-ui/hooks/DOM/UseIsInView.tsx +0 -44
- package/src/react-ui/hooks/Formatting/UseListFormat.ts +0 -134
- package/src/react-ui/hooks/State/UseControlledState.tsx +0 -57
- package/src/react-ui/hooks/State/UseDataState.tsx +0 -76
- package/src/react-ui/hooks/index.ts +0 -20
- package/src/react-ui/icons/index.ts +0 -12
- package/src/react-ui/icons/lucide-animated/activity.tsx +0 -109
- package/src/react-ui/icons/lucide-animated/arrow-down-to-line.tsx +0 -51
- package/src/react-ui/icons/lucide-animated/arrow-up.tsx +0 -50
- package/src/react-ui/icons/lucide-animated/bell-electric.tsx +0 -124
- package/src/react-ui/icons/lucide-animated/bell.tsx +0 -93
- package/src/react-ui/icons/lucide-animated/bot.tsx +0 -122
- package/src/react-ui/icons/lucide-animated/box.tsx +0 -117
- package/src/react-ui/icons/lucide-animated/check.tsx +0 -21
- package/src/react-ui/icons/lucide-animated/circle-check.tsx +0 -107
- package/src/react-ui/icons/lucide-animated/delete.tsx +0 -133
- package/src/react-ui/icons/lucide-animated/download.tsx +0 -99
- package/src/react-ui/icons/lucide-animated/edit-2.tsx +0 -21
- package/src/react-ui/icons/lucide-animated/globe.tsx +0 -23
- package/src/react-ui/icons/lucide-animated/home.tsx +0 -103
- package/src/react-ui/icons/lucide-animated/index.ts +0 -38
- package/src/react-ui/icons/lucide-animated/layers.tsx +0 -23
- package/src/react-ui/icons/lucide-animated/layout-panel-top.tsx +0 -143
- package/src/react-ui/icons/lucide-animated/list.tsx +0 -54
- package/src/react-ui/icons/lucide-animated/package.tsx +0 -24
- package/src/react-ui/icons/lucide-animated/palette.tsx +0 -25
- package/src/react-ui/icons/lucide-animated/plus.tsx +0 -92
- package/src/react-ui/icons/lucide-animated/refresh-cw.tsx +0 -24
- package/src/react-ui/icons/lucide-animated/rocket.tsx +0 -24
- package/src/react-ui/icons/lucide-animated/save.tsx +0 -23
- package/src/react-ui/icons/lucide-animated/search.tsx +0 -94
- package/src/react-ui/icons/lucide-animated/settings.tsx +0 -92
- package/src/react-ui/icons/lucide-animated/terminal.tsx +0 -46
- package/src/react-ui/icons/lucide-animated/trash-2.tsx +0 -25
- package/src/react-ui/icons/lucide-animated/trending-down.tsx +0 -151
- package/src/react-ui/icons/lucide-animated/trending-up.tsx +0 -150
- package/src/react-ui/icons/lucide-animated/type.tsx +0 -23
- package/src/react-ui/icons/lucide-animated/upload.tsx +0 -23
- package/src/react-ui/icons/lucide-animated/x.tsx +0 -102
- package/src/react-ui/index.ts +0 -30
- package/src/react-ui/lib/get-strict-context.tsx +0 -56
- package/src/react-ui/lib/icon-wrapper.tsx +0 -70
- package/src/react-ui/lib/index.ts +0 -9
- package/src/react-ui/lib/utils.ts +0 -24
- package/src/react-ui/primitives/AutoHeight/index.tsx +0 -74
- package/src/react-ui/primitives/CountingNumber/index.tsx +0 -147
- package/src/react-ui/primitives/Highlight/Highlight.types.ts +0 -136
- package/src/react-ui/primitives/Highlight/index.tsx +0 -577
- package/src/react-ui/primitives/Slot/index.tsx +0 -128
- package/src/react-ui/primitives/index.ts +0 -16
- package/src/react-ui/primitives/waapi/Gooey/Gooey.types.ts +0 -123
- package/src/react-ui/primitives/waapi/Gooey/GooeyCanvas.tsx +0 -80
- package/src/react-ui/primitives/waapi/Gooey/GooeyFilter.tsx +0 -77
- package/src/react-ui/primitives/waapi/Gooey/MorphPath.tsx +0 -58
- package/src/react-ui/primitives/waapi/Gooey/gooey-utils.ts +0 -244
- package/src/react-ui/primitives/waapi/Gooey/index.ts +0 -50
- package/src/react-ui/primitives/waapi/Gooey/useMorphPath.ts +0 -48
- package/src/react-ui/primitives/waapi/Morph/Morph.types.ts +0 -106
- package/src/react-ui/primitives/waapi/Morph/MorphContext.tsx +0 -21
- package/src/react-ui/primitives/waapi/Morph/index.tsx +0 -56
- package/src/react-ui/primitives/waapi/Morph/techniques/index.ts +0 -12
- package/src/react-ui/primitives/waapi/Morph/techniques/useCSSGridMorph.ts +0 -89
- package/src/react-ui/primitives/waapi/Morph/techniques/useFLIPClipPath.ts +0 -176
- package/src/react-ui/primitives/waapi/Morph/techniques/useViewTransitions.ts +0 -87
- package/src/react-ui/primitives/waapi/Morph/useMorph.ts +0 -101
- package/src/react-ui/primitives/waapi/Reorder/Reorder.types.ts +0 -177
- package/src/react-ui/primitives/waapi/Reorder/index.tsx +0 -260
- package/src/react-ui/primitives/waapi/Reorder/useReorder.ts +0 -47
- package/src/react-ui/primitives/waapi/Reorder/useReorderPresence.ts +0 -209
- package/src/react-ui/primitives/waapi/Reorder/utils/separatorCoordination.ts +0 -104
- package/src/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.styles.ts +0 -14
- package/src/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.types.ts +0 -84
- package/src/react-ui/primitives/waapi/SlidingNumber/index.tsx +0 -474
- package/src/react-ui/primitives/waapi/SlidingText/SlidingText.styles.ts +0 -32
- package/src/react-ui/primitives/waapi/SlidingText/SlidingText.types.ts +0 -69
- package/src/react-ui/primitives/waapi/SlidingText/index.tsx +0 -140
- package/src/react-ui/primitives/waapi/core/animationConstants.ts +0 -215
- package/src/react-ui/primitives/waapi/core/index.ts +0 -53
- package/src/react-ui/primitives/waapi/core/types.ts +0 -200
- package/src/react-ui/primitives/waapi/core/useAnimationOrchestrator.ts +0 -430
- package/src/react-ui/primitives/waapi/core/useElementRegistry.ts +0 -81
- package/src/react-ui/primitives/waapi/core/useFLIPAnimation.ts +0 -138
- package/src/react-ui/primitives/waapi/core/usePositionCapture.ts +0 -106
- package/src/react-ui/primitives/waapi/index.ts +0 -139
- package/src/react-ui/styles/animations.css +0 -369
- package/src/react-ui/ui/Accordion/Accordion.styles.ts +0 -72
- package/src/react-ui/ui/Accordion/Accordion.types.ts +0 -199
- package/src/react-ui/ui/Accordion/index.tsx +0 -362
- package/src/react-ui/ui/AlertDialog/AlertDialog.styles.ts +0 -38
- package/src/react-ui/ui/AlertDialog/AlertDialog.types.ts +0 -296
- package/src/react-ui/ui/AlertDialog/index.tsx +0 -540
- package/src/react-ui/ui/Badge/Badge.styles.ts +0 -43
- package/src/react-ui/ui/Badge/Badge.types.ts +0 -26
- package/src/react-ui/ui/Badge/index.tsx +0 -34
- package/src/react-ui/ui/Button/Button.styles.ts +0 -57
- package/src/react-ui/ui/Button/Button.types.ts +0 -63
- package/src/react-ui/ui/Button/index.tsx +0 -155
- package/src/react-ui/ui/Card/Card.styles.ts +0 -32
- package/src/react-ui/ui/Card/Card.types.ts +0 -39
- package/src/react-ui/ui/Card/index.tsx +0 -130
- package/src/react-ui/ui/Checkbox/Checkbox.styles.ts +0 -40
- package/src/react-ui/ui/Checkbox/Checkbox.types.ts +0 -98
- package/src/react-ui/ui/Checkbox/index.tsx +0 -233
- package/src/react-ui/ui/Combobox/Combobox.styles.ts +0 -34
- package/src/react-ui/ui/Combobox/Combobox.types.ts +0 -89
- package/src/react-ui/ui/Combobox/index.tsx +0 -331
- package/src/react-ui/ui/CornerBracket/CornerBracket.styles.ts +0 -38
- package/src/react-ui/ui/CornerBracket/CornerBracket.types.ts +0 -15
- package/src/react-ui/ui/CornerBracket/index.tsx +0 -49
- package/src/react-ui/ui/DataCard/DataCard.styles.ts +0 -94
- package/src/react-ui/ui/DataCard/DataCard.types.ts +0 -125
- package/src/react-ui/ui/DataCard/index.tsx +0 -340
- package/src/react-ui/ui/Dialog/Dialog.styles.ts +0 -59
- package/src/react-ui/ui/Dialog/Dialog.types.ts +0 -284
- package/src/react-ui/ui/Dialog/index.tsx +0 -452
- package/src/react-ui/ui/DropdownMenu/DropdownMenu.styles.ts +0 -35
- package/src/react-ui/ui/DropdownMenu/DropdownMenu.types.ts +0 -81
- package/src/react-ui/ui/DropdownMenu/index.tsx +0 -300
- package/src/react-ui/ui/DynamicToggle/DynamicToggle.css +0 -303
- package/src/react-ui/ui/DynamicToggle/DynamicToggle.styles.ts +0 -85
- package/src/react-ui/ui/DynamicToggle/DynamicToggle.types.ts +0 -174
- package/src/react-ui/ui/DynamicToggle/index.tsx +0 -294
- package/src/react-ui/ui/DynamicToggle/prototype-v7.html +0 -615
- package/src/react-ui/ui/DynamicToggle/prototype.html +0 -419
- package/src/react-ui/ui/Field/Field.styles.ts +0 -47
- package/src/react-ui/ui/Field/Field.types.ts +0 -60
- package/src/react-ui/ui/Field/index.tsx +0 -254
- package/src/react-ui/ui/Input/Input.styles.ts +0 -11
- package/src/react-ui/ui/Input/Input.types.ts +0 -10
- package/src/react-ui/ui/Input/index.tsx +0 -32
- package/src/react-ui/ui/InputGroup/InputGroup.styles.ts +0 -53
- package/src/react-ui/ui/InputGroup/InputGroup.types.ts +0 -44
- package/src/react-ui/ui/InputGroup/index.tsx +0 -149
- package/src/react-ui/ui/Label/Label.styles.ts +0 -10
- package/src/react-ui/ui/Label/Label.types.ts +0 -9
- package/src/react-ui/ui/Label/index.tsx +0 -27
- package/src/react-ui/ui/Menu/Menu.styles.ts +0 -71
- package/src/react-ui/ui/Menu/Menu.types.ts +0 -425
- package/src/react-ui/ui/Menu/index.tsx +0 -900
- package/src/react-ui/ui/Popover/Popover.styles.ts +0 -55
- package/src/react-ui/ui/Popover/Popover.types.ts +0 -261
- package/src/react-ui/ui/Popover/index.tsx +0 -422
- package/src/react-ui/ui/Progress/Progress.styles.ts +0 -36
- package/src/react-ui/ui/Progress/Progress.types.ts +0 -162
- package/src/react-ui/ui/Progress/index.tsx +0 -254
- package/src/react-ui/ui/Select/Select.styles.ts +0 -30
- package/src/react-ui/ui/Select/Select.types.ts +0 -51
- package/src/react-ui/ui/Select/index.tsx +0 -225
- package/src/react-ui/ui/Separator/Separator.styles.ts +0 -10
- package/src/react-ui/ui/Separator/Separator.types.ts +0 -10
- package/src/react-ui/ui/Separator/index.tsx +0 -37
- package/src/react-ui/ui/Switch/Switch.styles.ts +0 -50
- package/src/react-ui/ui/Switch/Switch.types.ts +0 -155
- package/src/react-ui/ui/Switch/index.tsx +0 -253
- package/src/react-ui/ui/Tabs/Tabs.css +0 -39
- package/src/react-ui/ui/Tabs/Tabs.styles.ts +0 -148
- package/src/react-ui/ui/Tabs/Tabs.types.ts +0 -255
- package/src/react-ui/ui/Tabs/index.tsx +0 -529
- package/src/react-ui/ui/TextFlow/TextFlow.styles.ts +0 -36
- package/src/react-ui/ui/TextFlow/TextFlow.types.ts +0 -118
- package/src/react-ui/ui/TextFlow/index.tsx +0 -276
- package/src/react-ui/ui/Textarea/Textarea.styles.ts +0 -10
- package/src/react-ui/ui/Textarea/Textarea.types.ts +0 -9
- package/src/react-ui/ui/Textarea/index.tsx +0 -27
- package/src/react-ui/ui/Tooltip/Tooltip.styles.ts +0 -43
- package/src/react-ui/ui/Tooltip/Tooltip.types.ts +0 -253
- package/src/react-ui/ui/Tooltip/index.tsx +0 -394
- package/src/react-ui/ui/index.ts +0 -41
- package/src/types/css-modules.d.ts +0 -18
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* MorphingPopover — a popover that animates from a trigger button to a target position.
|
|
5
|
-
*
|
|
6
|
-
* Uses CSS Anchor Positioning + Popover API + @starting-style for smooth morphing animations.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```tsx
|
|
10
|
-
* <MorphingPopover
|
|
11
|
-
* trigger="..."
|
|
12
|
-
* content={<div>Expanded content</div>}
|
|
13
|
-
* targetSize={{ width: 320, height: 200 }}
|
|
14
|
-
* />
|
|
15
|
-
* ```
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
forwardRef,
|
|
20
|
-
type CSSProperties,
|
|
21
|
-
} from 'react';
|
|
22
|
-
import { cn } from '@/react-ui/lib/utils';
|
|
23
|
-
import styles from './morphing-popover.module.css';
|
|
24
|
-
import type {
|
|
25
|
-
IMorphingPopoverProps,
|
|
26
|
-
IMorphingButtonProps,
|
|
27
|
-
IMorphingPopoverWithTargetProps,
|
|
28
|
-
} from './MorphingPopover.types';
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* MorphingPopover — animates from a circular trigger to an expanded popover.
|
|
32
|
-
*
|
|
33
|
-
* @param content - Content to display in the expanded popover
|
|
34
|
-
* @param trigger - Trigger button content (default: "...")
|
|
35
|
-
* @param targetSize - Expanded dimensions { width, height }
|
|
36
|
-
* @param onOpen - Callback when popover opens
|
|
37
|
-
* @param onClose - Callback when popover closes
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```tsx
|
|
41
|
-
* <MorphingPopover
|
|
42
|
-
* trigger="+"
|
|
43
|
-
* content={<div>Expanded content</div>}
|
|
44
|
-
* targetSize={{ width: 320, height: 200 }}
|
|
45
|
-
* />
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
const MorphingPopover = forwardRef<HTMLButtonElement, IMorphingPopoverProps>(
|
|
49
|
-
(
|
|
50
|
-
{
|
|
51
|
-
content,
|
|
52
|
-
trigger = '\u22EF',
|
|
53
|
-
triggerClassName,
|
|
54
|
-
popoverClassName,
|
|
55
|
-
targetSize = { width: 220, height: 120 },
|
|
56
|
-
onOpen,
|
|
57
|
-
onClose,
|
|
58
|
-
popoverId = 'morphing-popover',
|
|
59
|
-
},
|
|
60
|
-
ref,
|
|
61
|
-
) => {
|
|
62
|
-
const containerStyle: CSSProperties = {
|
|
63
|
-
'--target-width': `${targetSize.width}px`,
|
|
64
|
-
'--target-height': `${targetSize.height}px`,
|
|
65
|
-
} as CSSProperties;
|
|
66
|
-
|
|
67
|
-
const handleToggle = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
68
|
-
const isOpen = (e.target as HTMLButtonElement).matches(':popover-open');
|
|
69
|
-
if (isOpen) {
|
|
70
|
-
onOpen?.();
|
|
71
|
-
} else {
|
|
72
|
-
onClose?.();
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<div
|
|
78
|
-
className={styles.morphingPopoverContainer}
|
|
79
|
-
style={containerStyle}
|
|
80
|
-
>
|
|
81
|
-
<button
|
|
82
|
-
ref={ref}
|
|
83
|
-
{...({
|
|
84
|
-
popovertarget: popoverId,
|
|
85
|
-
popoverTargetAction: 'toggle',
|
|
86
|
-
} as IMorphingButtonProps)}
|
|
87
|
-
className={cn(styles.morphingTrigger, triggerClassName)}
|
|
88
|
-
onClick={handleToggle}
|
|
89
|
-
>
|
|
90
|
-
{trigger}
|
|
91
|
-
</button>
|
|
92
|
-
|
|
93
|
-
<div
|
|
94
|
-
id={popoverId}
|
|
95
|
-
popover="auto"
|
|
96
|
-
className={cn(styles.morphingPopover, popoverClassName)}
|
|
97
|
-
>
|
|
98
|
-
{content}
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
);
|
|
102
|
-
},
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
MorphingPopover.displayName = 'MorphingPopover';
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* MorphingPopoverWithTarget — variant that animates to a specific target element.
|
|
109
|
-
*
|
|
110
|
-
* The target element must have CSS `anchor-name: --target-anchor` set on it.
|
|
111
|
-
*
|
|
112
|
-
* @param targetRef - Reference to the target element to anchor to
|
|
113
|
-
* @param content - Content to display in the expanded popover
|
|
114
|
-
*
|
|
115
|
-
* @example
|
|
116
|
-
* ```tsx
|
|
117
|
-
* const targetRef = useRef<HTMLDivElement>(null);
|
|
118
|
-
*
|
|
119
|
-
* <div ref={targetRef} style={{ anchorName: '--target-anchor' }}>
|
|
120
|
-
* Drag me around
|
|
121
|
-
* </div>
|
|
122
|
-
*
|
|
123
|
-
* <MorphingPopoverWithTarget
|
|
124
|
-
* targetRef={targetRef}
|
|
125
|
-
* content={<div>Content</div>}
|
|
126
|
-
* />
|
|
127
|
-
* ```
|
|
128
|
-
*/
|
|
129
|
-
const MorphingPopoverWithTarget = forwardRef<
|
|
130
|
-
HTMLButtonElement,
|
|
131
|
-
IMorphingPopoverWithTargetProps
|
|
132
|
-
>(
|
|
133
|
-
(
|
|
134
|
-
{
|
|
135
|
-
content,
|
|
136
|
-
trigger = '\u22EF',
|
|
137
|
-
triggerClassName,
|
|
138
|
-
popoverClassName,
|
|
139
|
-
onOpen,
|
|
140
|
-
onClose,
|
|
141
|
-
popoverId = 'morphing-popover-target',
|
|
142
|
-
},
|
|
143
|
-
ref,
|
|
144
|
-
) => {
|
|
145
|
-
const handleToggle = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
146
|
-
const isOpen = (e.target as HTMLButtonElement).matches(':popover-open');
|
|
147
|
-
if (isOpen) {
|
|
148
|
-
onOpen?.();
|
|
149
|
-
} else {
|
|
150
|
-
onClose?.();
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
return (
|
|
155
|
-
<div className={styles.morphingPopoverTargetContainer}>
|
|
156
|
-
<button
|
|
157
|
-
ref={ref}
|
|
158
|
-
{...({
|
|
159
|
-
popovertarget: popoverId,
|
|
160
|
-
popoverTargetAction: 'toggle',
|
|
161
|
-
} as IMorphingButtonProps)}
|
|
162
|
-
className={cn(styles.morphingTrigger, triggerClassName)}
|
|
163
|
-
onClick={handleToggle}
|
|
164
|
-
>
|
|
165
|
-
{trigger}
|
|
166
|
-
</button>
|
|
167
|
-
|
|
168
|
-
<div
|
|
169
|
-
id={popoverId}
|
|
170
|
-
popover="auto"
|
|
171
|
-
className={cn(styles.morphingPopoverTarget, popoverClassName)}
|
|
172
|
-
>
|
|
173
|
-
{content}
|
|
174
|
-
</div>
|
|
175
|
-
</div>
|
|
176
|
-
);
|
|
177
|
-
},
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
MorphingPopoverWithTarget.displayName = 'MorphingPopoverWithTarget';
|
|
181
|
-
|
|
182
|
-
export { MorphingPopover, MorphingPopoverWithTarget };
|
|
183
|
-
export type {
|
|
184
|
-
IMorphingPopoverProps,
|
|
185
|
-
IMorphingPopoverWithTargetProps,
|
|
186
|
-
} from './MorphingPopover.types';
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
/* CSS Variables for anchor positioning */
|
|
2
|
-
.morphingPopoverContainer {
|
|
3
|
-
position: relative;
|
|
4
|
-
display: inline-block;
|
|
5
|
-
--anchor-name: morphing-trigger;
|
|
6
|
-
--target-width: 220px;
|
|
7
|
-
--target-height: 120px;
|
|
8
|
-
--transition-duration: 0.2s;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/* Trigger button styles */
|
|
12
|
-
.morphingTrigger {
|
|
13
|
-
anchor-name: var(--anchor-name);
|
|
14
|
-
width: 44px;
|
|
15
|
-
aspect-ratio: 1;
|
|
16
|
-
border-radius: 50%;
|
|
17
|
-
display: grid;
|
|
18
|
-
place-items: center;
|
|
19
|
-
font-size: 24px;
|
|
20
|
-
cursor: pointer;
|
|
21
|
-
border: none;
|
|
22
|
-
background: transparent;
|
|
23
|
-
transition: background-color 0.2s;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.morphingTrigger:hover {
|
|
27
|
-
background-color: hsl(var(--muted) / 0.5);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/* Popover styles - anchored to trigger by default */
|
|
31
|
-
.morphingPopover {
|
|
32
|
-
position-anchor: var(--anchor-name);
|
|
33
|
-
left: anchor(left);
|
|
34
|
-
top: anchor(top);
|
|
35
|
-
width: anchor-size(width);
|
|
36
|
-
min-height: 0;
|
|
37
|
-
border-radius: 50%;
|
|
38
|
-
overflow: hidden;
|
|
39
|
-
padding: 0;
|
|
40
|
-
height: anchor-size(height);
|
|
41
|
-
transition-behavior: allow-discrete;
|
|
42
|
-
transition-property: display, overlay, width, height, top,
|
|
43
|
-
border-radius, left, position-anchor, transform, opacity;
|
|
44
|
-
transition-duration: var(--transition-duration);
|
|
45
|
-
transition-timing-function: ease-out;
|
|
46
|
-
transform-origin: center center;
|
|
47
|
-
opacity: 0;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/* When popover is open - expand to target size */
|
|
51
|
-
.morphingPopover:popover-open {
|
|
52
|
-
position-anchor: fixed;
|
|
53
|
-
left: 50%;
|
|
54
|
-
top: 50%;
|
|
55
|
-
transform: translate(-50%, -50%);
|
|
56
|
-
width: var(--target-width);
|
|
57
|
-
height: var(--target-height);
|
|
58
|
-
border-radius: 12px;
|
|
59
|
-
opacity: 1;
|
|
60
|
-
border: 1px solid hsl(var(--border));
|
|
61
|
-
background: hsl(var(--background));
|
|
62
|
-
box-shadow: 0 10px 40px -10px hsl(var(--foreground) / 0.3);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/* Starting style for animation - browser support required */
|
|
66
|
-
@starting-style {
|
|
67
|
-
.morphingPopover:popover-open {
|
|
68
|
-
position-anchor: var(--anchor-name);
|
|
69
|
-
left: anchor(left);
|
|
70
|
-
top: anchor(top);
|
|
71
|
-
transform: none;
|
|
72
|
-
border-radius: 50%;
|
|
73
|
-
width: anchor-size(width);
|
|
74
|
-
height: anchor-size(height);
|
|
75
|
-
opacity: 0;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/* Fallback for browsers without anchor positioning */
|
|
80
|
-
@supports not (anchor-name: none) {
|
|
81
|
-
.morphingPopover {
|
|
82
|
-
position: fixed;
|
|
83
|
-
left: 50%;
|
|
84
|
-
top: 50%;
|
|
85
|
-
transform: translate(-50%, -50%) scale(0.8);
|
|
86
|
-
opacity: 0;
|
|
87
|
-
pointer-events: none;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.morphingPopover:popover-open {
|
|
91
|
-
transform: translate(-50%, -50%) scale(1);
|
|
92
|
-
opacity: 1;
|
|
93
|
-
pointer-events: auto;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
@starting-style {
|
|
97
|
-
.morphingPopover:popover-open {
|
|
98
|
-
transform: translate(-50%, -50%) scale(0.8);
|
|
99
|
-
opacity: 0;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/* Variant: Morphing to a specific target element */
|
|
105
|
-
.morphingPopoverTargetContainer {
|
|
106
|
-
position: relative;
|
|
107
|
-
display: inline-block;
|
|
108
|
-
--anchor-name: morphing-trigger;
|
|
109
|
-
--target-anchor-name: morphing-target;
|
|
110
|
-
--transition-duration: 0.2s;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.morphingPopoverTarget {
|
|
114
|
-
position-anchor: var(--anchor-name);
|
|
115
|
-
left: anchor(left);
|
|
116
|
-
top: anchor(top);
|
|
117
|
-
width: anchor-size(width);
|
|
118
|
-
min-height: 0;
|
|
119
|
-
border-radius: 50%;
|
|
120
|
-
overflow: hidden;
|
|
121
|
-
padding: 0;
|
|
122
|
-
height: anchor-size(height);
|
|
123
|
-
transition-behavior: allow-discrete;
|
|
124
|
-
transition-property: display, overlay, width, height, top,
|
|
125
|
-
border-radius, left, position-anchor, transform, opacity;
|
|
126
|
-
transition-duration: var(--transition-duration);
|
|
127
|
-
transition-timing-function: ease-out;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.morphingPopoverTarget:popover-open {
|
|
131
|
-
position-anchor: var(--target-anchor-name);
|
|
132
|
-
left: anchor(left);
|
|
133
|
-
top: anchor(top);
|
|
134
|
-
border-radius: 12px;
|
|
135
|
-
min-width: 200px;
|
|
136
|
-
min-height: 120px;
|
|
137
|
-
border: 1px solid hsl(var(--border));
|
|
138
|
-
background: hsl(var(--background));
|
|
139
|
-
box-shadow: 0 10px 40px -10px hsl(var(--foreground) / 0.3);
|
|
140
|
-
opacity: 1;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
@starting-style {
|
|
144
|
-
.morphingPopoverTarget:popover-open {
|
|
145
|
-
position-anchor: var(--anchor-name);
|
|
146
|
-
left: anchor(left);
|
|
147
|
-
top: anchor(top);
|
|
148
|
-
border-radius: 50%;
|
|
149
|
-
width: anchor-size(width);
|
|
150
|
-
height: anchor-size(height);
|
|
151
|
-
opacity: 0;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Options for the useAutoHeight hook.
|
|
7
|
-
*/
|
|
8
|
-
interface IAutoHeightOptions {
|
|
9
|
-
/** Include parent element box model (padding, border) in measurement. */
|
|
10
|
-
includeParentBox?: boolean;
|
|
11
|
-
/** Include self element box model in measurement. */
|
|
12
|
-
includeSelfBox?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Hook that measures and tracks the height of an element with ResizeObserver.
|
|
17
|
-
* Returns a ref to attach and the current height value.
|
|
18
|
-
*
|
|
19
|
-
* @param deps - Dependency list to trigger re-measurement
|
|
20
|
-
* @param options - Configuration for box model inclusion
|
|
21
|
-
* @returns Object with ref and height
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```tsx
|
|
25
|
-
* const { ref, height } = useAutoHeight<HTMLDivElement>([content]);
|
|
26
|
-
* <div style={{ height }}><div ref={ref}>{content}</div></div>
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export function useAutoHeight<T extends HTMLElement = HTMLDivElement>(
|
|
30
|
-
deps: React.DependencyList = [],
|
|
31
|
-
options: IAutoHeightOptions = {
|
|
32
|
-
includeParentBox: true,
|
|
33
|
-
includeSelfBox: false,
|
|
34
|
-
},
|
|
35
|
-
) {
|
|
36
|
-
const ref = React.useRef<T | null>(null);
|
|
37
|
-
const roRef = React.useRef<ResizeObserver | null>(null);
|
|
38
|
-
const [height, setHeight] = React.useState(0);
|
|
39
|
-
|
|
40
|
-
const measure = React.useCallback(() => {
|
|
41
|
-
const el = ref.current;
|
|
42
|
-
if (!el) return 0;
|
|
43
|
-
|
|
44
|
-
const base = el.getBoundingClientRect().height || 0;
|
|
45
|
-
|
|
46
|
-
let extra = 0;
|
|
47
|
-
|
|
48
|
-
if (options.includeParentBox && el.parentElement) {
|
|
49
|
-
const cs = getComputedStyle(el.parentElement);
|
|
50
|
-
const paddingY =
|
|
51
|
-
(parseFloat(cs.paddingTop || '0') || 0) +
|
|
52
|
-
(parseFloat(cs.paddingBottom || '0') || 0);
|
|
53
|
-
const borderY =
|
|
54
|
-
(parseFloat(cs.borderTopWidth || '0') || 0) +
|
|
55
|
-
(parseFloat(cs.borderBottomWidth || '0') || 0);
|
|
56
|
-
const isBorderBox = cs.boxSizing === 'border-box';
|
|
57
|
-
if (isBorderBox) {
|
|
58
|
-
extra += paddingY + borderY;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (options.includeSelfBox) {
|
|
63
|
-
const cs = getComputedStyle(el);
|
|
64
|
-
const paddingY =
|
|
65
|
-
(parseFloat(cs.paddingTop || '0') || 0) +
|
|
66
|
-
(parseFloat(cs.paddingBottom || '0') || 0);
|
|
67
|
-
const borderY =
|
|
68
|
-
(parseFloat(cs.borderTopWidth || '0') || 0) +
|
|
69
|
-
(parseFloat(cs.borderBottomWidth || '0') || 0);
|
|
70
|
-
const isBorderBox = cs.boxSizing === 'border-box';
|
|
71
|
-
if (isBorderBox) {
|
|
72
|
-
extra += paddingY + borderY;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const dpr =
|
|
77
|
-
typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;
|
|
78
|
-
const total = Math.ceil((base + extra) * dpr) / dpr;
|
|
79
|
-
|
|
80
|
-
return total;
|
|
81
|
-
}, [options.includeParentBox, options.includeSelfBox]);
|
|
82
|
-
|
|
83
|
-
React.useLayoutEffect(() => {
|
|
84
|
-
const el = ref.current;
|
|
85
|
-
if (!el) return;
|
|
86
|
-
|
|
87
|
-
setHeight(measure());
|
|
88
|
-
|
|
89
|
-
if (roRef.current) {
|
|
90
|
-
roRef.current.disconnect();
|
|
91
|
-
roRef.current = null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const ro = new ResizeObserver(() => {
|
|
95
|
-
const next = measure();
|
|
96
|
-
requestAnimationFrame(() => setHeight(next));
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
ro.observe(el);
|
|
100
|
-
if (options.includeParentBox && el.parentElement) {
|
|
101
|
-
ro.observe(el.parentElement);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
roRef.current = ro;
|
|
105
|
-
|
|
106
|
-
return () => {
|
|
107
|
-
ro.disconnect();
|
|
108
|
-
roRef.current = null;
|
|
109
|
-
};
|
|
110
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
111
|
-
}, deps);
|
|
112
|
-
|
|
113
|
-
React.useLayoutEffect(() => {
|
|
114
|
-
if (height === 0) {
|
|
115
|
-
const next = measure();
|
|
116
|
-
if (next !== 0) setHeight(next);
|
|
117
|
-
}
|
|
118
|
-
}, [height, measure]);
|
|
119
|
-
|
|
120
|
-
return { ref, height } as const;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export type { IAutoHeightOptions };
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { useInView, type UseInViewOptions } from 'motion/react';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Options for the useIsInView hook.
|
|
6
|
-
*/
|
|
7
|
-
interface IUseIsInViewOptions {
|
|
8
|
-
/** Enable intersection observer tracking. */
|
|
9
|
-
inView?: boolean;
|
|
10
|
-
/** Only trigger once when element enters viewport. */
|
|
11
|
-
inViewOnce?: boolean;
|
|
12
|
-
/** Root margin for intersection observer. */
|
|
13
|
-
inViewMargin?: UseInViewOptions['margin'];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Hook that tracks whether an element is visible in the viewport
|
|
18
|
-
* using the Intersection Observer API via motion/react.
|
|
19
|
-
*
|
|
20
|
-
* @param ref - Forwarded ref to merge with local ref
|
|
21
|
-
* @param options - InView configuration options
|
|
22
|
-
* @returns Object with local ref and isInView boolean
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```tsx
|
|
26
|
-
* const { ref, isInView } = useIsInView(forwardedRef, { inView: true, inViewOnce: true });
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
function useIsInView<T extends HTMLElement = HTMLElement>(
|
|
30
|
-
ref: React.Ref<T>,
|
|
31
|
-
options: IUseIsInViewOptions = {},
|
|
32
|
-
) {
|
|
33
|
-
const { inView, inViewOnce = false, inViewMargin = '0px' } = options;
|
|
34
|
-
const localRef = React.useRef<T>(null);
|
|
35
|
-
React.useImperativeHandle(ref, () => localRef.current as T);
|
|
36
|
-
const inViewResult = useInView(localRef, {
|
|
37
|
-
once: inViewOnce,
|
|
38
|
-
margin: inViewMargin,
|
|
39
|
-
});
|
|
40
|
-
const isInView = !inView || inViewResult;
|
|
41
|
-
return { ref: localRef, isInView };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export { useIsInView, type IUseIsInViewOptions };
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
|
|
3
|
-
/** Intl.ListFormat conjunction/disjunction/unit type */
|
|
4
|
-
export type ListFormatType = 'conjunction' | 'disjunction' | 'unit';
|
|
5
|
-
|
|
6
|
-
/** Intl.ListFormat style: long, short, or narrow */
|
|
7
|
-
export type ListFormatStyle = 'long' | 'short' | 'narrow';
|
|
8
|
-
|
|
9
|
-
/** Options for useListFormat hook */
|
|
10
|
-
export interface IUseListFormatOptions {
|
|
11
|
-
locale?: string;
|
|
12
|
-
type?: ListFormatType;
|
|
13
|
-
style?: ListFormatStyle;
|
|
14
|
-
separator?: string;
|
|
15
|
-
/** If true, format as if there's one more item (avoids "and" before overflow counter) */
|
|
16
|
-
hasOverflow?: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** A single part of a formatted list (element or separator) */
|
|
20
|
-
export interface IListPart {
|
|
21
|
-
type: 'element' | 'literal';
|
|
22
|
-
value: string;
|
|
23
|
-
index: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const getDefaultLocale = (): string => {
|
|
27
|
-
if (typeof navigator !== 'undefined' && navigator.language) {
|
|
28
|
-
return navigator.language;
|
|
29
|
-
}
|
|
30
|
-
return 'en';
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Hook for locale-aware list formatting using Intl.ListFormat.
|
|
35
|
-
*
|
|
36
|
-
* Provides parts that can be rendered individually with animations.
|
|
37
|
-
* Uses the browser's Intl.ListFormat API for proper locale handling.
|
|
38
|
-
*
|
|
39
|
-
* @param items - Array of strings to format
|
|
40
|
-
* @param options - Locale, type, style, separator override, overflow flag
|
|
41
|
-
* @returns Array of list parts (elements and literals)
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```tsx
|
|
45
|
-
* // Automatic locale detection
|
|
46
|
-
* const parts = useListFormat(['Apple', 'Banana', 'Cherry']);
|
|
47
|
-
* // en: [{ type: 'element', value: 'Apple' }, { type: 'literal', value: ', ' }, ...]
|
|
48
|
-
*
|
|
49
|
-
* // Spanish
|
|
50
|
-
* const parts = useListFormat(['Apple', 'Banana'], { locale: 'es' });
|
|
51
|
-
*
|
|
52
|
-
* // Disjunction ("or")
|
|
53
|
-
* const parts = useListFormat(['A', 'B', 'C'], { type: 'disjunction' });
|
|
54
|
-
* // en: "A, B, or C"
|
|
55
|
-
*
|
|
56
|
-
* // Manual separator override
|
|
57
|
-
* const parts = useListFormat(['A', 'B', 'C'], { separator: ' | ' });
|
|
58
|
-
* // "A | B | C"
|
|
59
|
-
* ```
|
|
60
|
-
*/
|
|
61
|
-
export function useListFormat(
|
|
62
|
-
items: string[],
|
|
63
|
-
options: IUseListFormatOptions = {}
|
|
64
|
-
): IListPart[] {
|
|
65
|
-
const {
|
|
66
|
-
locale,
|
|
67
|
-
type = 'conjunction',
|
|
68
|
-
style = 'long',
|
|
69
|
-
separator,
|
|
70
|
-
hasOverflow
|
|
71
|
-
} = options;
|
|
72
|
-
|
|
73
|
-
return useMemo(() => {
|
|
74
|
-
if (items.length === 0) return [];
|
|
75
|
-
|
|
76
|
-
// Manual override mode: use fixed separator
|
|
77
|
-
if (separator !== undefined) {
|
|
78
|
-
const parts: IListPart[] = [];
|
|
79
|
-
items.forEach((item, i) => {
|
|
80
|
-
parts.push({ type: 'element', value: item, index: i });
|
|
81
|
-
if (i < items.length - 1) {
|
|
82
|
-
parts.push({ type: 'literal', value: separator, index: i });
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
return parts;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Use Intl.ListFormat for locale-aware formatting
|
|
89
|
-
const resolvedLocale = locale ?? getDefaultLocale();
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
const formatter = new Intl.ListFormat(resolvedLocale, { type, style });
|
|
93
|
-
|
|
94
|
-
// If hasOverflow, add a placeholder to make Intl.ListFormat use commas
|
|
95
|
-
// instead of "and" before the overflow counter
|
|
96
|
-
const itemsToFormat = (hasOverflow && items.length > 1)
|
|
97
|
-
? [...items, '']
|
|
98
|
-
: items;
|
|
99
|
-
|
|
100
|
-
const formatted = formatter.formatToParts(itemsToFormat);
|
|
101
|
-
|
|
102
|
-
// If hasOverflow, remove the last part (placeholder and its separator)
|
|
103
|
-
const partsToProcess = hasOverflow
|
|
104
|
-
? formatted.slice(0, -2)
|
|
105
|
-
: formatted;
|
|
106
|
-
|
|
107
|
-
let elementIndex = 0;
|
|
108
|
-
return partsToProcess.map((part): IListPart => {
|
|
109
|
-
if (part.type === 'element') {
|
|
110
|
-
return {
|
|
111
|
-
type: 'element',
|
|
112
|
-
value: part.value,
|
|
113
|
-
index: elementIndex++
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
return {
|
|
117
|
-
type: 'literal',
|
|
118
|
-
value: part.value,
|
|
119
|
-
index: Math.max(0, elementIndex - 1)
|
|
120
|
-
};
|
|
121
|
-
});
|
|
122
|
-
} catch {
|
|
123
|
-
// Fallback if Intl.ListFormat fails
|
|
124
|
-
const parts: IListPart[] = [];
|
|
125
|
-
items.forEach((item, i) => {
|
|
126
|
-
parts.push({ type: 'element', value: item, index: i });
|
|
127
|
-
if (i < items.length - 1) {
|
|
128
|
-
parts.push({ type: 'literal', value: ', ', index: i });
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
return parts;
|
|
132
|
-
}
|
|
133
|
-
}, [items, locale, type, style, separator, hasOverflow]);
|
|
134
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Props for useControlledState hook.
|
|
5
|
-
*/
|
|
6
|
-
interface IControlledStateProps<T> {
|
|
7
|
-
/** Controlled value. */
|
|
8
|
-
value?: T;
|
|
9
|
-
/** Default value for uncontrolled usage. */
|
|
10
|
-
defaultValue?: T;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Hook for controlled/uncontrolled state management.
|
|
15
|
-
* Supports both patterns seamlessly — when `value` is provided, it's controlled;
|
|
16
|
-
* otherwise uses internal state initialized from `defaultValue`.
|
|
17
|
-
*
|
|
18
|
-
* @param props - Value, defaultValue, and onChange callback
|
|
19
|
-
* @returns Tuple of [currentValue, setValue]
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```tsx
|
|
23
|
-
* const [open, setOpen] = useControlledState({
|
|
24
|
-
* value: props.open,
|
|
25
|
-
* defaultValue: false,
|
|
26
|
-
* onChange: props.onOpenChange,
|
|
27
|
-
* });
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
-
export function useControlledState<T, Rest extends any[] = []>(
|
|
32
|
-
props: IControlledStateProps<T> & {
|
|
33
|
-
onChange?: (value: T, ...args: Rest) => void;
|
|
34
|
-
},
|
|
35
|
-
): readonly [T, (next: T, ...args: Rest) => void] {
|
|
36
|
-
const { value, defaultValue, onChange } = props;
|
|
37
|
-
|
|
38
|
-
const [state, setInternalState] = React.useState<T>(
|
|
39
|
-
value !== undefined ? value : (defaultValue as T),
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
React.useEffect(() => {
|
|
43
|
-
if (value !== undefined) setInternalState(value);
|
|
44
|
-
}, [value]);
|
|
45
|
-
|
|
46
|
-
const setState = React.useCallback(
|
|
47
|
-
(next: T, ...args: Rest) => {
|
|
48
|
-
setInternalState(next);
|
|
49
|
-
onChange?.(next, ...args);
|
|
50
|
-
},
|
|
51
|
-
[onChange],
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
return [state, setState] as const;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export type { IControlledStateProps };
|