@heyramzi/ui 0.0.0-stage → 1.53.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/COMPONENTS.md +133 -0
- package/README.md +136 -2
- package/package.json +217 -3
- package/scripts/lib/sync-core.mjs +113 -0
- package/scripts/relative-imports.mjs +39 -0
- package/scripts/sf-symbol.mjs +93 -0
- package/scripts/sync-shadcn.mjs +354 -0
- package/scripts/sync-transitions.mjs +146 -0
- package/scripts/validate-apple-symbol-exports.mjs +45 -0
- package/src/agents/agent-avatar.svelte +592 -0
- package/src/agents/agent-cast.svelte +197 -0
- package/src/agents/demo.svelte +125 -0
- package/src/agents/helpers.ts +58 -0
- package/src/agents/index.ts +11 -0
- package/src/agents/page-state.ts +70 -0
- package/src/agents/types.ts +74 -0
- package/src/agents/vendor/LICENSE +21 -0
- package/src/agents/vendor/color.ts +155 -0
- package/src/agents/vendor/draw.ts +840 -0
- package/src/agents/vendor/engine.ts +845 -0
- package/src/agents/vendor/mesh.ts +838 -0
- package/src/agents/vendor/plastic.ts +4958 -0
- package/src/agents/vendor/presets.ts +103 -0
- package/src/agents/vendor/shapes.ts +41 -0
- package/src/agents/vendor/ticker.ts +67 -0
- package/src/agents/vendor/types.ts +261 -0
- package/src/agents/vendor/wear.ts +1699 -0
- package/src/ai/ai-button/AiButton.svelte +86 -0
- package/src/ai/ai-button/index.ts +4 -0
- package/src/ai/confirmation/Confirmation.svelte +54 -0
- package/src/ai/confirmation/ConfirmationAccepted.svelte +23 -0
- package/src/ai/confirmation/ConfirmationAction.svelte +53 -0
- package/src/ai/confirmation/ConfirmationActions.svelte +24 -0
- package/src/ai/confirmation/ConfirmationFailed.svelte +23 -0
- package/src/ai/confirmation/ConfirmationRejected.svelte +23 -0
- package/src/ai/confirmation/ConfirmationRequest.svelte +25 -0
- package/src/ai/confirmation/ConfirmationTitle.svelte +25 -0
- package/src/ai/confirmation/confirmation-context.svelte.ts +22 -0
- package/src/ai/confirmation/index.ts +50 -0
- package/src/ai/generation-progress/GenerationProgress.svelte +354 -0
- package/src/ai/generation-progress/index.ts +7 -0
- package/src/ai/index.ts +111 -0
- package/src/ai/loading-state/PixelLoader.svelte +138 -0
- package/src/ai/loading-state/index.ts +5 -0
- package/src/ai/prompt-input/PromptInput.svelte +56 -0
- package/src/ai/prompt-input/PromptInputBody.svelte +19 -0
- package/src/ai/prompt-input/PromptInputSubmit.svelte +79 -0
- package/src/ai/prompt-input/PromptInputTextarea.svelte +74 -0
- package/src/ai/prompt-input/PromptInputToolbar.svelte +19 -0
- package/src/ai/prompt-input/index.ts +34 -0
- package/src/ai/reasoning/Reasoning.svelte +50 -0
- package/src/ai/reasoning/ReasoningContent.svelte +27 -0
- package/src/ai/reasoning/ReasoningTrigger.svelte +48 -0
- package/src/ai/reasoning/index.ts +22 -0
- package/src/ai/reasoning/reasoning-context.svelte.ts +20 -0
- package/src/ai/response/Response.css +32 -0
- package/src/ai/response/Response.svelte +130 -0
- package/src/ai/response/index.ts +5 -0
- package/src/ai/suggestion/Suggestion.svelte +43 -0
- package/src/ai/suggestion/Suggestions.svelte +25 -0
- package/src/ai/suggestion/index.ts +14 -0
- package/src/ai/thinking-dots/ThinkingDots.svelte +42 -0
- package/src/ai/thinking-dots/index.ts +5 -0
- package/src/ai/typing-dots/TypingDots.css +39 -0
- package/src/ai/typing-dots/TypingDots.svelte +27 -0
- package/src/ai/typing-dots/index.ts +10 -0
- package/src/ai/voice-waveform/VoiceWaveform.css +40 -0
- package/src/ai/voice-waveform/VoiceWaveform.svelte +46 -0
- package/src/ai/voice-waveform/index.ts +5 -0
- package/src/ambient.d.ts +4 -0
- package/src/animations/README.md +84 -0
- package/src/animations/avatar-group-hover.css +47 -0
- package/src/animations/card-tilt.css +74 -0
- package/src/animations/digit-pop-in.css +78 -0
- package/src/animations/dropdown.css +121 -0
- package/src/animations/index.css +23 -0
- package/src/animations/input-clear-dissolve.css +86 -0
- package/src/animations/input-shake.css +114 -0
- package/src/animations/learn-more-hover.css +48 -0
- package/src/animations/like-button.css +93 -0
- package/src/animations/modal.css +75 -0
- package/src/animations/page-side-by-side.css +63 -0
- package/src/animations/plus-menu-morph.css +97 -0
- package/src/animations/resize.css +20 -0
- package/src/animations/shimmer-text.css +72 -0
- package/src/animations/spinning-counter.css +52 -0
- package/src/animations/success-check.css +120 -0
- package/src/animations/text-swap.css +40 -0
- package/src/animations/text-swap.ts +40 -0
- package/src/animations/thinking-states.css +65 -0
- package/src/animations/upstream-coverage.test.ts +65 -0
- package/src/components/copy/clipboard.test.ts +206 -0
- package/src/components/copy/clipboard.ts +49 -0
- package/src/components/copy/copied.svelte.ts +39 -0
- package/src/components/copy/copy-button.svelte +175 -0
- package/src/components/copy/copy-button.test.ts +110 -0
- package/src/components/copy/index.ts +3 -0
- package/src/components/data-table/data-table.test.ts +134 -0
- package/src/components/data-table/data-table.ts +77 -0
- package/src/components/data-table/index.ts +20 -0
- package/src/components/data-table/infinite-scroll.svelte +69 -0
- package/src/components/data-table/pagination.svelte +62 -0
- package/src/components/data-table/row-arrow.svelte +28 -0
- package/src/components/data-table/row-context-menu.svelte +106 -0
- package/src/components/data-table/row-context-menu.test.ts +18 -0
- package/src/components/data-table/sortable-head.svelte +56 -0
- package/src/components/data-table/table-skeleton.svelte +83 -0
- package/src/components/hold-to-confirm/hold-to-confirm.svelte +232 -0
- package/src/components/hold-to-confirm/index.ts +1 -0
- package/src/components/icon/icon.svelte +23 -0
- package/src/components/icon/index.ts +1 -0
- package/src/components/language-flag/index.ts +1 -0
- package/src/components/language-flag/language-flag.svelte +60 -0
- package/src/components/list-page/detail-empty.svelte +50 -0
- package/src/components/list-page/detail-header.svelte +107 -0
- package/src/components/list-page/detail-panel.svelte +90 -0
- package/src/components/list-page/detail-row.svelte +36 -0
- package/src/components/list-page/detail-stack.svelte +33 -0
- package/src/components/list-page/empty-state.svelte +91 -0
- package/src/components/list-page/filter-item.svelte +49 -0
- package/src/components/list-page/filter-popover.svelte +88 -0
- package/src/components/list-page/filter-section.ts +12 -0
- package/src/components/list-page/grouped-list.svelte +116 -0
- package/src/components/list-page/index.ts +28 -0
- package/src/components/list-page/list-page-shell.svelte +195 -0
- package/src/components/list-page/list-table.ts +19 -0
- package/src/components/list-page/master-detail.svelte +206 -0
- package/src/components/list-page/master-detail.test.ts +37 -0
- package/src/components/list-page/notice-bar.svelte +68 -0
- package/src/components/list-page/prev-next.svelte +104 -0
- package/src/components/list-page/queue-rail.ts +25 -0
- package/src/components/list-page/rail-body.svelte +29 -0
- package/src/components/list-page/rail-empty.svelte +51 -0
- package/src/components/list-page/rail-head.svelte +49 -0
- package/src/components/list-page/rail-row.svelte +163 -0
- package/src/components/list-page/section-host.ts +24 -0
- package/src/components/list-page/section-label.svelte +39 -0
- package/src/components/list-page/step.test.ts +21 -0
- package/src/components/list-page/step.ts +10 -0
- package/src/components/list-page/toolbar-search.svelte +63 -0
- package/src/components/list-page/toolbar-trigger.ts +23 -0
- package/src/components/list-page/types.ts +1 -0
- package/src/components/locale-switch/index.ts +1 -0
- package/src/components/locale-switch/locale-switch.svelte +59 -0
- package/src/components/mark/index.ts +1 -0
- package/src/components/mark/mark.svelte +70 -0
- package/src/components/open-in/index.ts +6 -0
- package/src/components/open-in/open-in.svelte +106 -0
- package/src/components/picker/index.ts +4 -0
- package/src/components/picker/mark-picker.svelte +84 -0
- package/src/components/picker/picker.svelte +143 -0
- package/src/components/picker/trigger.ts +42 -0
- package/src/components/picker/types.ts +26 -0
- package/src/components/segmented/animated-tabs.svelte +175 -0
- package/src/components/segmented/collapse.test.ts +49 -0
- package/src/components/segmented/collapse.ts +39 -0
- package/src/components/segmented/index.ts +41 -0
- package/src/components/segmented/section-runs.svelte +79 -0
- package/src/components/segmented/segment.svelte +106 -0
- package/src/components/segmented/segmented-filter.svelte +125 -0
- package/src/components/segmented/segmented-nav.svelte +82 -0
- package/src/components/segmented/segmented-overflow.svelte +85 -0
- package/src/components/segmented/segmented-track.svelte +181 -0
- package/src/components/segmented/segmented.test.ts +20 -0
- package/src/components/segmented/segmented.ts +80 -0
- package/src/components/star-rating-input/index.ts +9 -0
- package/src/components/star-rating-input/star-rating-input.svelte +256 -0
- package/src/components/svg/index.ts +1 -0
- package/src/components/svg/svg.svelte +9 -0
- package/src/design/index.css +270 -0
- package/src/design/polish.css +159 -0
- package/src/effects/animated-beam/AnimatedBeam.svelte +132 -0
- package/src/effects/animated-beam/diagrams/BeamSvg.svelte +91 -0
- package/src/effects/animated-beam/index.ts +2 -0
- package/src/effects/animated-beam/path-calculator.svelte.ts +70 -0
- package/src/effects/animated-beam/types.ts +23 -0
- package/src/effects/border-beam/BorderBeam.svelte +223 -0
- package/src/effects/border-beam/css.test.ts +46 -0
- package/src/effects/border-beam/css.ts +574 -0
- package/src/effects/border-beam/presets.ts +699 -0
- package/src/effects/border-beam/register-properties.test.ts +74 -0
- package/src/effects/border-beam/register-properties.ts +73 -0
- package/src/effects/border-beam/types.ts +38 -0
- package/src/effects/image-comparison/ImageComparison.css +100 -0
- package/src/effects/image-comparison/ImageComparison.svelte +209 -0
- package/src/effects/image-comparison/compare.test.ts +17 -0
- package/src/effects/image-comparison/compare.ts +5 -0
- package/src/effects/image-reveal/ImageReveal.css +10 -0
- package/src/effects/image-reveal/ImageReveal.svelte +357 -0
- package/src/effects/image-reveal/index.ts +2 -0
- package/src/effects/image-reveal/types.ts +35 -0
- package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +338 -0
- package/src/effects/magic-card/MagicCard.css +47 -0
- package/src/effects/magic-card/MagicCard.svelte +57 -0
- package/src/effects/magnetic/magnetic.ts +81 -0
- package/src/effects/marquee/Marquee.css +78 -0
- package/src/effects/marquee/Marquee.svelte +160 -0
- package/src/effects/progressive-blur/ProgressiveBlur.css +9 -0
- package/src/effects/progressive-blur/ProgressiveBlur.svelte +63 -0
- package/src/effects/reveal/AnimatedGroup.svelte +101 -0
- package/src/effects/reveal/InView.svelte +77 -0
- package/src/effects/reveal/TextEffect.svelte +142 -0
- package/src/effects/reveal/index.ts +11 -0
- package/src/effects/reveal/observe.test.ts +60 -0
- package/src/effects/reveal/observe.ts +65 -0
- package/src/effects/reveal/reveal.css +141 -0
- package/src/effects/reveal/types.ts +32 -0
- package/src/effects/shader-gradient/ShaderGradient.svelte +227 -0
- package/src/effects/shader-gradient/color.ts +44 -0
- package/src/effects/shader-gradient/index.ts +5 -0
- package/src/effects/shader-gradient/renderer.ts +456 -0
- package/src/effects/shader-gradient/shaders.ts +324 -0
- package/src/effects/shader-gradient/types.ts +68 -0
- package/src/effects/spotlight/Spotlight.css +14 -0
- package/src/effects/spotlight/Spotlight.svelte +61 -0
- package/src/effects/theme-toggle/ThemeToggle.css +214 -0
- package/src/effects/theme-toggle/ThemeToggle.svelte +126 -0
- package/src/gallery.test.ts +52 -0
- package/src/icons/TradeIcon.svelte +16 -0
- package/src/icons/apple-symbol-exports.ts +135 -0
- package/src/icons/sf-symbols.ts +527 -0
- package/src/icons/trades.ts +89 -0
- package/src/index.ts +53 -0
- package/src/lib/arrive.ts +26 -0
- package/src/lib/icon-types.ts +17 -0
- package/src/lib/layout.ts +8 -0
- package/src/lib/panel.ts +21 -0
- package/src/lib/sliding-pill.ts +68 -0
- package/src/lib/theme-transition.ts +79 -0
- package/src/lib/tone.ts +48 -0
- package/src/shadcn/accordion/accordion-content.svelte +22 -0
- package/src/shadcn/accordion/accordion-item.svelte +17 -0
- package/src/shadcn/accordion/accordion-trigger.svelte +44 -0
- package/src/shadcn/accordion/accordion.svelte +19 -0
- package/src/shadcn/accordion/index.ts +16 -0
- package/src/shadcn/alert/alert-description.svelte +23 -0
- package/src/shadcn/alert/alert-title.svelte +20 -0
- package/src/shadcn/alert/alert.svelte +46 -0
- package/src/shadcn/alert/index.ts +14 -0
- package/src/shadcn/alert-dialog/alert-dialog-action.svelte +18 -0
- package/src/shadcn/alert-dialog/alert-dialog-cancel.svelte +18 -0
- package/src/shadcn/alert-dialog/alert-dialog-content.svelte +29 -0
- package/src/shadcn/alert-dialog/alert-dialog-description.svelte +17 -0
- package/src/shadcn/alert-dialog/alert-dialog-footer.svelte +20 -0
- package/src/shadcn/alert-dialog/alert-dialog-header.svelte +20 -0
- package/src/shadcn/alert-dialog/alert-dialog-overlay.svelte +17 -0
- package/src/shadcn/alert-dialog/alert-dialog-portal.svelte +7 -0
- package/src/shadcn/alert-dialog/alert-dialog-title.svelte +17 -0
- package/src/shadcn/alert-dialog/alert-dialog-trigger.svelte +7 -0
- package/src/shadcn/alert-dialog/alert-dialog.svelte +7 -0
- package/src/shadcn/alert-dialog/index.ts +37 -0
- package/src/shadcn/attachment/attachment-action.svelte +23 -0
- package/src/shadcn/attachment/attachment-actions.svelte +23 -0
- package/src/shadcn/attachment/attachment-content.svelte +23 -0
- package/src/shadcn/attachment/attachment-description.svelte +23 -0
- package/src/shadcn/attachment/attachment-group.svelte +23 -0
- package/src/shadcn/attachment/attachment-media.svelte +44 -0
- package/src/shadcn/attachment/attachment-title.svelte +23 -0
- package/src/shadcn/attachment/attachment-trigger.svelte +29 -0
- package/src/shadcn/attachment/attachment.svelte +55 -0
- package/src/shadcn/attachment/index.ts +40 -0
- package/src/shadcn/avatar/avatar-badge.svelte +26 -0
- package/src/shadcn/avatar/avatar-fallback.svelte +20 -0
- package/src/shadcn/avatar/avatar-group-count.svelte +23 -0
- package/src/shadcn/avatar/avatar-group.svelte +23 -0
- package/src/shadcn/avatar/avatar-image.svelte +17 -0
- package/src/shadcn/avatar/avatar.svelte +26 -0
- package/src/shadcn/avatar/index.ts +22 -0
- package/src/shadcn/badge/badge.svelte +54 -0
- package/src/shadcn/badge/index.ts +2 -0
- package/src/shadcn/breadcrumb/breadcrumb-ellipsis.svelte +23 -0
- package/src/shadcn/breadcrumb/breadcrumb-item.svelte +20 -0
- package/src/shadcn/breadcrumb/breadcrumb-link.svelte +31 -0
- package/src/shadcn/breadcrumb/breadcrumb-list.svelte +23 -0
- package/src/shadcn/breadcrumb/breadcrumb-page.svelte +23 -0
- package/src/shadcn/breadcrumb/breadcrumb-separator.svelte +27 -0
- package/src/shadcn/breadcrumb/breadcrumb.svelte +21 -0
- package/src/shadcn/breadcrumb/index.ts +25 -0
- package/src/shadcn/bubble/bubble-content.svelte +31 -0
- package/src/shadcn/bubble/bubble-group.svelte +20 -0
- package/src/shadcn/bubble/bubble-reactions.svelte +52 -0
- package/src/shadcn/bubble/bubble.svelte +58 -0
- package/src/shadcn/bubble/index.ts +25 -0
- package/src/shadcn/button/button.svelte +121 -0
- package/src/shadcn/button/index.ts +17 -0
- package/src/shadcn/calendar/calendar-caption.svelte +76 -0
- package/src/shadcn/calendar/calendar-cell.svelte +19 -0
- package/src/shadcn/calendar/calendar-day.svelte +35 -0
- package/src/shadcn/calendar/calendar-grid-body.svelte +12 -0
- package/src/shadcn/calendar/calendar-grid-head.svelte +12 -0
- package/src/shadcn/calendar/calendar-grid-row.svelte +12 -0
- package/src/shadcn/calendar/calendar-grid.svelte +16 -0
- package/src/shadcn/calendar/calendar-head-cell.svelte +19 -0
- package/src/shadcn/calendar/calendar-header.svelte +19 -0
- package/src/shadcn/calendar/calendar-heading.svelte +16 -0
- package/src/shadcn/calendar/calendar-month-select.svelte +44 -0
- package/src/shadcn/calendar/calendar-month.svelte +15 -0
- package/src/shadcn/calendar/calendar-months.svelte +19 -0
- package/src/shadcn/calendar/calendar-nav.svelte +19 -0
- package/src/shadcn/calendar/calendar-next-button.svelte +31 -0
- package/src/shadcn/calendar/calendar-prev-button.svelte +31 -0
- package/src/shadcn/calendar/calendar-year-select.svelte +43 -0
- package/src/shadcn/calendar/calendar.svelte +124 -0
- package/src/shadcn/calendar/index.ts +40 -0
- package/src/shadcn/card/card-action.svelte +23 -0
- package/src/shadcn/card/card-content.svelte +20 -0
- package/src/shadcn/card/card-description.svelte +20 -0
- package/src/shadcn/card/card-footer.svelte +23 -0
- package/src/shadcn/card/card-header.svelte +23 -0
- package/src/shadcn/card/card-title.svelte +29 -0
- package/src/shadcn/card/card.svelte +25 -0
- package/src/shadcn/card/index.ts +25 -0
- package/src/shadcn/checkbox/checkbox.svelte +38 -0
- package/src/shadcn/checkbox/index.ts +6 -0
- package/src/shadcn/code-block/code-block-actions.svelte +23 -0
- package/src/shadcn/code-block/code-block-content.svelte +55 -0
- package/src/shadcn/code-block/code-block-copy-button.svelte +10 -0
- package/src/shadcn/code-block/code-block-footer.svelte +20 -0
- package/src/shadcn/code-block/code-block-header.svelte +23 -0
- package/src/shadcn/code-block/code-block-line.svelte +71 -0
- package/src/shadcn/code-block/code-block-title.svelte +23 -0
- package/src/shadcn/code-block/code-block.svelte +47 -0
- package/src/shadcn/code-block/index.ts +42 -0
- package/src/shadcn/collapsible/collapsible-content.svelte +7 -0
- package/src/shadcn/collapsible/collapsible-trigger.svelte +7 -0
- package/src/shadcn/collapsible/collapsible.svelte +11 -0
- package/src/shadcn/collapsible/index.ts +13 -0
- package/src/shadcn/command/command-dialog.svelte +66 -0
- package/src/shadcn/command/command-empty.svelte +17 -0
- package/src/shadcn/command/command-group.svelte +40 -0
- package/src/shadcn/command/command-input.svelte +64 -0
- package/src/shadcn/command/command-item.svelte +37 -0
- package/src/shadcn/command/command-link-item.svelte +20 -0
- package/src/shadcn/command/command-list.svelte +20 -0
- package/src/shadcn/command/command-loading.svelte +7 -0
- package/src/shadcn/command/command-separator.svelte +17 -0
- package/src/shadcn/command/command-shortcut.svelte +23 -0
- package/src/shadcn/command/command.svelte +28 -0
- package/src/shadcn/command/index.ts +40 -0
- package/src/shadcn/context-menu/context-menu-checkbox-item.svelte +41 -0
- package/src/shadcn/context-menu/context-menu-content.svelte +28 -0
- package/src/shadcn/context-menu/context-menu-group-heading.svelte +21 -0
- package/src/shadcn/context-menu/context-menu-group.svelte +7 -0
- package/src/shadcn/context-menu/context-menu-item.svelte +27 -0
- package/src/shadcn/context-menu/context-menu-label.svelte +27 -0
- package/src/shadcn/context-menu/context-menu-portal.svelte +7 -0
- package/src/shadcn/context-menu/context-menu-radio-group.svelte +16 -0
- package/src/shadcn/context-menu/context-menu-radio-item.svelte +35 -0
- package/src/shadcn/context-menu/context-menu-separator.svelte +17 -0
- package/src/shadcn/context-menu/context-menu-shortcut.svelte +23 -0
- package/src/shadcn/context-menu/context-menu-sub-content.svelte +25 -0
- package/src/shadcn/context-menu/context-menu-sub-trigger.svelte +29 -0
- package/src/shadcn/context-menu/context-menu-sub.svelte +7 -0
- package/src/shadcn/context-menu/context-menu-trigger.svelte +17 -0
- package/src/shadcn/context-menu/context-menu.svelte +7 -0
- package/src/shadcn/context-menu/index.ts +52 -0
- package/src/shadcn/dialog/dialog-close.svelte +11 -0
- package/src/shadcn/dialog/dialog-content.svelte +57 -0
- package/src/shadcn/dialog/dialog-description.svelte +20 -0
- package/src/shadcn/dialog/dialog-footer.svelte +32 -0
- package/src/shadcn/dialog/dialog-header.svelte +20 -0
- package/src/shadcn/dialog/dialog-overlay.svelte +20 -0
- package/src/shadcn/dialog/dialog-portal.svelte +7 -0
- package/src/shadcn/dialog/dialog-title.svelte +17 -0
- package/src/shadcn/dialog/dialog-trigger.svelte +11 -0
- package/src/shadcn/dialog/dialog.svelte +7 -0
- package/src/shadcn/dialog/index.ts +34 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-checkbox-item.svelte +43 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-content.svelte +31 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-group.svelte +7 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-item.svelte +29 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-label.svelte +27 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-portal.svelte +7 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-radio-item.svelte +34 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-separator.svelte +17 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-shortcut.svelte +23 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-sub-content.svelte +20 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-sub.svelte +7 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
- package/src/shadcn/dropdown-menu/dropdown-menu.svelte +7 -0
- package/src/shadcn/dropdown-menu/index.ts +56 -0
- package/src/shadcn/dropdown-menu/menu-item.ts +43 -0
- package/src/shadcn/empty/empty-content.svelte +23 -0
- package/src/shadcn/empty/empty-description.svelte +23 -0
- package/src/shadcn/empty/empty-header.svelte +20 -0
- package/src/shadcn/empty/empty-media.svelte +41 -0
- package/src/shadcn/empty/empty-title.svelte +20 -0
- package/src/shadcn/empty/empty.svelte +23 -0
- package/src/shadcn/empty/index.ts +22 -0
- package/src/shadcn/field/field-content.svelte +20 -0
- package/src/shadcn/field/field-description.svelte +25 -0
- package/src/shadcn/field/field-error.svelte +58 -0
- package/src/shadcn/field/field-group.svelte +23 -0
- package/src/shadcn/field/field-label.svelte +26 -0
- package/src/shadcn/field/field-legend.svelte +29 -0
- package/src/shadcn/field/field-separator.svelte +35 -0
- package/src/shadcn/field/field-set.svelte +24 -0
- package/src/shadcn/field/field-title.svelte +23 -0
- package/src/shadcn/field/field.svelte +53 -0
- package/src/shadcn/field/index.ts +36 -0
- package/src/shadcn/field/simple-field.svelte +71 -0
- package/src/shadcn/field/simple-field.test.ts +47 -0
- package/src/shadcn/hooks/highlighter.ts +132 -0
- package/src/shadcn/hooks/is-mobile.svelte.ts +9 -0
- package/src/shadcn/hooks/use-highlighted-code.svelte.ts +98 -0
- package/src/shadcn/hover-card/hover-card-content.svelte +31 -0
- package/src/shadcn/hover-card/hover-card-portal.svelte +7 -0
- package/src/shadcn/hover-card/hover-card-trigger.svelte +7 -0
- package/src/shadcn/hover-card/hover-card.svelte +7 -0
- package/src/shadcn/hover-card/index.ts +15 -0
- package/src/shadcn/input/index.ts +9 -0
- package/src/shadcn/input/input.svelte +48 -0
- package/src/shadcn/input/secret-input.svelte +75 -0
- package/src/shadcn/input-otp/index.ts +15 -0
- package/src/shadcn/input-otp/input-otp-group.svelte +20 -0
- package/src/shadcn/input-otp/input-otp-separator.svelte +25 -0
- package/src/shadcn/input-otp/input-otp-slot.svelte +26 -0
- package/src/shadcn/input-otp/input-otp.svelte +22 -0
- package/src/shadcn/kbd/index.ts +10 -0
- package/src/shadcn/kbd/kbd-group.svelte +20 -0
- package/src/shadcn/kbd/kbd.svelte +23 -0
- package/src/shadcn/label/index.ts +7 -0
- package/src/shadcn/label/label.svelte +20 -0
- package/src/shadcn/marker/index.ts +15 -0
- package/src/shadcn/marker/marker-content.svelte +23 -0
- package/src/shadcn/marker/marker-icon.svelte +21 -0
- package/src/shadcn/marker/marker.svelte +52 -0
- package/src/shadcn/message/index.ts +22 -0
- package/src/shadcn/message/message-avatar.svelte +23 -0
- package/src/shadcn/message/message-content.svelte +23 -0
- package/src/shadcn/message/message-footer.svelte +23 -0
- package/src/shadcn/message/message-group.svelte +20 -0
- package/src/shadcn/message/message-header.svelte +23 -0
- package/src/shadcn/message/message.svelte +27 -0
- package/src/shadcn/message-scroller/index.ts +28 -0
- package/src/shadcn/message-scroller/message-scroller-button.svelte +52 -0
- package/src/shadcn/message-scroller/message-scroller-content.svelte +17 -0
- package/src/shadcn/message-scroller/message-scroller-item.svelte +22 -0
- package/src/shadcn/message-scroller/message-scroller-provider.svelte +7 -0
- package/src/shadcn/message-scroller/message-scroller-viewport.svelte +20 -0
- package/src/shadcn/message-scroller/message-scroller.svelte +20 -0
- package/src/shadcn/popover/index.ts +28 -0
- package/src/shadcn/popover/popover-close.svelte +7 -0
- package/src/shadcn/popover/popover-content.svelte +38 -0
- package/src/shadcn/popover/popover-description.svelte +20 -0
- package/src/shadcn/popover/popover-header.svelte +20 -0
- package/src/shadcn/popover/popover-portal.svelte +7 -0
- package/src/shadcn/popover/popover-title.svelte +15 -0
- package/src/shadcn/popover/popover-trigger.svelte +17 -0
- package/src/shadcn/popover/popover.svelte +7 -0
- package/src/shadcn/progress/index.ts +7 -0
- package/src/shadcn/progress/progress.svelte +30 -0
- package/src/shadcn/radio-group/index.ts +10 -0
- package/src/shadcn/radio-group/radio-group-item.svelte +32 -0
- package/src/shadcn/radio-group/radio-group.svelte +19 -0
- package/src/shadcn/scroll-area/index.ts +10 -0
- package/src/shadcn/scroll-area/scroll-area-scrollbar.svelte +30 -0
- package/src/shadcn/scroll-area/scroll-area.svelte +43 -0
- package/src/shadcn/select/index.ts +37 -0
- package/src/shadcn/select/select-content.svelte +45 -0
- package/src/shadcn/select/select-group-heading.svelte +21 -0
- package/src/shadcn/select/select-group.svelte +17 -0
- package/src/shadcn/select/select-item.svelte +38 -0
- package/src/shadcn/select/select-label.svelte +20 -0
- package/src/shadcn/select/select-portal.svelte +7 -0
- package/src/shadcn/select/select-scroll-down-button.svelte +23 -0
- package/src/shadcn/select/select-scroll-up-button.svelte +23 -0
- package/src/shadcn/select/select-separator.svelte +18 -0
- package/src/shadcn/select/select-trigger.svelte +33 -0
- package/src/shadcn/select/select.svelte +11 -0
- package/src/shadcn/separator/index.ts +7 -0
- package/src/shadcn/separator/separator.svelte +23 -0
- package/src/shadcn/sheet/index.ts +34 -0
- package/src/shadcn/sheet/sheet-close.svelte +7 -0
- package/src/shadcn/sheet/sheet-content.svelte +73 -0
- package/src/shadcn/sheet/sheet-description.svelte +17 -0
- package/src/shadcn/sheet/sheet-footer.svelte +20 -0
- package/src/shadcn/sheet/sheet-header.svelte +20 -0
- package/src/shadcn/sheet/sheet-overlay.svelte +20 -0
- package/src/shadcn/sheet/sheet-portal.svelte +7 -0
- package/src/shadcn/sheet/sheet-title.svelte +17 -0
- package/src/shadcn/sheet/sheet-trigger.svelte +7 -0
- package/src/shadcn/sheet/sheet.css +48 -0
- package/src/shadcn/sheet/sheet.svelte +7 -0
- package/src/shadcn/sidebar/constants.ts +6 -0
- package/src/shadcn/sidebar/context.svelte.ts +81 -0
- package/src/shadcn/sidebar/index.ts +75 -0
- package/src/shadcn/sidebar/sidebar-content.svelte +24 -0
- package/src/shadcn/sidebar/sidebar-footer.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-group-action.svelte +33 -0
- package/src/shadcn/sidebar/sidebar-group-content.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-group-label.svelte +33 -0
- package/src/shadcn/sidebar/sidebar-group.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-header.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-input.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-inset.svelte +23 -0
- package/src/shadcn/sidebar/sidebar-menu-action.svelte +37 -0
- package/src/shadcn/sidebar/sidebar-menu-badge.svelte +24 -0
- package/src/shadcn/sidebar/sidebar-menu-button.svelte +103 -0
- package/src/shadcn/sidebar/sidebar-menu-item.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-menu-skeleton.svelte +36 -0
- package/src/shadcn/sidebar/sidebar-menu-sub-button.svelte +39 -0
- package/src/shadcn/sidebar/sidebar-menu-sub-item.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-menu-sub.svelte +24 -0
- package/src/shadcn/sidebar/sidebar-menu.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-provider.svelte +62 -0
- package/src/shadcn/sidebar/sidebar-rail.svelte +36 -0
- package/src/shadcn/sidebar/sidebar-separator.svelte +19 -0
- package/src/shadcn/sidebar/sidebar-trigger.svelte +37 -0
- package/src/shadcn/sidebar/sidebar.svelte +109 -0
- package/src/shadcn/skeleton/index.ts +7 -0
- package/src/shadcn/skeleton/skeleton.svelte +17 -0
- package/src/shadcn/slider/index.ts +7 -0
- package/src/shadcn/slider/slider.svelte +52 -0
- package/src/shadcn/sonner/index.ts +1 -0
- package/src/shadcn/sonner/sonner.svelte +31 -0
- package/src/shadcn/spinner/index.ts +1 -0
- package/src/shadcn/spinner/spinner.svelte +26 -0
- package/src/shadcn/switch/index.ts +7 -0
- package/src/shadcn/switch/switch.svelte +31 -0
- package/src/shadcn/table/index.ts +28 -0
- package/src/shadcn/table/table-body.svelte +20 -0
- package/src/shadcn/table/table-caption.svelte +20 -0
- package/src/shadcn/table/table-cell.svelte +20 -0
- package/src/shadcn/table/table-footer.svelte +20 -0
- package/src/shadcn/table/table-head.svelte +23 -0
- package/src/shadcn/table/table-header.svelte +20 -0
- package/src/shadcn/table/table-row.svelte +23 -0
- package/src/shadcn/table/table.svelte +25 -0
- package/src/shadcn/tabs/index.ts +16 -0
- package/src/shadcn/tabs/tabs-content.svelte +17 -0
- package/src/shadcn/tabs/tabs-list.svelte +36 -0
- package/src/shadcn/tabs/tabs-trigger.svelte +20 -0
- package/src/shadcn/tabs/tabs.svelte +19 -0
- package/src/shadcn/textarea/index.ts +7 -0
- package/src/shadcn/textarea/textarea.svelte +23 -0
- package/src/shadcn/toggle/index.ts +13 -0
- package/src/shadcn/toggle/toggle.svelte +53 -0
- package/src/shadcn/toggle-group/index.ts +10 -0
- package/src/shadcn/toggle-group/toggle-group-item.svelte +35 -0
- package/src/shadcn/toggle-group/toggle-group.svelte +66 -0
- package/src/shadcn/tooltip/context.ts +16 -0
- package/src/shadcn/tooltip/index.ts +19 -0
- package/src/shadcn/tooltip/tooltip-content.svelte +38 -0
- package/src/shadcn/tooltip/tooltip-portal.svelte +7 -0
- package/src/shadcn/tooltip/tooltip-provider.svelte +16 -0
- package/src/shadcn/tooltip/tooltip-trigger.svelte +7 -0
- package/src/shadcn/tooltip/tooltip.svelte +19 -0
- package/src/shadcn/trace/index.ts +29 -0
- package/src/shadcn/trace/trace-collapsible-content.svelte +29 -0
- package/src/shadcn/trace/trace-collapsible-trigger.svelte +29 -0
- package/src/shadcn/trace/trace-collapsible.svelte +20 -0
- package/src/shadcn/trace/trace-description.svelte +20 -0
- package/src/shadcn/trace/trace-icon.svelte +24 -0
- package/src/shadcn/trace/trace-line.svelte +31 -0
- package/src/shadcn/trace/trace-title.svelte +20 -0
- package/src/shadcn/trace/trace.svelte +23 -0
- package/src/shadcn/utilities.css +549 -0
- package/src/test-setup.ts +34 -0
- package/src/tokens.css +993 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import type { Snippet } from "svelte";
|
|
6
|
+
import EmptyState from "./empty-state.svelte";
|
|
7
|
+
import { cn } from "@heyramzi/utils";
|
|
8
|
+
import { pageGutterClass } from "../../lib/layout.js";
|
|
9
|
+
import { getSectionHost } from "./section-host.js";
|
|
10
|
+
import SectionRuns from "../segmented/section-runs.svelte";
|
|
11
|
+
import type { PhosphorIcon, IconWeight } from "../../lib/icon-types.js";
|
|
12
|
+
|
|
13
|
+
// =========================
|
|
14
|
+
// TYPES
|
|
15
|
+
// =========================
|
|
16
|
+
type EmptyConfig = {
|
|
17
|
+
icon?: PhosphorIcon;
|
|
18
|
+
iconWeight?: IconWeight;
|
|
19
|
+
title: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
actionLabel?: string;
|
|
22
|
+
onAction?: () => void;
|
|
23
|
+
actionHref?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type Props = {
|
|
27
|
+
isEmpty?: boolean;
|
|
28
|
+
emptyState?: EmptyConfig;
|
|
29
|
+
hideToolbarWhenEmpty?: boolean;
|
|
30
|
+
maxWidth?: string | false;
|
|
31
|
+
fillHeight?: boolean;
|
|
32
|
+
containerClass?: string;
|
|
33
|
+
toolbarClass?: string;
|
|
34
|
+
tabs?: Snippet;
|
|
35
|
+
search?: Snippet;
|
|
36
|
+
filters?: Snippet;
|
|
37
|
+
rightActions?: Snippet;
|
|
38
|
+
summary?: Snippet;
|
|
39
|
+
content?: Snippet;
|
|
40
|
+
empty?: Snippet;
|
|
41
|
+
extra?: Snippet;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// =========================
|
|
45
|
+
// PROPS
|
|
46
|
+
// =========================
|
|
47
|
+
let {
|
|
48
|
+
isEmpty = false,
|
|
49
|
+
emptyState,
|
|
50
|
+
hideToolbarWhenEmpty = false,
|
|
51
|
+
maxWidth = "1600px",
|
|
52
|
+
fillHeight = false,
|
|
53
|
+
containerClass,
|
|
54
|
+
toolbarClass,
|
|
55
|
+
tabs,
|
|
56
|
+
search,
|
|
57
|
+
filters,
|
|
58
|
+
rightActions,
|
|
59
|
+
summary,
|
|
60
|
+
content,
|
|
61
|
+
empty,
|
|
62
|
+
extra,
|
|
63
|
+
}: Props = $props();
|
|
64
|
+
|
|
65
|
+
// =========================
|
|
66
|
+
// STATE
|
|
67
|
+
// =========================
|
|
68
|
+
// Null off an app shell, where the toolbar just has no section strip to draw.
|
|
69
|
+
const section = getSectionHost();
|
|
70
|
+
|
|
71
|
+
// =========================
|
|
72
|
+
// DERIVED
|
|
73
|
+
// =========================
|
|
74
|
+
let hideChrome = $derived(isEmpty && hideToolbarWhenEmpty);
|
|
75
|
+
// WHY the section strip counts: on a page whose only chrome is the section
|
|
76
|
+
// nav, this row is what draws it, and skipping the row would drop the tabs.
|
|
77
|
+
let hasToolbar = $derived(
|
|
78
|
+
Boolean(search || filters || tabs || rightActions || section?.strip) && !hideChrome,
|
|
79
|
+
);
|
|
80
|
+
// Whether anything holds the left of the row. With nothing there, the tabs
|
|
81
|
+
// take that edge instead of floating mid-row beside the actions.
|
|
82
|
+
let hasLeading = $derived(Boolean(search || filters || section?.strip));
|
|
83
|
+
|
|
84
|
+
// =========================
|
|
85
|
+
// EFFECTS
|
|
86
|
+
// =========================
|
|
87
|
+
// Claiming stops the app shell drawing its own strip above this page.
|
|
88
|
+
// Unmount releases it, so shell-less pages get the standalone row back.
|
|
89
|
+
$effect(() => {
|
|
90
|
+
if (!hasToolbar) return;
|
|
91
|
+
return section?.claim();
|
|
92
|
+
});
|
|
93
|
+
// The shared gutter from `lib/layout.ts`. A typed one put the search field
|
|
94
|
+
// 8px left of the section tabs above it.
|
|
95
|
+
let outerClass = $derived(
|
|
96
|
+
cn(
|
|
97
|
+
`flex flex-col gap-4 py-4 ${pageGutterClass}`,
|
|
98
|
+
fillHeight && "min-h-0 flex-1",
|
|
99
|
+
!fillHeight && "flex-1",
|
|
100
|
+
containerClass,
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
// flex-1 lets a snippet pin a footer with mt-auto. min-w-0 keeps wide
|
|
104
|
+
// tables and chip rows from causing horizontal scroll.
|
|
105
|
+
let innerClass = $derived(
|
|
106
|
+
maxWidth === false
|
|
107
|
+
? cn("flex min-w-0 flex-1 flex-col gap-4", fillHeight && "min-h-0")
|
|
108
|
+
: cn(
|
|
109
|
+
"mx-auto flex w-full min-w-0 flex-1 flex-col gap-4",
|
|
110
|
+
fillHeight && "min-h-0",
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
let innerStyle = $derived(maxWidth ? `max-width: ${maxWidth}` : undefined);
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<!-- =========================== -->
|
|
117
|
+
<!-- MARKUP -->
|
|
118
|
+
<!-- =========================== -->
|
|
119
|
+
<div class={outerClass}>
|
|
120
|
+
<div class={innerClass} style={innerStyle}>
|
|
121
|
+
{#if hasToolbar}
|
|
122
|
+
<!-- One row on every list page: search and filters left, then tabs,
|
|
123
|
+
view toggle and primary action right. -->
|
|
124
|
+
<!-- Without room, `order` keeps the actions beside search and drops
|
|
125
|
+
filters to a scrolling line below. flex-wrap stays on at every width. -->
|
|
126
|
+
<!-- A container query, not a breakpoint: an 834px iPad read as desktop
|
|
127
|
+
and clipped a 733px toolbar into a 538px column. -->
|
|
128
|
+
<!-- Sticky, because the section tabs live here. `-mx-4 px-4` covers the
|
|
129
|
+
gutter, `-mt-4 py-4` pulls the top padding inside the sticky box. -->
|
|
130
|
+
<div
|
|
131
|
+
class="@container/toolbar sticky top-0 z-20 -mx-4 -mt-4 bg-background/95 px-4 py-4 backdrop-blur supports-[backdrop-filter]:bg-background/70"
|
|
132
|
+
>
|
|
133
|
+
<div class={cn("flex flex-wrap items-center gap-2", toolbarClass)}>
|
|
134
|
+
<!-- The section tabs lead: which page comes before which slice of it.
|
|
135
|
+
One row replaces two rows that held four controls. -->
|
|
136
|
+
{#if section?.strip}
|
|
137
|
+
<SectionRuns
|
|
138
|
+
strip={section.strip}
|
|
139
|
+
class="order-first w-full max-w-full @3xl/toolbar:w-auto"
|
|
140
|
+
/>
|
|
141
|
+
{/if}
|
|
142
|
+
{#if search}
|
|
143
|
+
<div class="order-1 flex min-w-0 flex-1 items-center @3xl/toolbar:flex-none">
|
|
144
|
+
{@render search()}
|
|
145
|
+
</div>
|
|
146
|
+
{/if}
|
|
147
|
+
{#if tabs || rightActions}
|
|
148
|
+
<!-- Shrinks and wraps, not shrink-0: /tasks needs 610px here, and
|
|
149
|
+
shrink-0 clipped its last control off an iPad screen. -->
|
|
150
|
+
<div
|
|
151
|
+
class={cn(
|
|
152
|
+
"order-2 flex min-w-0 flex-wrap items-center gap-2 @3xl/toolbar:order-3",
|
|
153
|
+
hasLeading ? "@3xl/toolbar:ms-auto" : "flex-1",
|
|
154
|
+
)}
|
|
155
|
+
>
|
|
156
|
+
{#if tabs}
|
|
157
|
+
<div class={cn("flex min-w-0 items-center", !hasLeading && "me-auto")}>
|
|
158
|
+
{@render tabs()}
|
|
159
|
+
</div>
|
|
160
|
+
{/if}
|
|
161
|
+
{#if rightActions}
|
|
162
|
+
{@render rightActions()}
|
|
163
|
+
{/if}
|
|
164
|
+
</div>
|
|
165
|
+
{/if}
|
|
166
|
+
{#if filters}
|
|
167
|
+
<div
|
|
168
|
+
class="no-scrollbar order-3 flex w-full max-w-full min-w-0 items-center gap-2 overflow-x-auto @3xl/toolbar:order-2 @3xl/toolbar:w-auto"
|
|
169
|
+
>
|
|
170
|
+
{@render filters()}
|
|
171
|
+
</div>
|
|
172
|
+
{/if}
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
{/if}
|
|
176
|
+
|
|
177
|
+
{#if summary && !hideChrome}
|
|
178
|
+
{@render summary()}
|
|
179
|
+
{/if}
|
|
180
|
+
|
|
181
|
+
{#if extra && !hideChrome}
|
|
182
|
+
{@render extra()}
|
|
183
|
+
{/if}
|
|
184
|
+
|
|
185
|
+
{#if isEmpty}
|
|
186
|
+
{#if empty}
|
|
187
|
+
{@render empty()}
|
|
188
|
+
{:else if emptyState}
|
|
189
|
+
<EmptyState {...emptyState} />
|
|
190
|
+
{/if}
|
|
191
|
+
{:else if content}
|
|
192
|
+
{@render content()}
|
|
193
|
+
{/if}
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { cn } from "@heyramzi/utils";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The list-page table rhythm, Attio's: one 40px row, a hairline under each, a flat hover.
|
|
5
|
+
*/
|
|
6
|
+
export const listTable = {
|
|
7
|
+
/** The header row: 36px, a hairline under it. On the cells: `thead` zeroes its row's border. */
|
|
8
|
+
headRow: (className?: string) =>
|
|
9
|
+
cn(
|
|
10
|
+
"hover:translate-y-0 hover:bg-transparent hover:shadow-none [&>th]:h-9 [&>th]:border-b [&>th]:border-border",
|
|
11
|
+
className,
|
|
12
|
+
),
|
|
13
|
+
/** A body row. The shared row lifts on hover; a flush list row does not. */
|
|
14
|
+
row: (className?: string) =>
|
|
15
|
+
cn(
|
|
16
|
+
"border-b border-border/50 last:border-b-0 hover:translate-y-0 hover:bg-muted/40 hover:shadow-none [&>td]:h-10 [&>td]:py-0",
|
|
17
|
+
className,
|
|
18
|
+
),
|
|
19
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import type { Snippet } from "svelte";
|
|
6
|
+
import { setContext } from "svelte";
|
|
7
|
+
import { CaretLeft } from "phosphor-svelte";
|
|
8
|
+
import { cn } from "@heyramzi/utils";
|
|
9
|
+
import { Button } from "../../shadcn/button/index.js";
|
|
10
|
+
import { MASTER_DETAIL_PANE, type MasterDetailPane } from "./queue-rail.js";
|
|
11
|
+
|
|
12
|
+
// =========================
|
|
13
|
+
// TYPES
|
|
14
|
+
// =========================
|
|
15
|
+
type Props = {
|
|
16
|
+
// The list rail (an <aside>) and the content pane. Pages style both; this
|
|
17
|
+
// shell owns the two columns and the min-h-0/min-w-0 scroll containment.
|
|
18
|
+
rail: Snippet;
|
|
19
|
+
detail: Snippet;
|
|
20
|
+
// A full-width strip above both columns for facts to read before acting,
|
|
21
|
+
// like a posting cap. Pages wrapping the shell to add one broke the padding.
|
|
22
|
+
banner?: Snippet;
|
|
23
|
+
// WHY: a CSS grid track for the rail column ("16rem", "minmax(280px, 340px)").
|
|
24
|
+
// Queue pages pass `queueRailWidth` rather than a literal.
|
|
25
|
+
railWidth?: string;
|
|
26
|
+
// Stack to one column below this. "lg" because rail and detail can't both be
|
|
27
|
+
// read under 1024px. null only for a rail narrow enough for a phone.
|
|
28
|
+
stackBelow?: "lg" | "xl" | null;
|
|
29
|
+
// WHY: the phone back button says where it goes back to. "Back" alone is the
|
|
30
|
+
// browser's word and the reader has to remember what they left.
|
|
31
|
+
backLabel?: string;
|
|
32
|
+
// Bindable so a url naming a record opens on it after a refresh or a pasted
|
|
33
|
+
// link. The page sets it. Ignored above the stack breakpoint.
|
|
34
|
+
pane?: "rail" | "detail";
|
|
35
|
+
gap?: boolean;
|
|
36
|
+
pad?: boolean;
|
|
37
|
+
railClass?: string;
|
|
38
|
+
detailClass?: string;
|
|
39
|
+
class?: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// =========================
|
|
43
|
+
// PROPS
|
|
44
|
+
// =========================
|
|
45
|
+
let {
|
|
46
|
+
rail,
|
|
47
|
+
detail,
|
|
48
|
+
banner,
|
|
49
|
+
railWidth = "minmax(260px, 320px)",
|
|
50
|
+
stackBelow = "lg",
|
|
51
|
+
backLabel = "Back to the list",
|
|
52
|
+
// The rail wins by default: an unpicked detail is an empty state or a
|
|
53
|
+
// guessed row, both worse than the list.
|
|
54
|
+
pane = $bindable("rail"),
|
|
55
|
+
gap = true,
|
|
56
|
+
pad = true,
|
|
57
|
+
railClass,
|
|
58
|
+
detailClass,
|
|
59
|
+
class: className,
|
|
60
|
+
}: Props = $props();
|
|
61
|
+
|
|
62
|
+
// =========================
|
|
63
|
+
// DERIVED
|
|
64
|
+
// =========================
|
|
65
|
+
// One pane at a time when stacked. Two rows put a 34svh list scroller on top of
|
|
66
|
+
// a record scroller, and neither was readable.
|
|
67
|
+
let gridClass = $derived(
|
|
68
|
+
stackBelow === "xl"
|
|
69
|
+
? "grid-cols-1 grid-rows-[minmax(0,1fr)] xl:grid-rows-1 xl:[grid-template-columns:var(--md-cols)]"
|
|
70
|
+
: stackBelow === "lg"
|
|
71
|
+
? "grid-cols-1 grid-rows-[minmax(0,1fr)] lg:grid-rows-1 lg:[grid-template-columns:var(--md-cols)]"
|
|
72
|
+
: "[grid-template-columns:var(--md-cols)]",
|
|
73
|
+
);
|
|
74
|
+
// Hidden while stacked; back to normal above the breakpoint, where both panes show.
|
|
75
|
+
let hideWhileStacked = $derived(stackBelow === "xl" ? "max-xl:hidden" : "max-lg:hidden");
|
|
76
|
+
// Drawn only while stacked. The back bar has no job beside a visible rail.
|
|
77
|
+
let stackedOnly = $derived(stackBelow === "xl" ? "xl:hidden" : "lg:hidden");
|
|
78
|
+
|
|
79
|
+
// =========================
|
|
80
|
+
// FUNCTIONS
|
|
81
|
+
// =========================
|
|
82
|
+
// `RailRow` calls this when a record is picked. Above the breakpoint the classes
|
|
83
|
+
// that read `pane` are all prefixed away, so the flag changes nothing there.
|
|
84
|
+
setContext<MasterDetailPane>(MASTER_DETAIL_PANE, {
|
|
85
|
+
open: () => {
|
|
86
|
+
if (stackBelow) pane = "detail";
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<!-- =========================== -->
|
|
92
|
+
<!-- MARKUP -->
|
|
93
|
+
<!-- =========================== -->
|
|
94
|
+
{#snippet columns(inBanner: boolean)}
|
|
95
|
+
<div
|
|
96
|
+
class={cn(
|
|
97
|
+
"md-cols grid min-h-0",
|
|
98
|
+
inBanner ? "flex-1" : "h-full",
|
|
99
|
+
gap && "gap-4",
|
|
100
|
+
pad && !inBanner && "p-4",
|
|
101
|
+
gridClass,
|
|
102
|
+
className,
|
|
103
|
+
)}
|
|
104
|
+
style="--md-cols: {railWidth} minmax(0, 1fr)"
|
|
105
|
+
data-pane={stackBelow ? pane : undefined}
|
|
106
|
+
data-stack={stackBelow ?? undefined}
|
|
107
|
+
>
|
|
108
|
+
<aside
|
|
109
|
+
class={cn(
|
|
110
|
+
"md-rail min-h-0",
|
|
111
|
+
railClass,
|
|
112
|
+
stackBelow && pane === "detail" && hideWhileStacked,
|
|
113
|
+
)}
|
|
114
|
+
>
|
|
115
|
+
{@render rail()}
|
|
116
|
+
</aside>
|
|
117
|
+
<div
|
|
118
|
+
class={cn(
|
|
119
|
+
"md-detail flex min-h-0 min-w-0 flex-col",
|
|
120
|
+
stackBelow && pane === "rail" && hideWhileStacked,
|
|
121
|
+
)}
|
|
122
|
+
>
|
|
123
|
+
{#if stackBelow}
|
|
124
|
+
<!-- WHY a button and not a link: nothing navigated, so there is no url to
|
|
125
|
+
go back to and the browser's own back would leave the page. -->
|
|
126
|
+
<Button
|
|
127
|
+
variant="ghost"
|
|
128
|
+
size="sm"
|
|
129
|
+
class={cn("-mx-1 mb-2 self-start px-1 text-muted-foreground", stackedOnly)}
|
|
130
|
+
onclick={() => (pane = "rail")}
|
|
131
|
+
>
|
|
132
|
+
<CaretLeft size={16} weight="bold" />
|
|
133
|
+
{backLabel}
|
|
134
|
+
</Button>
|
|
135
|
+
{/if}
|
|
136
|
+
<div class={cn("min-h-0 min-w-0 flex-1", detailClass)}>
|
|
137
|
+
{@render detail()}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
{/snippet}
|
|
142
|
+
|
|
143
|
+
{#if banner}
|
|
144
|
+
<div class={cn("flex h-full min-h-0 flex-col", gap && "gap-4", pad && "p-4")}>
|
|
145
|
+
{@render banner()}
|
|
146
|
+
{@render columns(true)}
|
|
147
|
+
</div>
|
|
148
|
+
{:else}
|
|
149
|
+
{@render columns(false)}
|
|
150
|
+
{/if}
|
|
151
|
+
|
|
152
|
+
<!-- =========================== -->
|
|
153
|
+
<!-- STYLE -->
|
|
154
|
+
<!-- =========================== -->
|
|
155
|
+
<style>
|
|
156
|
+
/* The stacked list-to-record swap. The incoming pane moves: forward from the
|
|
157
|
+
right, back from the left. It runs when `data-pane` flips. */
|
|
158
|
+
@media (max-width: 1023px) {
|
|
159
|
+
.md-cols[data-stack="lg"][data-pane="detail"] > .md-detail {
|
|
160
|
+
animation: md-in-forward var(--duration-fast) var(--ease-smooth-out) both;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.md-cols[data-stack="lg"][data-pane="rail"] > .md-rail {
|
|
164
|
+
animation: md-in-back var(--duration-fast) var(--ease-smooth-out) both;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@media (max-width: 1279px) {
|
|
169
|
+
.md-cols[data-stack="xl"][data-pane="detail"] > .md-detail {
|
|
170
|
+
animation: md-in-forward var(--duration-fast) var(--ease-smooth-out) both;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.md-cols[data-stack="xl"][data-pane="rail"] > .md-rail {
|
|
174
|
+
animation: md-in-back var(--duration-fast) var(--ease-smooth-out) both;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@keyframes md-in-forward {
|
|
179
|
+
from {
|
|
180
|
+
opacity: 0;
|
|
181
|
+
transform: translateX(var(--distance-large));
|
|
182
|
+
}
|
|
183
|
+
to {
|
|
184
|
+
opacity: 1;
|
|
185
|
+
transform: none;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@keyframes md-in-back {
|
|
190
|
+
from {
|
|
191
|
+
opacity: 0;
|
|
192
|
+
transform: translateX(calc(var(--distance-large) * -1));
|
|
193
|
+
}
|
|
194
|
+
to {
|
|
195
|
+
opacity: 1;
|
|
196
|
+
transform: none;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@media (prefers-reduced-motion: reduce) {
|
|
201
|
+
.md-cols > .md-rail,
|
|
202
|
+
.md-cols > .md-detail {
|
|
203
|
+
animation-duration: 1ms;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
</style>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { describe, expect, it } from "vite-plus/test";
|
|
5
|
+
|
|
6
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const shell = readFileSync(resolve(here, "master-detail.svelte"), "utf8");
|
|
8
|
+
const row = readFileSync(resolve(here, "rail-row.svelte"), "utf8");
|
|
9
|
+
|
|
10
|
+
describe("master detail on a phone", () => {
|
|
11
|
+
it("shows one pane at a time below the stack breakpoint", () => {
|
|
12
|
+
// Two rows put a 34svh rail scroller above a second one. One row, one
|
|
13
|
+
// pane, hidden only while stacked so desktop stays untouched.
|
|
14
|
+
expect(shell).toContain("grid-rows-[minmax(0,1fr)] lg:grid-rows-1");
|
|
15
|
+
expect(shell).toContain("grid-rows-[minmax(0,1fr)] xl:grid-rows-1");
|
|
16
|
+
expect(shell).not.toContain("max-h-[34svh]");
|
|
17
|
+
expect(shell).toContain('stackBelow === "xl" ? "max-xl:hidden" : "max-lg:hidden"');
|
|
18
|
+
expect(shell).toContain('stackBelow && pane === "detail" && hideWhileStacked');
|
|
19
|
+
expect(shell).toContain('stackBelow && pane === "rail" && hideWhileStacked');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("draws a way back to the list, and only while stacked", () => {
|
|
23
|
+
expect(shell).toContain('stackBelow === "xl" ? "xl:hidden" : "lg:hidden"');
|
|
24
|
+
expect(shell).toContain('onclick={() => (pane = "rail")}');
|
|
25
|
+
expect(shell).toContain("{backLabel}");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("lets a page open on the record its url names", () => {
|
|
29
|
+
expect(shell).toContain('pane = $bindable("rail")');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("moves to the record when a rail row is picked", () => {
|
|
33
|
+
expect(shell).toContain("setContext<MasterDetailPane>(MASTER_DETAIL_PANE");
|
|
34
|
+
expect(row).toContain("getContext<MasterDetailPane | undefined>(MASTER_DETAIL_PANE)");
|
|
35
|
+
expect(row).toContain("pane?.open()");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import type { Snippet } from "svelte";
|
|
6
|
+
import { Warning, Info, CheckCircle, WarningCircle } from "phosphor-svelte";
|
|
7
|
+
import { cn } from "@heyramzi/utils";
|
|
8
|
+
import { Alert } from "../../shadcn/alert/index.js";
|
|
9
|
+
import { TONE_CLASS, type Tone as SeverityTone } from "../../lib/tone.js";
|
|
10
|
+
import type { PhosphorIcon } from "../../lib/icon-types.js";
|
|
11
|
+
|
|
12
|
+
// =========================
|
|
13
|
+
// TYPES
|
|
14
|
+
// =========================
|
|
15
|
+
// A strip that stops the reader is a severity. Neutral and accent are chip tones.
|
|
16
|
+
type Tone = Extract<SeverityTone, "destructive" | "warning" | "info" | "success">;
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
// The "look at this before you act" strip. Hand-rolled ones used raw amber
|
|
20
|
+
// and sky, so one severity showed two colours one route apart.
|
|
21
|
+
tone?: Tone;
|
|
22
|
+
icon?: PhosphorIcon;
|
|
23
|
+
// The trailing control: a retry, a jump to the thing that needs fixing.
|
|
24
|
+
actions?: Snippet;
|
|
25
|
+
children: Snippet;
|
|
26
|
+
class?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const TONE_ICON: Record<Tone, PhosphorIcon> = {
|
|
30
|
+
destructive: Warning,
|
|
31
|
+
warning: WarningCircle,
|
|
32
|
+
info: Info,
|
|
33
|
+
success: CheckCircle,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// =========================
|
|
37
|
+
// PROPS
|
|
38
|
+
// =========================
|
|
39
|
+
let { tone = "destructive", icon, actions, children, class: className }: Props = $props();
|
|
40
|
+
|
|
41
|
+
// =========================
|
|
42
|
+
// DERIVED
|
|
43
|
+
// =========================
|
|
44
|
+
let Icon = $derived(icon ?? TONE_ICON[tone]);
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<!-- =========================== -->
|
|
48
|
+
<!-- MARKUP -->
|
|
49
|
+
<!-- =========================== -->
|
|
50
|
+
<!-- WHY `status` for the calm tones: info and success shouldn't interrupt a
|
|
51
|
+
screen reader the way a failure does. -->
|
|
52
|
+
<Alert
|
|
53
|
+
variant={tone === "destructive" ? "destructive" : "default"}
|
|
54
|
+
role={tone === "destructive" || tone === "warning" ? "alert" : "status"}
|
|
55
|
+
class={cn(
|
|
56
|
+
"flex flex-wrap items-center gap-x-3 gap-y-2 rounded-xl [&>svg]:translate-y-0",
|
|
57
|
+
TONE_CLASS[tone],
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
|
+
<Icon class="size-4 shrink-0" aria-hidden="true" />
|
|
62
|
+
<span class="min-w-0 flex-1 font-medium">{@render children()}</span>
|
|
63
|
+
{#if actions}
|
|
64
|
+
<div class="flex shrink-0 items-center gap-2">
|
|
65
|
+
{@render actions()}
|
|
66
|
+
</div>
|
|
67
|
+
{/if}
|
|
68
|
+
</Alert>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import { CaretLeft, CaretRight } from "phosphor-svelte";
|
|
6
|
+
import { cn } from "@heyramzi/utils";
|
|
7
|
+
import { Button } from "../../shadcn/button/index.js";
|
|
8
|
+
import { stepTo } from "./step.js";
|
|
9
|
+
|
|
10
|
+
// =========================
|
|
11
|
+
// TYPES
|
|
12
|
+
// =========================
|
|
13
|
+
type Props = {
|
|
14
|
+
/** Ids in the order the list shows them. */
|
|
15
|
+
ids: readonly string[];
|
|
16
|
+
current: string | null | undefined;
|
|
17
|
+
onselect: (id: string) => void;
|
|
18
|
+
previousLabel?: string;
|
|
19
|
+
nextLabel?: string;
|
|
20
|
+
/** Off when two steppers share a page, so one key press moves one list. */
|
|
21
|
+
keys?: boolean;
|
|
22
|
+
class?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// =========================
|
|
26
|
+
// PROPS
|
|
27
|
+
// =========================
|
|
28
|
+
let {
|
|
29
|
+
ids,
|
|
30
|
+
current,
|
|
31
|
+
onselect,
|
|
32
|
+
previousLabel = "Previous",
|
|
33
|
+
nextLabel = "Next",
|
|
34
|
+
keys = true,
|
|
35
|
+
class: className,
|
|
36
|
+
}: Props = $props();
|
|
37
|
+
|
|
38
|
+
// =========================
|
|
39
|
+
// DERIVED
|
|
40
|
+
// =========================
|
|
41
|
+
let index = $derived(current ? ids.indexOf(current) : -1);
|
|
42
|
+
let prev = $derived(stepTo(ids, current, -1));
|
|
43
|
+
let next = $derived(stepTo(ids, current, 1));
|
|
44
|
+
|
|
45
|
+
// =========================
|
|
46
|
+
// HANDLERS
|
|
47
|
+
// =========================
|
|
48
|
+
function isTyping(event: KeyboardEvent): boolean {
|
|
49
|
+
const target = event.target as HTMLElement | null;
|
|
50
|
+
return Boolean(target?.closest("input, textarea, select, [contenteditable='true']"));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function ignored(event: KeyboardEvent): boolean {
|
|
54
|
+
const modified = event.metaKey || event.ctrlKey || event.altKey;
|
|
55
|
+
return !keys || modified || event.defaultPrevented || isTyping(event);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// K and J, as in Gmail: the arrows already belong to slide viewers and text fields.
|
|
59
|
+
function targetFor(key: string): string | null {
|
|
60
|
+
if (key === "k") return prev;
|
|
61
|
+
if (key === "j") return next;
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function onkeydown(event: KeyboardEvent) {
|
|
66
|
+
if (ignored(event)) return;
|
|
67
|
+
const id = targetFor(event.key);
|
|
68
|
+
if (!id) return;
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
onselect(id);
|
|
71
|
+
}
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<svelte:window {onkeydown} />
|
|
75
|
+
|
|
76
|
+
{#if index >= 0 && ids.length > 1}
|
|
77
|
+
<div class={cn("flex items-center gap-0.5 text-muted-foreground", className)}>
|
|
78
|
+
<Button
|
|
79
|
+
variant="ghost"
|
|
80
|
+
size="sm"
|
|
81
|
+
icon
|
|
82
|
+
class="text-muted-foreground"
|
|
83
|
+
disabled={!prev}
|
|
84
|
+
onclick={() => prev && onselect(prev)}
|
|
85
|
+
aria-label={previousLabel}
|
|
86
|
+
title={`${previousLabel} (K)`}
|
|
87
|
+
>
|
|
88
|
+
<CaretLeft size={15} />
|
|
89
|
+
</Button>
|
|
90
|
+
<span class="min-w-9 text-center text-xs tabular-nums">{index + 1}/{ids.length}</span>
|
|
91
|
+
<Button
|
|
92
|
+
variant="ghost"
|
|
93
|
+
size="sm"
|
|
94
|
+
icon
|
|
95
|
+
class="text-muted-foreground"
|
|
96
|
+
disabled={!next}
|
|
97
|
+
onclick={() => next && onselect(next)}
|
|
98
|
+
aria-label={nextLabel}
|
|
99
|
+
title={`${nextLabel} (J)`}
|
|
100
|
+
>
|
|
101
|
+
<CaretRight size={15} />
|
|
102
|
+
</Button>
|
|
103
|
+
</div>
|
|
104
|
+
{/if}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rail surface of a queue page, passed to `MasterDetail`'s `railClass`.
|
|
3
|
+
* A class, not a wrapper: the shell's `<aside>` already owns the scroll height.
|
|
4
|
+
*/
|
|
5
|
+
// Copies of this string drifted to `rounded-lg`, the control radius, not the container one.
|
|
6
|
+
export const queueRailClass =
|
|
7
|
+
"flex flex-col overflow-hidden rounded-xl border border-border bg-card";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The rail column track, passed to `MasterDetail`'s `railWidth`. One page ran
|
|
11
|
+
* 20px wider, so the detail pane jumped between pages.
|
|
12
|
+
*/
|
|
13
|
+
// 280px fits a two-line thread title. Past 340px the list competes with the record.
|
|
14
|
+
export const queueRailWidth = "minmax(280px, 340px)";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Context key for the pane `MasterDetail` shows on a phone. Picking a row moves
|
|
18
|
+
* to the record, so `RailRow` reads this instead of each page passing a callback.
|
|
19
|
+
*/
|
|
20
|
+
export const MASTER_DETAIL_PANE = Symbol("master-detail-pane");
|
|
21
|
+
|
|
22
|
+
export type MasterDetailPane = {
|
|
23
|
+
/** Show the detail pane. No-op above the stack breakpoint, where both are visible. */
|
|
24
|
+
open: () => void;
|
|
25
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import type { Snippet } from "svelte";
|
|
6
|
+
import { cn } from "@heyramzi/utils";
|
|
7
|
+
|
|
8
|
+
// =========================
|
|
9
|
+
// TYPES
|
|
10
|
+
// =========================
|
|
11
|
+
type Props = {
|
|
12
|
+
// The scroller under `RailHead`. `min-h-0` makes a flex child scroll instead
|
|
13
|
+
// of growing its parent, and it's the line hand-rolled rails forgot.
|
|
14
|
+
children: Snippet;
|
|
15
|
+
class?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// =========================
|
|
19
|
+
// PROPS
|
|
20
|
+
// =========================
|
|
21
|
+
let { children, class: className }: Props = $props();
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<!-- =========================== -->
|
|
25
|
+
<!-- MARKUP -->
|
|
26
|
+
<!-- =========================== -->
|
|
27
|
+
<div class={cn("min-h-0 flex-1 overflow-y-auto", className)}>
|
|
28
|
+
{@render children()}
|
|
29
|
+
</div>
|