@refraction-ui/astro 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accordion/index.ts +1 -0
- package/dist/ai/ai-manager.ts +121 -0
- package/dist/ai/index.ts +23 -0
- package/dist/ai/mock-providers.ts +68 -0
- package/dist/ai/sanitizer.ts +174 -0
- package/dist/ai/tts-manager.ts +80 -0
- package/dist/ai/types.ts +72 -0
- package/dist/animated-text/animated-text.styles.ts +28 -0
- package/dist/animated-text/animated-text.ts +83 -0
- package/dist/animated-text/index.ts +15 -0
- package/dist/app-shell/app-shell.ts +252 -0
- package/dist/app-shell/auth-shell.ts +84 -0
- package/dist/app-shell/index.ts +22 -0
- package/dist/app-shell/page-shell.ts +125 -0
- package/dist/astro-accordion/Button.astro +1 -1
- package/dist/astro-ai/index.ts +1 -1
- package/dist/astro-animated-text/AnimatedText.astro +1 -1
- package/dist/astro-animated-text/TypewriterText.astro +1 -1
- package/dist/astro-app-shell/AppShell.astro +1 -1
- package/dist/astro-app-shell/AuthShell.astro +1 -1
- package/dist/astro-app-shell/AuthShellCard.astro +1 -1
- package/dist/astro-app-shell/PageShell.astro +1 -1
- package/dist/astro-app-shell/PageShellSection.astro +1 -1
- package/dist/astro-app-shell/index.ts +1 -1
- package/dist/astro-auth/index.ts +2 -2
- package/dist/astro-avatar/Avatar.astro +1 -1
- package/dist/astro-avatar/AvatarFallback.astro +1 -1
- package/dist/astro-avatar/AvatarImage.astro +1 -1
- package/dist/astro-avatar-group/AvatarGroup.astro +1 -1
- package/dist/astro-badge/Badge.astro +1 -1
- package/dist/astro-bottom-nav/BottomNav.astro +1 -1
- package/dist/astro-breadcrumbs/Breadcrumbs.astro +1 -1
- package/dist/astro-button/Button.astro +1 -1
- package/dist/astro-calendar/Calendar.astro +1 -1
- package/dist/astro-card/Card.astro +1 -1
- package/dist/astro-card/CardContent.astro +1 -1
- package/dist/astro-card/CardDescription.astro +1 -1
- package/dist/astro-card/CardFooter.astro +1 -1
- package/dist/astro-card/CardHeader.astro +1 -1
- package/dist/astro-card/CardTitle.astro +1 -1
- package/dist/astro-card-grid/CardGrid.astro +1 -1
- package/dist/astro-carousel/Button.astro +1 -1
- package/dist/astro-charts/Bars.astro +1 -1
- package/dist/astro-charts/Chart.astro +1 -1
- package/dist/astro-charts/Circles.astro +1 -1
- package/dist/astro-charts/Histogram.astro +1 -1
- package/dist/astro-charts/Line.astro +1 -1
- package/dist/astro-charts/PieChart.astro +1 -1
- package/dist/astro-charts/ScatterPlot.astro +1 -1
- package/dist/astro-charts/index.ts +1 -1
- package/dist/astro-checkbox/Checkbox.astro +1 -1
- package/dist/astro-code-editor/CodeEditor.astro +1 -1
- package/dist/astro-collapsible/Collapsible.astro +1 -1
- package/dist/astro-collapsible/CollapsibleContent.astro +1 -1
- package/dist/astro-command/Command.astro +1 -1
- package/dist/astro-command/CommandGroup.astro +1 -1
- package/dist/astro-command/CommandInput.astro +1 -1
- package/dist/astro-command/CommandItem.astro +1 -1
- package/dist/astro-command-input/CommandInput.astro +1 -1
- package/dist/astro-content-protection/ContentProtection.astro +1 -1
- package/dist/astro-data-table/DataTable.astro +1 -1
- package/dist/astro-date-picker/DatePicker.astro +1 -1
- package/dist/astro-device-frame/DeviceFrame.astro +1 -1
- package/dist/astro-dialog/DialogContent.astro +1 -1
- package/dist/astro-dialog/DialogOverlay.astro +1 -1
- package/dist/astro-diff-viewer/DiffViewer.astro +1 -1
- package/dist/astro-dropdown-menu/DropdownMenuContent.astro +1 -1
- package/dist/astro-dropdown-menu/DropdownMenuItem.astro +1 -1
- package/dist/astro-emoji-picker/EmojiPicker.astro +1 -1
- package/dist/astro-feedback-dialog/FeedbackDialog.astro +1 -1
- package/dist/astro-file-upload/FileUpload.astro +1 -1
- package/dist/astro-footer/Footer.astro +1 -1
- package/dist/astro-inline-editor/InlineEditor.astro +1 -1
- package/dist/astro-input/Input.astro +1 -1
- package/dist/astro-input-group/InputGroup.astro +1 -1
- package/dist/astro-input-group/InputGroupAddon.astro +1 -1
- package/dist/astro-input-group/InputGroupButton.astro +1 -1
- package/dist/astro-install-prompt/InstallPrompt.astro +1 -1
- package/dist/astro-keyboard-shortcut/ShortcutBadge.astro +1 -1
- package/dist/astro-language-selector/LanguageSelector.astro +1 -1
- package/dist/astro-link-card/LinkCard.astro +1 -1
- package/dist/astro-markdown-renderer/MarkdownRenderer.astro +1 -1
- package/dist/astro-mobile-nav/MobileNav.astro +1 -1
- package/dist/astro-mobile-nav/MobileNavLink.astro +1 -1
- package/dist/astro-mobile-nav/MobileNavTrigger.astro +1 -1
- package/dist/astro-navbar/Navbar.astro +1 -1
- package/dist/astro-otp-input/OtpInput.astro +1 -1
- package/dist/astro-payment/Payment.astro +1 -1
- package/dist/astro-popover/PopoverContent.astro +1 -1
- package/dist/astro-presence-indicator/PresenceIndicator.astro +1 -1
- package/dist/astro-progress-display/BadgeDisplay.astro +1 -1
- package/dist/astro-progress-display/ProgressBar.astro +1 -1
- package/dist/astro-progress-display/StatsGrid.astro +1 -1
- package/dist/astro-radio/RadioGroup.astro +1 -1
- package/dist/astro-radio/RadioItem.astro +1 -1
- package/dist/astro-reaction-bar/ReactionBar.astro +1 -1
- package/dist/astro-resizable-layout/ResizableDivider.astro +1 -1
- package/dist/astro-resizable-layout/ResizableLayout.astro +1 -1
- package/dist/astro-resizable-layout/ResizablePane.astro +1 -1
- package/dist/astro-resizable-layout/index.ts +1 -1
- package/dist/astro-rich-editor/index.ts +2 -2
- package/dist/astro-search-bar/SearchBar.astro +1 -1
- package/dist/astro-search-bar/SearchResults.astro +1 -1
- package/dist/astro-select/Select.astro +1 -1
- package/dist/astro-select/SelectContent.astro +1 -1
- package/dist/astro-select/SelectItem.astro +1 -1
- package/dist/astro-select/SelectTrigger.astro +1 -1
- package/dist/astro-sidebar/Sidebar.astro +1 -1
- package/dist/astro-skeleton/Skeleton.astro +1 -1
- package/dist/astro-slide-viewer/SlideViewer.astro +1 -1
- package/dist/astro-status-indicator/StatusIndicator.astro +1 -1
- package/dist/astro-switch/Switch.astro +1 -1
- package/dist/astro-table-of-contents/TableOfContents.astro +1 -1
- package/dist/astro-tabs/Tabs.astro +1 -1
- package/dist/astro-tabs/TabsList.astro +1 -1
- package/dist/astro-tabs/TabsTrigger.astro +1 -1
- package/dist/astro-textarea/Textarea.astro +1 -1
- package/dist/astro-theme/ThemeScript.astro +1 -1
- package/dist/astro-thread-view/ThreadView.astro +1 -1
- package/dist/astro-toast/Toast.astro +1 -1
- package/dist/astro-tooltip/TooltipContent.astro +1 -1
- package/dist/astro-version-selector/VersionSelector.astro +1 -1
- package/dist/astro-video-player/VideoPlayer.astro +1 -1
- package/dist/auth/adapters/mock.ts +74 -0
- package/dist/auth/auth-machine.ts +120 -0
- package/dist/auth/index.ts +22 -0
- package/dist/auth/rbac.ts +44 -0
- package/dist/auth/types.ts +44 -0
- package/dist/avatar/avatar.styles.ts +47 -0
- package/dist/avatar/avatar.ts +73 -0
- package/dist/avatar/index.ts +15 -0
- package/dist/avatar-group/avatar-group.styles.ts +63 -0
- package/dist/avatar-group/avatar-group.ts +106 -0
- package/dist/avatar-group/index.ts +18 -0
- package/dist/badge/badge.styles.ts +24 -0
- package/dist/badge/badge.ts +48 -0
- package/dist/badge/index.ts +10 -0
- package/dist/bottom-nav/bottom-nav.styles.ts +18 -0
- package/dist/bottom-nav/bottom-nav.ts +33 -0
- package/dist/bottom-nav/index.ts +2 -0
- package/dist/breadcrumbs/breadcrumbs.styles.ts +20 -0
- package/dist/breadcrumbs/breadcrumbs.ts +72 -0
- package/dist/breadcrumbs/index.ts +8 -0
- package/dist/button/button.styles.ts +38 -0
- package/dist/button/button.ts +64 -0
- package/dist/button/index.ts +10 -0
- package/dist/calendar/calendar.styles.ts +21 -0
- package/dist/calendar/calendar.ts +202 -0
- package/dist/calendar/index.ts +9 -0
- package/dist/callout/callout.ts +1 -0
- package/dist/callout/index.ts +1 -0
- package/dist/card/card.styles.ts +55 -0
- package/dist/card/card.ts +45 -0
- package/dist/card/index.ts +20 -0
- package/dist/card-grid/card-grid.ts +9 -0
- package/dist/card-grid/index.ts +1 -0
- package/dist/carousel/index.ts +1 -0
- package/dist/charts/axis.ts +50 -0
- package/dist/charts/chart.ts +73 -0
- package/dist/charts/colorblind.ts +100 -0
- package/dist/charts/data.ts +72 -0
- package/dist/charts/geometry.ts +54 -0
- package/dist/charts/index.ts +61 -0
- package/dist/charts/scales.ts +151 -0
- package/dist/checkbox/checkbox.styles.ts +33 -0
- package/dist/checkbox/checkbox.ts +73 -0
- package/dist/checkbox/index.ts +10 -0
- package/dist/code-block/code-block.ts +1 -0
- package/dist/code-block/index.ts +1 -0
- package/dist/code-editor/code-editor.styles.ts +32 -0
- package/dist/code-editor/code-editor.ts +140 -0
- package/dist/code-editor/index.ts +9 -0
- package/dist/collapsible/collapsible.styles.ts +14 -0
- package/dist/collapsible/collapsible.ts +93 -0
- package/dist/collapsible/index.ts +7 -0
- package/dist/command/command.styles.ts +27 -0
- package/dist/command/command.ts +179 -0
- package/dist/command/index.ts +14 -0
- package/dist/command-input/command-input.ts +176 -0
- package/dist/command-input/index.ts +1 -0
- package/dist/content-protection/content-protection.styles.ts +9 -0
- package/dist/content-protection/content-protection.ts +68 -0
- package/dist/content-protection/index.ts +8 -0
- package/dist/data-table/data-table.styles.ts +55 -0
- package/dist/data-table/data-table.ts +183 -0
- package/dist/data-table/index.ts +15 -0
- package/dist/date-picker/date-picker.styles.ts +31 -0
- package/dist/date-picker/date-picker.ts +333 -0
- package/dist/date-picker/index.ts +18 -0
- package/dist/device-frame/device-frame.styles.ts +21 -0
- package/dist/device-frame/device-frame.ts +112 -0
- package/dist/device-frame/index.ts +10 -0
- package/dist/dialog/dialog.styles.ts +8 -0
- package/dist/dialog/dialog.ts +133 -0
- package/dist/dialog/index.ts +8 -0
- package/dist/diff-viewer/diff-viewer.styles.ts +124 -0
- package/dist/diff-viewer/diff-viewer.ts +255 -0
- package/dist/diff-viewer/index.ts +20 -0
- package/dist/dropdown-menu/dropdown-menu.styles.ts +9 -0
- package/dist/dropdown-menu/dropdown-menu.ts +139 -0
- package/dist/dropdown-menu/index.ts +9 -0
- package/dist/emoji-picker/emoji-data.ts +282 -0
- package/dist/emoji-picker/emoji-picker.styles.ts +32 -0
- package/dist/emoji-picker/emoji-picker.ts +165 -0
- package/dist/emoji-picker/index.ts +25 -0
- package/dist/feedback-dialog/feedback-dialog.styles.ts +15 -0
- package/dist/feedback-dialog/feedback-dialog.ts +147 -0
- package/dist/feedback-dialog/index.ts +11 -0
- package/dist/file-upload/file-upload.styles.ts +30 -0
- package/dist/file-upload/file-upload.ts +260 -0
- package/dist/file-upload/index.ts +19 -0
- package/dist/footer/footer.styles.ts +5 -0
- package/dist/footer/footer.ts +32 -0
- package/dist/footer/index.ts +9 -0
- package/dist/index.ts +1 -1
- package/dist/inline-editor/index.ts +13 -0
- package/dist/inline-editor/inline-editor.styles.ts +42 -0
- package/dist/inline-editor/inline-editor.ts +96 -0
- package/dist/input/index.ts +9 -0
- package/dist/input/input.styles.ts +15 -0
- package/dist/input/input.ts +84 -0
- package/dist/input-group/index.ts +13 -0
- package/dist/input-group/input-group.styles.ts +50 -0
- package/dist/input-group/input-group.ts +44 -0
- package/dist/install-prompt/index.ts +9 -0
- package/dist/install-prompt/install-prompt.styles.ts +5 -0
- package/dist/install-prompt/install-prompt.ts +68 -0
- package/dist/keyboard-shortcut/index.ts +12 -0
- package/dist/keyboard-shortcut/keyboard-shortcut.styles.ts +8 -0
- package/dist/keyboard-shortcut/keyboard-shortcut.ts +153 -0
- package/dist/language-selector/index.ts +8 -0
- package/dist/language-selector/language-selector.styles.ts +28 -0
- package/dist/language-selector/language-selector.ts +149 -0
- package/dist/link-card/index.ts +1 -0
- package/dist/link-card/link-card.ts +9 -0
- package/dist/markdown-renderer/index.ts +8 -0
- package/dist/markdown-renderer/markdown-renderer.styles.ts +31 -0
- package/dist/markdown-renderer/markdown-renderer.ts +251 -0
- package/dist/mobile-nav/index.ts +14 -0
- package/dist/mobile-nav/mobile-nav.styles.ts +39 -0
- package/dist/mobile-nav/mobile-nav.ts +74 -0
- package/dist/navbar/index.ts +9 -0
- package/dist/navbar/navbar.styles.ts +29 -0
- package/dist/navbar/navbar.ts +39 -0
- package/dist/otp-input/index.ts +13 -0
- package/dist/otp-input/otp-input.styles.ts +51 -0
- package/dist/otp-input/otp-input.ts +198 -0
- package/dist/pagination/index.ts +1 -0
- package/dist/payment/index.ts +1 -0
- package/dist/payment/payment.ts +16 -0
- package/dist/popover/index.ts +7 -0
- package/dist/popover/popover.styles.ts +16 -0
- package/dist/popover/popover.ts +89 -0
- package/dist/presence-indicator/index.ts +14 -0
- package/dist/presence-indicator/presence-indicator.styles.ts +29 -0
- package/dist/presence-indicator/presence-indicator.ts +63 -0
- package/dist/progress-display/index.ts +15 -0
- package/dist/progress-display/progress-display.styles.ts +72 -0
- package/dist/progress-display/progress-display.ts +60 -0
- package/dist/radio/index.ts +8 -0
- package/dist/radio/radio.styles.ts +34 -0
- package/dist/radio/radio.ts +70 -0
- package/dist/reaction-bar/index.ts +14 -0
- package/dist/reaction-bar/reaction-bar.styles.ts +23 -0
- package/dist/reaction-bar/reaction-bar.ts +87 -0
- package/dist/resizable-layout/index.ts +12 -0
- package/dist/resizable-layout/resizable-layout.styles.ts +50 -0
- package/dist/resizable-layout/resizable-layout.ts +139 -0
- package/dist/rich-editor/editor.ts +342 -0
- package/dist/rich-editor/emoji.ts +175 -0
- package/dist/rich-editor/history.ts +79 -0
- package/dist/rich-editor/index.ts +106 -0
- package/dist/rich-editor/markdown-shortcuts.ts +248 -0
- package/dist/rich-editor/mentions.ts +162 -0
- package/dist/rich-editor/model.ts +206 -0
- package/dist/rich-editor/operations.ts +772 -0
- package/dist/rich-editor/selection.ts +157 -0
- package/dist/rich-editor/serialization.ts +711 -0
- package/dist/rich-editor/slash-commands.ts +212 -0
- package/dist/search-bar/index.ts +7 -0
- package/dist/search-bar/search-bar.styles.ts +29 -0
- package/dist/search-bar/search-bar.ts +130 -0
- package/dist/select/index.ts +13 -0
- package/dist/select/select.styles.ts +52 -0
- package/dist/select/select.ts +152 -0
- package/dist/sidebar/index.ts +9 -0
- package/dist/sidebar/sidebar.styles.ts +27 -0
- package/dist/sidebar/sidebar.ts +59 -0
- package/dist/skeleton/index.ts +8 -0
- package/dist/skeleton/skeleton.styles.ts +16 -0
- package/dist/skeleton/skeleton.ts +39 -0
- package/dist/skip-to-content/index.ts +1 -0
- package/dist/skip-to-content/skip-to-content.ts +1 -0
- package/dist/slide-viewer/index.ts +16 -0
- package/dist/slide-viewer/slide-viewer.styles.ts +57 -0
- package/dist/slide-viewer/slide-viewer.ts +152 -0
- package/dist/slider/index.ts +1 -0
- package/dist/slider/slider.ts +2 -0
- package/dist/status-indicator/index.ts +15 -0
- package/dist/status-indicator/status-indicator.styles.ts +41 -0
- package/dist/status-indicator/status-indicator.ts +66 -0
- package/dist/steps/index.ts +1 -0
- package/dist/steps/steps.ts +1 -0
- package/dist/switch/index.ts +7 -0
- package/dist/switch/switch.styles.ts +55 -0
- package/dist/switch/switch.ts +63 -0
- package/dist/table-of-contents/index.ts +1 -0
- package/dist/table-of-contents/table-of-contents.ts +37 -0
- package/dist/tabs/index.ts +8 -0
- package/dist/tabs/tabs.styles.ts +9 -0
- package/dist/tabs/tabs.ts +133 -0
- package/dist/textarea/index.ts +7 -0
- package/dist/textarea/textarea.styles.ts +15 -0
- package/dist/textarea/textarea.ts +59 -0
- package/dist/theme/dom-adapters.ts +60 -0
- package/dist/theme/index.ts +18 -0
- package/dist/theme/theme-machine.ts +130 -0
- package/dist/theme/theme-script.ts +18 -0
- package/dist/thread-view/index.ts +26 -0
- package/dist/thread-view/thread-view.styles.ts +35 -0
- package/dist/thread-view/thread-view.ts +176 -0
- package/dist/toast/index.ts +13 -0
- package/dist/toast/toast.styles.ts +19 -0
- package/dist/toast/toast.ts +203 -0
- package/dist/tooltip/index.ts +7 -0
- package/dist/tooltip/tooltip.styles.ts +16 -0
- package/dist/tooltip/tooltip.ts +109 -0
- package/dist/version-selector/index.ts +12 -0
- package/dist/version-selector/version-selector.styles.ts +41 -0
- package/dist/version-selector/version-selector.ts +136 -0
- package/dist/video-player/index.ts +12 -0
- package/dist/video-player/video-player.styles.ts +42 -0
- package/dist/video-player/video-player.ts +100 -0
- package/package.json +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { KeyboardHandlerMap } from '../shared/index.ts'
|
|
2
|
+
import { generateId, Keys, createKeyboardHandler } from '../shared/index.ts'
|
|
3
|
+
|
|
4
|
+
export interface LanguageOption {
|
|
5
|
+
value: string
|
|
6
|
+
label: string
|
|
7
|
+
group?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface LanguageSelectorProps {
|
|
11
|
+
value?: string | string[]
|
|
12
|
+
onValueChange?: (value: string | string[]) => void
|
|
13
|
+
options: LanguageOption[]
|
|
14
|
+
multiple?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface LanguageSelectorAPI {
|
|
18
|
+
/** Current state */
|
|
19
|
+
state: { selectedValues: string[]; isOpen: boolean }
|
|
20
|
+
/** Props to spread on the trigger element */
|
|
21
|
+
triggerProps: {
|
|
22
|
+
'aria-expanded': boolean
|
|
23
|
+
'aria-controls': string
|
|
24
|
+
'aria-haspopup': 'listbox'
|
|
25
|
+
role: 'combobox'
|
|
26
|
+
}
|
|
27
|
+
/** Props to spread on the content/dropdown element */
|
|
28
|
+
contentProps: {
|
|
29
|
+
role: 'listbox'
|
|
30
|
+
id: string
|
|
31
|
+
'aria-multiselectable'?: boolean
|
|
32
|
+
}
|
|
33
|
+
/** Get props for an individual option */
|
|
34
|
+
getOptionProps: (value: string) => {
|
|
35
|
+
role: 'option'
|
|
36
|
+
'aria-selected': boolean
|
|
37
|
+
'data-value': string
|
|
38
|
+
}
|
|
39
|
+
/** Toggle an option's selection */
|
|
40
|
+
toggle: (value: string) => void
|
|
41
|
+
/** Open the dropdown */
|
|
42
|
+
open: () => void
|
|
43
|
+
/** Close the dropdown */
|
|
44
|
+
close: () => void
|
|
45
|
+
/** Keyboard handler map */
|
|
46
|
+
keyboardHandlers: KeyboardHandlerMap
|
|
47
|
+
/** Pre-built keyboard event handler */
|
|
48
|
+
handleKeyDown: (event: KeyboardEvent) => void
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function createLanguageSelector(props: LanguageSelectorProps): LanguageSelectorAPI {
|
|
52
|
+
const {
|
|
53
|
+
value: initialValue,
|
|
54
|
+
onValueChange,
|
|
55
|
+
options: _options,
|
|
56
|
+
multiple = false,
|
|
57
|
+
} = props
|
|
58
|
+
|
|
59
|
+
const contentId = generateId('rfr-lang-sel')
|
|
60
|
+
|
|
61
|
+
let selectedValues: string[] = Array.isArray(initialValue)
|
|
62
|
+
? [...initialValue]
|
|
63
|
+
: initialValue
|
|
64
|
+
? [initialValue]
|
|
65
|
+
: []
|
|
66
|
+
let isOpen = false
|
|
67
|
+
|
|
68
|
+
const state = { selectedValues: [...selectedValues], isOpen }
|
|
69
|
+
|
|
70
|
+
function setOpen(value: boolean) {
|
|
71
|
+
isOpen = value
|
|
72
|
+
state.isOpen = value
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function toggle(value: string) {
|
|
76
|
+
if (multiple) {
|
|
77
|
+
const index = selectedValues.indexOf(value)
|
|
78
|
+
if (index >= 0) {
|
|
79
|
+
selectedValues.splice(index, 1)
|
|
80
|
+
} else {
|
|
81
|
+
selectedValues.push(value)
|
|
82
|
+
}
|
|
83
|
+
state.selectedValues = [...selectedValues]
|
|
84
|
+
onValueChange?.([...selectedValues])
|
|
85
|
+
} else {
|
|
86
|
+
selectedValues = [value]
|
|
87
|
+
state.selectedValues = [value]
|
|
88
|
+
onValueChange?.(value)
|
|
89
|
+
setOpen(false)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const triggerProps = {
|
|
94
|
+
'aria-expanded': isOpen,
|
|
95
|
+
'aria-controls': contentId,
|
|
96
|
+
'aria-haspopup': 'listbox' as const,
|
|
97
|
+
role: 'combobox' as const,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const contentProps: LanguageSelectorAPI['contentProps'] = {
|
|
101
|
+
role: 'listbox' as const,
|
|
102
|
+
id: contentId,
|
|
103
|
+
...(multiple ? { 'aria-multiselectable': true } : {}),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getOptionProps(value: string) {
|
|
107
|
+
return {
|
|
108
|
+
role: 'option' as const,
|
|
109
|
+
'aria-selected': selectedValues.includes(value),
|
|
110
|
+
'data-value': value,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const keyboardHandlers: KeyboardHandlerMap = {
|
|
115
|
+
[Keys.Escape]: () => {
|
|
116
|
+
setOpen(false)
|
|
117
|
+
},
|
|
118
|
+
[Keys.Enter]: () => {
|
|
119
|
+
setOpen(!isOpen)
|
|
120
|
+
},
|
|
121
|
+
[Keys.Space]: () => {
|
|
122
|
+
setOpen(!isOpen)
|
|
123
|
+
},
|
|
124
|
+
[Keys.ArrowDown]: () => {
|
|
125
|
+
if (!isOpen) {
|
|
126
|
+
setOpen(true)
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
[Keys.ArrowUp]: () => {
|
|
130
|
+
if (!isOpen) {
|
|
131
|
+
setOpen(true)
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const handleKeyDown = createKeyboardHandler(keyboardHandlers)
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
state,
|
|
140
|
+
triggerProps,
|
|
141
|
+
contentProps,
|
|
142
|
+
getOptionProps,
|
|
143
|
+
toggle,
|
|
144
|
+
open: () => setOpen(true),
|
|
145
|
+
close: () => setOpen(false),
|
|
146
|
+
keyboardHandlers,
|
|
147
|
+
handleKeyDown,
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './link-card.js'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
import type { TokenContract } from '../shared/index.ts'
|
|
3
|
+
|
|
4
|
+
export const markdownRendererTokens: TokenContract = {
|
|
5
|
+
name: 'markdown-renderer',
|
|
6
|
+
tokens: {
|
|
7
|
+
bg: { variable: '--rfr-markdown-bg', fallback: 'hsl(var(--background))' },
|
|
8
|
+
fg: { variable: '--rfr-markdown-fg', fallback: 'hsl(var(--foreground))' },
|
|
9
|
+
codeBg: { variable: '--rfr-markdown-code-bg', fallback: 'hsl(var(--muted))' },
|
|
10
|
+
linkColor: { variable: '--rfr-markdown-link', fallback: 'hsl(var(--primary))' },
|
|
11
|
+
borderColor: { variable: '--rfr-markdown-border', fallback: 'hsl(var(--border))' },
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const proseVariants = cva({
|
|
16
|
+
base: 'prose max-w-none text-foreground leading-relaxed',
|
|
17
|
+
variants: {
|
|
18
|
+
size: {
|
|
19
|
+
sm: 'prose-sm text-sm',
|
|
20
|
+
default: 'prose-base text-base',
|
|
21
|
+
lg: 'prose-lg text-lg',
|
|
22
|
+
},
|
|
23
|
+
theme: {
|
|
24
|
+
light: 'bg-white text-gray-900',
|
|
25
|
+
dark: 'bg-gray-900 text-gray-100',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: {
|
|
29
|
+
size: 'default',
|
|
30
|
+
},
|
|
31
|
+
})
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
export interface ComponentDef {
|
|
4
|
+
type: string
|
|
5
|
+
pattern: RegExp
|
|
6
|
+
props: Record<string, string>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface MarkdownRendererProps {
|
|
10
|
+
content: string
|
|
11
|
+
components?: Record<string, ComponentDef>
|
|
12
|
+
linkResolver?: (url: string) => string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MarkdownRendererAPI {
|
|
16
|
+
/** Rendered HTML string */
|
|
17
|
+
html: string
|
|
18
|
+
/** Extracted component slots from custom component definitions */
|
|
19
|
+
components: Array<{ name: string; props: Record<string, string> }>
|
|
20
|
+
/** ARIA attributes for the rendered content container */
|
|
21
|
+
ariaProps: Partial<AccessibilityProps>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Escape HTML entities to prevent injection in non-code contexts.
|
|
26
|
+
*/
|
|
27
|
+
function escapeHtml(text: string): string {
|
|
28
|
+
return text
|
|
29
|
+
.replace(/&/g, '&')
|
|
30
|
+
.replace(/</g, '<')
|
|
31
|
+
.replace(/>/g, '>')
|
|
32
|
+
.replace(/"/g, '"')
|
|
33
|
+
.replace(/'/g, ''')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parse inline markdown elements: bold, italic, code, links.
|
|
38
|
+
*/
|
|
39
|
+
function parseInline(text: string, linkResolver?: (url: string) => string): string {
|
|
40
|
+
let result = escapeHtml(text)
|
|
41
|
+
|
|
42
|
+
// Inline code (backticks) — must come before bold/italic to avoid conflicts
|
|
43
|
+
result = result.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
44
|
+
|
|
45
|
+
// Bold (**text** or __text__)
|
|
46
|
+
result = result.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
47
|
+
result = result.replace(/__([^_]+)__/g, '<strong>$1</strong>')
|
|
48
|
+
|
|
49
|
+
// Italic (*text* or _text_)
|
|
50
|
+
result = result.replace(/\*([^*]+)\*/g, '<em>$1</em>')
|
|
51
|
+
result = result.replace(/_([^_]+)_/g, '<em>$1</em>')
|
|
52
|
+
|
|
53
|
+
// Links [text](url) — reject javascript: URLs for XSS safety
|
|
54
|
+
result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, text, url) => {
|
|
55
|
+
if (/^\s*javascript\s*:/i.test(url)) {
|
|
56
|
+
return text
|
|
57
|
+
}
|
|
58
|
+
const resolvedUrl = linkResolver ? linkResolver(url) : url
|
|
59
|
+
return `<a href="${resolvedUrl}">${text}</a>`
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
return result
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Parse a full markdown string into HTML.
|
|
67
|
+
* Handles: headings, bold, italic, links, code (inline & block),
|
|
68
|
+
* lists (unordered & ordered), blockquotes, horizontal rules.
|
|
69
|
+
*/
|
|
70
|
+
function parseMarkdown(content: string, linkResolver?: (url: string) => string): string {
|
|
71
|
+
const lines = content.split('\n')
|
|
72
|
+
const outputLines: string[] = []
|
|
73
|
+
let inCodeBlock = false
|
|
74
|
+
let codeBlockContent: string[] = []
|
|
75
|
+
let codeBlockLang = ''
|
|
76
|
+
let inList: 'ul' | 'ol' | null = null
|
|
77
|
+
let inBlockquote = false
|
|
78
|
+
|
|
79
|
+
function closeList() {
|
|
80
|
+
if (inList) {
|
|
81
|
+
outputLines.push(inList === 'ul' ? '</ul>' : '</ol>')
|
|
82
|
+
inList = null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function closeBlockquote() {
|
|
87
|
+
if (inBlockquote) {
|
|
88
|
+
outputLines.push('</blockquote>')
|
|
89
|
+
inBlockquote = false
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < lines.length; i++) {
|
|
94
|
+
const line = lines[i]
|
|
95
|
+
|
|
96
|
+
// Code blocks (fenced with ```)
|
|
97
|
+
if (line.trimStart().startsWith('```')) {
|
|
98
|
+
if (inCodeBlock) {
|
|
99
|
+
// Closing code block
|
|
100
|
+
outputLines.push(`<pre><code${codeBlockLang ? ` class="language-${escapeHtml(codeBlockLang)}"` : ''}>${escapeHtml(codeBlockContent.join('\n'))}</code></pre>`)
|
|
101
|
+
codeBlockContent = []
|
|
102
|
+
codeBlockLang = ''
|
|
103
|
+
inCodeBlock = false
|
|
104
|
+
} else {
|
|
105
|
+
// Opening code block
|
|
106
|
+
closeList()
|
|
107
|
+
closeBlockquote()
|
|
108
|
+
inCodeBlock = true
|
|
109
|
+
codeBlockLang = line.trimStart().slice(3).trim()
|
|
110
|
+
}
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (inCodeBlock) {
|
|
115
|
+
codeBlockContent.push(line)
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Horizontal rule (--- or ___ or ***)
|
|
120
|
+
if (/^(\s*[-*_]\s*){3,}$/.test(line)) {
|
|
121
|
+
closeList()
|
|
122
|
+
closeBlockquote()
|
|
123
|
+
outputLines.push('<hr />')
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Headings (# through ######)
|
|
128
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+)$/)
|
|
129
|
+
if (headingMatch) {
|
|
130
|
+
closeList()
|
|
131
|
+
closeBlockquote()
|
|
132
|
+
const level = headingMatch[1].length
|
|
133
|
+
const text = parseInline(headingMatch[2], linkResolver)
|
|
134
|
+
outputLines.push(`<h${level}>${text}</h${level}>`)
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Blockquotes (> text)
|
|
139
|
+
const blockquoteMatch = line.match(/^>\s?(.*)$/)
|
|
140
|
+
if (blockquoteMatch) {
|
|
141
|
+
closeList()
|
|
142
|
+
if (!inBlockquote) {
|
|
143
|
+
inBlockquote = true
|
|
144
|
+
outputLines.push('<blockquote>')
|
|
145
|
+
}
|
|
146
|
+
const text = blockquoteMatch[1].trim()
|
|
147
|
+
if (text) {
|
|
148
|
+
outputLines.push(`<p>${parseInline(text, linkResolver)}</p>`)
|
|
149
|
+
}
|
|
150
|
+
continue
|
|
151
|
+
} else if (inBlockquote) {
|
|
152
|
+
closeBlockquote()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Unordered list items (- or * at start)
|
|
156
|
+
const ulMatch = line.match(/^[\s]*[-*+]\s+(.+)$/)
|
|
157
|
+
if (ulMatch) {
|
|
158
|
+
closeBlockquote()
|
|
159
|
+
if (inList !== 'ul') {
|
|
160
|
+
closeList()
|
|
161
|
+
inList = 'ul'
|
|
162
|
+
outputLines.push('<ul>')
|
|
163
|
+
}
|
|
164
|
+
outputLines.push(`<li>${parseInline(ulMatch[1], linkResolver)}</li>`)
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Ordered list items (1. 2. etc.)
|
|
169
|
+
const olMatch = line.match(/^[\s]*\d+\.\s+(.+)$/)
|
|
170
|
+
if (olMatch) {
|
|
171
|
+
closeBlockquote()
|
|
172
|
+
if (inList !== 'ol') {
|
|
173
|
+
closeList()
|
|
174
|
+
inList = 'ol'
|
|
175
|
+
outputLines.push('<ol>')
|
|
176
|
+
}
|
|
177
|
+
outputLines.push(`<li>${parseInline(olMatch[1], linkResolver)}</li>`)
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// If we were in a list and hit a non-list line, close the list
|
|
182
|
+
if (inList) {
|
|
183
|
+
closeList()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Empty line
|
|
187
|
+
if (line.trim() === '') {
|
|
188
|
+
continue
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Regular paragraph
|
|
192
|
+
outputLines.push(`<p>${parseInline(line, linkResolver)}</p>`)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Close any open blocks
|
|
196
|
+
if (inCodeBlock) {
|
|
197
|
+
outputLines.push(`<pre><code${codeBlockLang ? ` class="language-${escapeHtml(codeBlockLang)}"` : ''}>${escapeHtml(codeBlockContent.join('\n'))}</code></pre>`)
|
|
198
|
+
}
|
|
199
|
+
closeList()
|
|
200
|
+
closeBlockquote()
|
|
201
|
+
|
|
202
|
+
return outputLines.join('\n')
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Extract custom component slots from content based on component definitions.
|
|
207
|
+
*/
|
|
208
|
+
function extractComponents(
|
|
209
|
+
content: string,
|
|
210
|
+
components?: Record<string, ComponentDef>,
|
|
211
|
+
): Array<{ name: string; props: Record<string, string> }> {
|
|
212
|
+
if (!components) return []
|
|
213
|
+
|
|
214
|
+
const extracted: Array<{ name: string; props: Record<string, string> }> = []
|
|
215
|
+
|
|
216
|
+
for (const [name, def] of Object.entries(components)) {
|
|
217
|
+
const matches = Array.from(content.matchAll(def.pattern))
|
|
218
|
+
matches.forEach(() => {
|
|
219
|
+
extracted.push({
|
|
220
|
+
name,
|
|
221
|
+
props: { ...def.props },
|
|
222
|
+
})
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return extracted
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Create a headless markdown renderer.
|
|
231
|
+
*
|
|
232
|
+
* Uses the headless pattern — returns an API object with rendered HTML,
|
|
233
|
+
* extracted component slots, and ARIA props for the container.
|
|
234
|
+
*/
|
|
235
|
+
export function createMarkdownRenderer(props: MarkdownRendererProps): MarkdownRendererAPI {
|
|
236
|
+
const { content, components, linkResolver } = props
|
|
237
|
+
|
|
238
|
+
const html = parseMarkdown(content, linkResolver)
|
|
239
|
+
const extractedComponents = extractComponents(content, components)
|
|
240
|
+
|
|
241
|
+
const ariaProps: Partial<AccessibilityProps> = {
|
|
242
|
+
role: 'document',
|
|
243
|
+
'aria-label': 'Rendered markdown content',
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
html,
|
|
248
|
+
components: extractedComponents,
|
|
249
|
+
ariaProps,
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createMobileNav,
|
|
3
|
+
type MobileNavProps,
|
|
4
|
+
type MobileNavState,
|
|
5
|
+
type MobileNavAPI,
|
|
6
|
+
} from './mobile-nav.js'
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
mobileNavVariants,
|
|
10
|
+
mobileNavContentVariants,
|
|
11
|
+
mobileNavTriggerVariants,
|
|
12
|
+
mobileNavLinkVariants,
|
|
13
|
+
mobileNavTokens,
|
|
14
|
+
} from './mobile-nav.styles.js'
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
import type { TokenContract } from '../shared/index.ts'
|
|
3
|
+
|
|
4
|
+
export const mobileNavTokens: TokenContract = {
|
|
5
|
+
name: 'mobile-nav',
|
|
6
|
+
tokens: {
|
|
7
|
+
bg: { variable: '--rfr-mobile-nav-bg', fallback: 'hsl(var(--background))' },
|
|
8
|
+
fg: { variable: '--rfr-mobile-nav-fg', fallback: 'hsl(var(--foreground))' },
|
|
9
|
+
border: { variable: '--rfr-mobile-nav-border', fallback: 'hsl(var(--border))' },
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const mobileNavVariants = cva({
|
|
14
|
+
base: 'relative',
|
|
15
|
+
variants: {},
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const mobileNavContentVariants = cva({
|
|
19
|
+
base: 'w-full overflow-hidden border-b bg-background transition-all duration-200',
|
|
20
|
+
variants: {
|
|
21
|
+
state: {
|
|
22
|
+
open: 'max-h-screen opacity-100 animate-in slide-in-from-top',
|
|
23
|
+
closed: 'max-h-0 opacity-0 pointer-events-none animate-out slide-out-to-top',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
state: 'closed',
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export const mobileNavTriggerVariants = cva({
|
|
32
|
+
base: 'inline-flex items-center justify-center rounded-md p-2 transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
|
|
33
|
+
variants: {},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export const mobileNavLinkVariants = cva({
|
|
37
|
+
base: 'block w-full px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2',
|
|
38
|
+
variants: {},
|
|
39
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { AccessibilityProps, KeyboardHandlerMap } from '../shared/index.ts'
|
|
2
|
+
import { Keys, generateId } from '../shared/index.ts'
|
|
3
|
+
|
|
4
|
+
export interface MobileNavProps {
|
|
5
|
+
open?: boolean
|
|
6
|
+
onOpenChange?: (open: boolean) => void
|
|
7
|
+
id?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface MobileNavState {
|
|
11
|
+
open: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface MobileNavAPI {
|
|
15
|
+
/** Current state */
|
|
16
|
+
state: MobileNavState
|
|
17
|
+
/** ARIA and interaction props for the trigger button */
|
|
18
|
+
triggerProps: Partial<AccessibilityProps> & Record<string, string | boolean | undefined>
|
|
19
|
+
/** ARIA and data attributes for the content panel */
|
|
20
|
+
contentProps: Partial<AccessibilityProps> & Record<string, string | boolean | undefined>
|
|
21
|
+
/** Toggle open state */
|
|
22
|
+
toggle: () => void
|
|
23
|
+
/** Open the nav */
|
|
24
|
+
open: () => void
|
|
25
|
+
/** Close the nav */
|
|
26
|
+
close: () => void
|
|
27
|
+
/** Keyboard handlers (Escape closes) */
|
|
28
|
+
keyboardHandlers: KeyboardHandlerMap
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createMobileNav(props: MobileNavProps = {}): MobileNavAPI {
|
|
32
|
+
const isOpen = props.open ?? false
|
|
33
|
+
const id = props.id ?? generateId('rfr-mobile-nav')
|
|
34
|
+
const contentId = `${id}-content`
|
|
35
|
+
|
|
36
|
+
const state: MobileNavState = {
|
|
37
|
+
open: isOpen,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const triggerProps: MobileNavAPI['triggerProps'] = {
|
|
41
|
+
'aria-expanded': isOpen,
|
|
42
|
+
'aria-controls': contentId,
|
|
43
|
+
'aria-label': 'Toggle menu',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const contentProps: MobileNavAPI['contentProps'] = {
|
|
47
|
+
id: contentId,
|
|
48
|
+
role: 'menu',
|
|
49
|
+
'data-state': isOpen ? 'open' : 'closed',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function setOpen(value: boolean) {
|
|
53
|
+
state.open = value
|
|
54
|
+
props.onOpenChange?.(value)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const toggle = () => setOpen(!state.open)
|
|
58
|
+
const open = () => setOpen(true)
|
|
59
|
+
const close = () => setOpen(false)
|
|
60
|
+
|
|
61
|
+
const keyboardHandlers: KeyboardHandlerMap = {
|
|
62
|
+
[Keys.Escape]: () => close(),
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
state,
|
|
67
|
+
triggerProps,
|
|
68
|
+
contentProps,
|
|
69
|
+
toggle,
|
|
70
|
+
open,
|
|
71
|
+
close,
|
|
72
|
+
keyboardHandlers,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
export const navbarVariants = cva({
|
|
4
|
+
base: 'sticky top-0 z-40 w-full border-b',
|
|
5
|
+
variants: {
|
|
6
|
+
variant: {
|
|
7
|
+
solid: 'bg-background',
|
|
8
|
+
blur: 'bg-background/80 backdrop-blur-md',
|
|
9
|
+
gradient: 'bg-gradient-to-b from-background to-background/80 backdrop-blur-sm',
|
|
10
|
+
transparent: 'bg-transparent border-transparent',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
defaultVariants: {
|
|
14
|
+
variant: 'blur',
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const navLinkVariants = cva({
|
|
19
|
+
base: 'text-sm font-medium transition-colors hover:text-foreground',
|
|
20
|
+
variants: {
|
|
21
|
+
active: {
|
|
22
|
+
true: 'text-foreground',
|
|
23
|
+
false: 'text-muted-foreground',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
active: 'false',
|
|
28
|
+
},
|
|
29
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface NavLink {
|
|
2
|
+
label: string
|
|
3
|
+
href: string
|
|
4
|
+
icon?: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type NavbarVariant = 'solid' | 'blur' | 'gradient' | 'transparent'
|
|
8
|
+
|
|
9
|
+
export interface NavbarProps {
|
|
10
|
+
links?: NavLink[]
|
|
11
|
+
currentPath?: string
|
|
12
|
+
variant?: NavbarVariant
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface NavbarAPI {
|
|
16
|
+
ariaProps: Record<string, string>
|
|
17
|
+
isActive: (href: string) => boolean
|
|
18
|
+
linkAriaProps: (href: string) => Record<string, string>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createNavbar(props: NavbarProps = {}): NavbarAPI {
|
|
22
|
+
const { currentPath = '/' } = props
|
|
23
|
+
|
|
24
|
+
function isActive(href: string): boolean {
|
|
25
|
+
if (href === '/') return currentPath === '/'
|
|
26
|
+
return currentPath.startsWith(href)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function linkAriaProps(href: string): Record<string, string> {
|
|
30
|
+
const active = isActive(href)
|
|
31
|
+
return active ? { 'aria-current': 'page' } : {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
ariaProps: { role: 'navigation', 'aria-label': 'Main navigation' },
|
|
36
|
+
isActive,
|
|
37
|
+
linkAriaProps,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createOtpInput,
|
|
3
|
+
type OtpInputProps,
|
|
4
|
+
type OtpInputAPI,
|
|
5
|
+
type OtpSlotProps,
|
|
6
|
+
type OtpInputType,
|
|
7
|
+
} from './otp-input.js'
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
otpInputContainerVariants,
|
|
11
|
+
otpInputSlotVariants,
|
|
12
|
+
otpInputTokens,
|
|
13
|
+
} from './otp-input.styles.js'
|