@heyramzi/ui 6.1.0 → 8.0.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 +32 -40
- package/README.md +10 -4
- package/package.json +31 -29
- package/scripts/sync-shadcn.mjs +29 -16
- package/scripts/sync-transitions.mjs +11 -11
- package/src/ai/ai-button/AiButton.svelte +86 -0
- package/src/ai/ai-button/index.ts +4 -0
- package/src/ai/confirmation/Confirmation.svelte +8 -8
- package/src/ai/confirmation/confirmation-context.svelte.ts +2 -2
- package/src/ai/generation-progress/GenerationProgress.svelte +354 -0
- package/src/ai/generation-progress/index.ts +7 -0
- package/src/ai/index.ts +16 -24
- package/src/ai/prompt-input/PromptInput.svelte +19 -1
- package/src/ai/prompt-input/PromptInputSubmit.svelte +9 -3
- package/src/ai/reasoning/Reasoning.svelte +11 -8
- package/src/ambient.d.ts +4 -0
- package/src/animations/README.md +17 -17
- package/src/animations/index.css +3 -14
- package/src/animations/upstream-coverage.test.ts +1 -1
- 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/effects/border-beam/BorderBeam.svelte +1 -1
- package/src/effects/image-comparison/ImageComparison.css +42 -8
- package/src/effects/image-comparison/ImageComparison.svelte +138 -21
- package/src/effects/image-comparison/compare.test.ts +17 -0
- package/src/effects/image-comparison/compare.ts +5 -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/marquee/Marquee.css +27 -1
- package/src/effects/marquee/Marquee.svelte +67 -3
- package/src/effects/reveal/index.ts +2 -1
- package/src/effects/reveal/observe.test.ts +60 -0
- package/src/effects/reveal/observe.ts +33 -1
- package/src/effects/reveal/types.ts +7 -0
- package/src/effects/theme-toggle/ThemeToggle.css +199 -6
- package/src/effects/theme-toggle/ThemeToggle.svelte +51 -26
- package/src/gallery.test.ts +52 -0
- package/src/index.ts +5 -1
- package/src/lib/panel.ts +7 -1
- package/src/lib/sliding-pill.ts +68 -0
- package/src/lib/theme-transition.ts +41 -13
- package/src/lib/tone.ts +48 -0
- package/src/shadcn/accordion/accordion-trigger.svelte +15 -6
- package/src/shadcn/alert/alert.svelte +3 -1
- package/src/shadcn/badge/badge.svelte +8 -7
- package/src/shadcn/breadcrumb/breadcrumb-ellipsis.svelte +2 -2
- package/src/shadcn/breadcrumb/breadcrumb-separator.svelte +2 -2
- package/src/shadcn/calendar/calendar-month-select.svelte +2 -2
- package/src/shadcn/calendar/calendar-next-button.svelte +2 -2
- package/src/shadcn/calendar/calendar-prev-button.svelte +2 -2
- package/src/shadcn/calendar/calendar-year-select.svelte +2 -2
- package/src/shadcn/calendar/calendar.svelte +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/command/index.ts +3 -0
- package/src/shadcn/field/index.ts +3 -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/use-highlighted-code.svelte.ts +98 -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/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/radio-group/radio-group-item.svelte +3 -2
- package/src/shadcn/sheet/sheet-content.svelte +1 -0
- package/src/shadcn/sheet/sheet.css +48 -0
- package/src/shadcn/sonner/sonner.svelte +7 -10
- package/src/shadcn/tabs/tabs-list.svelte +13 -2
- package/src/shadcn/tabs/tabs-trigger.svelte +1 -1
- package/src/shadcn/toggle-group/toggle-group.svelte +10 -3
- 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/ai/composer/Composer.css +0 -108
- package/src/ai/composer/Composer.svelte +0 -111
- package/src/ai/composer/index.ts +0 -10
- package/src/ai/conversation/Conversation.svelte +0 -28
- package/src/ai/conversation/ConversationContent.svelte +0 -35
- package/src/ai/conversation/ConversationEmptyState.svelte +0 -46
- package/src/ai/conversation/ConversationScrollButton.svelte +0 -51
- package/src/ai/conversation/index.ts +0 -33
- package/src/ai/conversation/stick-to-bottom-context.svelte.ts +0 -131
- package/src/animations/accordion.css +0 -58
- package/src/animations/icon-swap.css +0 -43
- package/src/animations/notification-badge.css +0 -81
- package/src/animations/panel.css +0 -86
- package/src/animations/reasoning-stream.css +0 -49
- package/src/animations/skeleton-reveal.css +0 -97
- package/src/animations/streaming-text.css +0 -31
- package/src/animations/tabs-sliding.css +0 -83
- package/src/animations/texts-reveal.css +0 -70
- package/src/animations/tooltip.css +0 -72
- package/src/animations/view-transition-reveal.ts +0 -74
- package/src/charts/animated-value.css +0 -7
- package/src/charts/animated-value.svelte +0 -110
- package/src/charts/area-chart.css +0 -29
- package/src/charts/area-chart.svelte +0 -195
- package/src/charts/bar-chart.css +0 -119
- package/src/charts/bar-chart.svelte +0 -269
- package/src/charts/charts.css +0 -201
- package/src/charts/crosshair.svelte +0 -35
- package/src/charts/donut-chart.css +0 -56
- package/src/charts/donut-chart.svelte +0 -158
- package/src/charts/edge-fade.svelte +0 -43
- package/src/charts/edge-fade.ts +0 -16
- package/src/charts/figure.css +0 -100
- package/src/charts/figure.svelte +0 -122
- package/src/charts/gauge-chart.css +0 -94
- package/src/charts/gauge-chart.svelte +0 -303
- package/src/charts/hover-band.test.ts +0 -30
- package/src/charts/hover-band.ts +0 -21
- package/src/charts/hover-focus.svelte +0 -85
- package/src/charts/index.ts +0 -30
- package/src/charts/legend.css +0 -131
- package/src/charts/legend.svelte +0 -112
- package/src/charts/line-chart.css +0 -26
- package/src/charts/line-chart.svelte +0 -256
- package/src/charts/motion.ts +0 -19
- package/src/charts/number-chart.css +0 -168
- package/src/charts/number-chart.svelte +0 -177
- package/src/charts/primitives/chart-container.svelte +0 -57
- package/src/charts/primitives/chart-style.svelte +0 -36
- package/src/charts/primitives/chart-tooltip.svelte +0 -161
- package/src/charts/primitives/chart-utils.ts +0 -54
- package/src/charts/primitives/index.ts +0 -11
- package/src/charts/reveal.svelte +0 -61
- package/src/charts/ring-chart.css +0 -96
- package/src/charts/ring-chart.svelte +0 -158
- package/src/charts/sparkline.css +0 -45
- package/src/charts/sparkline.svelte +0 -140
- package/src/charts/stat-card.css +0 -103
- package/src/charts/stat-card.svelte +0 -267
- package/src/charts/time-series-chart.css +0 -28
- package/src/charts/time-series-chart.svelte +0 -179
- package/src/charts/trend-pill.css +0 -38
- package/src/charts/trend-pill.svelte +0 -48
- package/src/lib/hooks/is-mobile.svelte.ts +0 -11
- package/src/lib/utils.ts +0 -45
- package/src/showcase/AiSection.svelte +0 -157
- package/src/showcase/AnimationsSection.svelte +0 -1658
- package/src/showcase/ChartsSection.svelte +0 -458
- package/src/showcase/EffectsSection.svelte +0 -152
- package/src/showcase/ShadcnSection.svelte +0 -27
- package/src/showcase/ShowcaseImport.svelte +0 -126
- package/src/showcase/ShowcaseList.svelte +0 -132
- package/src/showcase/ShowcaseNav.svelte +0 -247
- package/src/showcase/ShowcaseSection.svelte +0 -112
- package/src/showcase/ShowcaseShell.svelte +0 -55
- package/src/showcase/ShowcaseStage.svelte +0 -342
- package/src/showcase/ShowcaseVariant.svelte +0 -109
- package/src/showcase/assets/image-reveal/art-1.jpg +0 -0
- package/src/showcase/assets/image-reveal/art-2.jpg +0 -0
- package/src/showcase/assets/image-reveal/art-3.jpg +0 -0
- package/src/showcase/check.svg +0 -1
- package/src/showcase/chevron-arms.svg +0 -2
- package/src/showcase/chevron-down.svg +0 -1
- package/src/showcase/copy.svg +0 -2
- package/src/showcase/demos/ai/confirmation.svelte +0 -59
- package/src/showcase/demos/ai/conversation.svelte +0 -59
- package/src/showcase/demos/ai/loading-state.svelte +0 -19
- package/src/showcase/demos/ai/prompt-input.svelte +0 -57
- package/src/showcase/demos/ai/reasoning.svelte +0 -19
- package/src/showcase/demos/ai/thinking-dots.svelte +0 -15
- package/src/showcase/demos/ai/voice-waveform.svelte +0 -24
- package/src/showcase/demos/effects/animated-beam.svelte +0 -50
- package/src/showcase/demos/effects/animated-group.svelte +0 -47
- package/src/showcase/demos/effects/border-beam-default.svelte +0 -20
- package/src/showcase/demos/effects/border-beam-line.svelte +0 -20
- package/src/showcase/demos/effects/border-beam-mono.svelte +0 -20
- package/src/showcase/demos/effects/border-beam-sunset.svelte +0 -20
- package/src/showcase/demos/effects/image-comparison.svelte +0 -25
- package/src/showcase/demos/effects/image-reveal.svelte +0 -49
- package/src/showcase/demos/effects/in-view.svelte +0 -41
- package/src/showcase/demos/effects/integrations-hub.svelte +0 -82
- package/src/showcase/demos/effects/magnetic.svelte +0 -31
- package/src/showcase/demos/effects/marquee.svelte +0 -33
- package/src/showcase/demos/effects/progressive-blur.svelte +0 -47
- package/src/showcase/demos/effects/spotlight.svelte +0 -40
- package/src/showcase/demos/effects/text-effect.svelte +0 -55
- package/src/showcase/demos/shadcn/accordion.svelte +0 -45
- package/src/showcase/demos/shadcn/alert-dialog.svelte +0 -23
- package/src/showcase/demos/shadcn/alert.svelte +0 -30
- package/src/showcase/demos/shadcn/attachment.svelte +0 -71
- package/src/showcase/demos/shadcn/avatar.svelte +0 -30
- package/src/showcase/demos/shadcn/badge.svelte +0 -26
- package/src/showcase/demos/shadcn/breadcrumb.svelte +0 -34
- package/src/showcase/demos/shadcn/bubble.svelte +0 -36
- package/src/showcase/demos/shadcn/button.svelte +0 -11
- package/src/showcase/demos/shadcn/calendar.svelte +0 -8
- package/src/showcase/demos/shadcn/card.svelte +0 -39
- package/src/showcase/demos/shadcn/checkbox.svelte +0 -39
- package/src/showcase/demos/shadcn/collapsible.svelte +0 -20
- package/src/showcase/demos/shadcn/command.svelte +0 -48
- package/src/showcase/demos/shadcn/context-menu.svelte +0 -54
- package/src/showcase/demos/shadcn/demo-harness.svelte +0 -12
- package/src/showcase/demos/shadcn/demos.test.ts +0 -20
- package/src/showcase/demos/shadcn/dialog.svelte +0 -38
- package/src/showcase/demos/shadcn/dropdown-menu.svelte +0 -59
- package/src/showcase/demos/shadcn/empty.svelte +0 -29
- package/src/showcase/demos/shadcn/field.svelte +0 -115
- package/src/showcase/demos/shadcn/hover-card.svelte +0 -32
- package/src/showcase/demos/shadcn/index.ts +0 -358
- package/src/showcase/demos/shadcn/input-otp.svelte +0 -19
- package/src/showcase/demos/shadcn/input.svelte +0 -5
- package/src/showcase/demos/shadcn/label.svelte +0 -11
- package/src/showcase/demos/shadcn/marker.svelte +0 -30
- package/src/showcase/demos/shadcn/message.svelte +0 -75
- package/src/showcase/demos/shadcn/popover.svelte +0 -36
- package/src/showcase/demos/shadcn/progress.svelte +0 -13
- package/src/showcase/demos/shadcn/radio-group.svelte +0 -19
- package/src/showcase/demos/shadcn/scroll-area.svelte +0 -18
- package/src/showcase/demos/shadcn/select.svelte +0 -29
- package/src/showcase/demos/shadcn/separator.svelte +0 -12
- package/src/showcase/demos/shadcn/sheet.svelte +0 -48
- package/src/showcase/demos/shadcn/skeleton.svelte +0 -11
- package/src/showcase/demos/shadcn/slider.svelte +0 -7
- package/src/showcase/demos/shadcn/sonner.svelte +0 -18
- package/src/showcase/demos/shadcn/spinner.svelte +0 -11
- package/src/showcase/demos/shadcn/switch.svelte +0 -9
- package/src/showcase/demos/shadcn/table.svelte +0 -76
- package/src/showcase/demos/shadcn/tabs.svelte +0 -62
- package/src/showcase/demos/shadcn/textarea.svelte +0 -5
- package/src/showcase/demos/shadcn/toggle-group.svelte +0 -18
- package/src/showcase/demos/shadcn/toggle.svelte +0 -14
- package/src/showcase/demos/shadcn/tooltip.svelte +0 -13
- package/src/showcase/heart.svg +0 -1
- package/src/showcase/index.ts +0 -38
- package/src/showcase/kit.test.ts +0 -72
- package/src/showcase/kit.ts +0 -17
- package/src/showcase/plus.svg +0 -1
- package/src/showcase/showcase-shell.css +0 -91
- package/src/showcase/stacked-section.css +0 -15
- package/src/showcase/success-check.svg +0 -1
- package/src/showcase/types.ts +0 -29
- package/src/svg-raw.d.ts +0 -7
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// ========================= IMPORTS
|
|
3
|
+
import { cn } from "@heyramzi/utils";
|
|
4
|
+
import { buttonVariants, type ButtonSize, type ButtonVariant } from "../../shadcn/button/index.js";
|
|
5
|
+
import type { Snippet } from "svelte";
|
|
6
|
+
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
7
|
+
|
|
8
|
+
// ========================= TYPES
|
|
9
|
+
type Props = {
|
|
10
|
+
duration?: number;
|
|
11
|
+
onConfirm: () => void;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** Uses the button recipe so call sites stop drawing their own.
|
|
14
|
+
* `variant: null` opts out, for a caller that really is a menu row. */
|
|
15
|
+
variant?: ButtonVariant | null;
|
|
16
|
+
size?: ButtonSize;
|
|
17
|
+
/** Square, for a glyph with no label. Mirrors `Button`. */
|
|
18
|
+
icon?: boolean;
|
|
19
|
+
class?: string;
|
|
20
|
+
idle: Snippet;
|
|
21
|
+
holding: Snippet;
|
|
22
|
+
} & HTMLButtonAttributes;
|
|
23
|
+
|
|
24
|
+
// ========================= PROPS
|
|
25
|
+
let {
|
|
26
|
+
duration = 800,
|
|
27
|
+
onConfirm,
|
|
28
|
+
disabled = false,
|
|
29
|
+
variant = "destructive",
|
|
30
|
+
size = "default",
|
|
31
|
+
icon = false,
|
|
32
|
+
class: className = "",
|
|
33
|
+
idle,
|
|
34
|
+
holding,
|
|
35
|
+
...restProps
|
|
36
|
+
}: Props = $props();
|
|
37
|
+
|
|
38
|
+
// ========================= STATE
|
|
39
|
+
let progress = $state(0);
|
|
40
|
+
let isHolding = $state(false);
|
|
41
|
+
let rafId: number | null = $state(null);
|
|
42
|
+
let startTime: number | null = $state(null);
|
|
43
|
+
|
|
44
|
+
// ========================= FUNCTIONS
|
|
45
|
+
function startHold() {
|
|
46
|
+
if (disabled) return;
|
|
47
|
+
isHolding = true;
|
|
48
|
+
startTime = performance.now();
|
|
49
|
+
tick();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function tick() {
|
|
53
|
+
if (!isHolding || !startTime) return;
|
|
54
|
+
|
|
55
|
+
const elapsed = performance.now() - startTime;
|
|
56
|
+
progress = Math.min(elapsed / duration, 1);
|
|
57
|
+
|
|
58
|
+
if (progress >= 1) {
|
|
59
|
+
complete();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
rafId = requestAnimationFrame(tick);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function cancelHold() {
|
|
67
|
+
if (!isHolding) return;
|
|
68
|
+
isHolding = false;
|
|
69
|
+
startTime = null;
|
|
70
|
+
if (rafId !== null) {
|
|
71
|
+
cancelAnimationFrame(rafId);
|
|
72
|
+
rafId = null;
|
|
73
|
+
}
|
|
74
|
+
// WHY: dropping is-holding lets the fill retract via the CSS transition below
|
|
75
|
+
progress = 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function complete() {
|
|
79
|
+
isHolding = false;
|
|
80
|
+
startTime = null;
|
|
81
|
+
if (rafId !== null) {
|
|
82
|
+
cancelAnimationFrame(rafId);
|
|
83
|
+
rafId = null;
|
|
84
|
+
}
|
|
85
|
+
onConfirm();
|
|
86
|
+
// Reset after confirm fires
|
|
87
|
+
progress = 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
91
|
+
if (e.key === " " || e.key === "Enter") {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
startHold();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function handleKeyup(e: KeyboardEvent) {
|
|
98
|
+
if (e.key === " " || e.key === "Enter") {
|
|
99
|
+
cancelHold();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<!-- ========================= MARKUP -->
|
|
105
|
+
<button
|
|
106
|
+
{...restProps}
|
|
107
|
+
type="button"
|
|
108
|
+
class={cn(
|
|
109
|
+
// Callers size this at about 22px. On touch, a hold needs a box the finger
|
|
110
|
+
// can stay inside for the whole press without cancelling it.
|
|
111
|
+
"hold-to-confirm relative overflow-hidden select-none",
|
|
112
|
+
"any-pointer-coarse:inline-flex any-pointer-coarse:min-h-10 any-pointer-coarse:min-w-10 any-pointer-coarse:items-center any-pointer-coarse:justify-center",
|
|
113
|
+
variant && buttonVariants({ variant, size, icon }),
|
|
114
|
+
disabled && "pointer-events-none opacity-50",
|
|
115
|
+
className,
|
|
116
|
+
)}
|
|
117
|
+
style="--progress: {progress}"
|
|
118
|
+
data-holding={isHolding}
|
|
119
|
+
onpointerdown={startHold}
|
|
120
|
+
onpointerup={cancelHold}
|
|
121
|
+
onpointerleave={cancelHold}
|
|
122
|
+
onpointercancel={cancelHold}
|
|
123
|
+
onkeydown={handleKeydown}
|
|
124
|
+
onkeyup={handleKeyup}
|
|
125
|
+
{disabled}
|
|
126
|
+
>
|
|
127
|
+
<!-- Fill sweep: scales from the left across the whole control -->
|
|
128
|
+
<span
|
|
129
|
+
class="hold-to-confirm-fill pointer-events-none absolute inset-0 z-0 origin-left bg-destructive"
|
|
130
|
+
class:is-holding={isHolding}
|
|
131
|
+
></span>
|
|
132
|
+
|
|
133
|
+
<!-- Leading edge: a bright sliver that rides the head of the fill -->
|
|
134
|
+
<span
|
|
135
|
+
class="hold-to-confirm-edge pointer-events-none absolute inset-y-0 z-0"
|
|
136
|
+
class:is-holding={isHolding}
|
|
137
|
+
></span>
|
|
138
|
+
|
|
139
|
+
<!-- Content -->
|
|
140
|
+
<span class="relative z-[1] flex items-center gap-1.5">
|
|
141
|
+
{#if isHolding}
|
|
142
|
+
<span class="hold-to-confirm-text flex items-center gap-1.5 text-destructive-foreground">
|
|
143
|
+
{@render holding()}
|
|
144
|
+
</span>
|
|
145
|
+
{:else}
|
|
146
|
+
<span class="hold-to-confirm-text flex items-center gap-1.5">
|
|
147
|
+
{@render idle()}
|
|
148
|
+
</span>
|
|
149
|
+
{/if}
|
|
150
|
+
</span>
|
|
151
|
+
</button>
|
|
152
|
+
|
|
153
|
+
<!-- ========================= STYLE -->
|
|
154
|
+
<style>
|
|
155
|
+
.hold-to-confirm {
|
|
156
|
+
touch-action: none;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.hold-to-confirm:active:not(:disabled) {
|
|
160
|
+
transform: scale(0.97);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* Fill: GPU-friendly scaleX. A vertical sheen survives the horizontal
|
|
164
|
+
scale because it runs top-to-bottom, so the wipe keeps a crisp surface. */
|
|
165
|
+
.hold-to-confirm-fill {
|
|
166
|
+
transform: scaleX(var(--progress, 0));
|
|
167
|
+
background-image: linear-gradient(
|
|
168
|
+
to bottom,
|
|
169
|
+
color-mix(in oklch, var(--color-destructive-foreground) 14%, transparent),
|
|
170
|
+
transparent 55%
|
|
171
|
+
);
|
|
172
|
+
transition: transform var(--duration-quick) var(--ease-smooth-out);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.hold-to-confirm-fill.is-holding {
|
|
176
|
+
transition: none;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Leading edge: positioned (not scaled) so its glow stays crisp at any
|
|
180
|
+
width. Sits exactly at the head of the fill and fades in while held. */
|
|
181
|
+
.hold-to-confirm-edge {
|
|
182
|
+
left: calc(var(--progress, 0) * 100%);
|
|
183
|
+
width: 2px;
|
|
184
|
+
margin-left: -2px;
|
|
185
|
+
opacity: 0;
|
|
186
|
+
background: color-mix(in oklch, var(--color-destructive-foreground) 65%, var(--color-destructive));
|
|
187
|
+
box-shadow: 0 0 4px 0 color-mix(in oklch, var(--color-destructive) 92%, transparent);
|
|
188
|
+
transition:
|
|
189
|
+
left var(--duration-quick) var(--ease-smooth-out),
|
|
190
|
+
opacity var(--duration-quick) var(--ease-out);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.hold-to-confirm-edge.is-holding {
|
|
194
|
+
opacity: 1;
|
|
195
|
+
transition: opacity var(--duration-quick) var(--ease-out);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.hold-to-confirm-text {
|
|
199
|
+
animation: holdTextFadeIn var(--duration-quick) var(--ease-out);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@keyframes holdTextFadeIn {
|
|
203
|
+
from {
|
|
204
|
+
opacity: 0;
|
|
205
|
+
}
|
|
206
|
+
to {
|
|
207
|
+
opacity: 1;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@media (prefers-reduced-motion: reduce) {
|
|
212
|
+
.hold-to-confirm:active:not(:disabled) {
|
|
213
|
+
transform: none;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.hold-to-confirm-fill {
|
|
217
|
+
transition: none;
|
|
218
|
+
transform: scaleX(var(--progress, 0));
|
|
219
|
+
background-image: none;
|
|
220
|
+
opacity: 0.5;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.hold-to-confirm-edge {
|
|
224
|
+
transition: none;
|
|
225
|
+
box-shadow: none;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.hold-to-confirm-text {
|
|
229
|
+
animation: none;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as HoldToConfirm } from "./hold-to-confirm.svelte";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { PhosphorIcon, IconWeight } from "../../lib/icon-types.js";
|
|
3
|
+
|
|
4
|
+
// =========================
|
|
5
|
+
// TYPES
|
|
6
|
+
// =========================
|
|
7
|
+
type Props = {
|
|
8
|
+
icon: PhosphorIcon;
|
|
9
|
+
size?: number;
|
|
10
|
+
class?: string;
|
|
11
|
+
weight?: IconWeight;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// =========================
|
|
15
|
+
// PROPS
|
|
16
|
+
// =========================
|
|
17
|
+
let { icon: IconComp, size = 24, class: className = "", weight }: Props = $props();
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<!-- =========================== -->
|
|
21
|
+
<!-- MARKUP -->
|
|
22
|
+
<!-- =========================== -->
|
|
23
|
+
<IconComp {size} class={className} {weight} />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Icon } from "./icon.svelte";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as LanguageFlag } from "./language-flag.svelte";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import { cn } from "@heyramzi/utils";
|
|
6
|
+
|
|
7
|
+
// =========================
|
|
8
|
+
// CONSTANTS
|
|
9
|
+
// =========================
|
|
10
|
+
// The host serves these files from /flags. Pass `flags` for another set or path.
|
|
11
|
+
const DEFAULT_FLAGS: Record<string, string> = {
|
|
12
|
+
en: "/flags/US.svg",
|
|
13
|
+
fr: "/flags/FR.svg",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// =========================
|
|
17
|
+
// PROPS
|
|
18
|
+
// =========================
|
|
19
|
+
interface Props {
|
|
20
|
+
language: string;
|
|
21
|
+
/** Language code to image URL. */
|
|
22
|
+
flags?: Record<string, string>;
|
|
23
|
+
size?: "sm" | "md";
|
|
24
|
+
showLabel?: boolean;
|
|
25
|
+
class?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
language,
|
|
30
|
+
flags = DEFAULT_FLAGS,
|
|
31
|
+
size = "sm",
|
|
32
|
+
showLabel = false,
|
|
33
|
+
class: className,
|
|
34
|
+
}: Props = $props();
|
|
35
|
+
|
|
36
|
+
// =========================
|
|
37
|
+
// DERIVED
|
|
38
|
+
// =========================
|
|
39
|
+
let src = $derived(flags[language]);
|
|
40
|
+
let sizeClass = $derived(size === "md" ? "size-5" : "size-3.5");
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<!-- =========================== -->
|
|
44
|
+
<!-- MARKUP -->
|
|
45
|
+
<!-- =========================== -->
|
|
46
|
+
{#if src}
|
|
47
|
+
<span class={cn("inline-flex items-center gap-1.5", className)}>
|
|
48
|
+
<img
|
|
49
|
+
{src}
|
|
50
|
+
alt={language.toUpperCase()}
|
|
51
|
+
class={cn("shrink-0 rounded-sm", sizeClass)}
|
|
52
|
+
loading="lazy"
|
|
53
|
+
/>
|
|
54
|
+
{#if showLabel}
|
|
55
|
+
<span class="text-xs font-medium uppercase text-muted-foreground">{language}</span>
|
|
56
|
+
{/if}
|
|
57
|
+
</span>
|
|
58
|
+
{:else if showLabel}
|
|
59
|
+
<span class={cn("text-xs font-medium uppercase text-muted-foreground", className)}>{language}</span>
|
|
60
|
+
{/if}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import EmptyState from "./empty-state.svelte";
|
|
6
|
+
import { cn } from "@heyramzi/utils";
|
|
7
|
+
import type { PhosphorIcon, IconWeight } from "../../lib/icon-types.js";
|
|
8
|
+
|
|
9
|
+
// =========================
|
|
10
|
+
// TYPES
|
|
11
|
+
// =========================
|
|
12
|
+
type Props = {
|
|
13
|
+
// Shown when nothing is picked or the board is empty. Both mean "content
|
|
14
|
+
// goes here", so both get a dashed `border-2` edge.
|
|
15
|
+
icon?: PhosphorIcon;
|
|
16
|
+
iconWeight?: IconWeight;
|
|
17
|
+
title: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
actionLabel?: string;
|
|
20
|
+
actionHref?: string;
|
|
21
|
+
onAction?: () => void;
|
|
22
|
+
class?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// =========================
|
|
26
|
+
// PROPS
|
|
27
|
+
// =========================
|
|
28
|
+
let {
|
|
29
|
+
icon,
|
|
30
|
+
iconWeight,
|
|
31
|
+
title,
|
|
32
|
+
description,
|
|
33
|
+
actionLabel,
|
|
34
|
+
actionHref,
|
|
35
|
+
onAction,
|
|
36
|
+
class: className,
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<!-- =========================== -->
|
|
41
|
+
<!-- MARKUP -->
|
|
42
|
+
<!-- =========================== -->
|
|
43
|
+
<div
|
|
44
|
+
class={cn(
|
|
45
|
+
"flex h-full items-center justify-center rounded-xl border-2 border-dashed border-border",
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
<EmptyState {icon} {iconWeight} {title} {description} {actionLabel} {actionHref} {onAction} />
|
|
50
|
+
</div>
|
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
// Answers three questions in one order: what is this, what state is it in,
|
|
13
|
+
// what can I do with it. Each queue page used to order them differently.
|
|
14
|
+
title: string;
|
|
15
|
+
// The state chip beside the title.
|
|
16
|
+
badge?: Snippet;
|
|
17
|
+
// A picture ahead of the title, like a video frame or a deck cover. Some
|
|
18
|
+
// records aren't recognisable from their title alone.
|
|
19
|
+
leading?: Snippet;
|
|
20
|
+
// Facts about the record, rendered as one dot-separated line under the
|
|
21
|
+
// title. Falsy entries drop out, so a caller can inline its conditionals.
|
|
22
|
+
meta?: Array<string | null | undefined | false>;
|
|
23
|
+
// A sentence under the meta line, when the record explains itself.
|
|
24
|
+
note?: string | null;
|
|
25
|
+
// What you can do to the record. Pinned right of the identity block, EXCEPT
|
|
26
|
+
// when a picture leads: see the note in the markup.
|
|
27
|
+
actions?: Snippet;
|
|
28
|
+
// The top-right control, a record's way out. Pass `undefined` when empty:
|
|
29
|
+
// an empty snippet is still truthy and reserves the corner.
|
|
30
|
+
corner?: Snippet;
|
|
31
|
+
// Hairline-separated rows below the identity block. Use `DetailRow`.
|
|
32
|
+
children?: Snippet;
|
|
33
|
+
class?: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// =========================
|
|
37
|
+
// PROPS
|
|
38
|
+
// =========================
|
|
39
|
+
let { title, badge, leading, meta, note, actions, corner, children, class: className }: Props =
|
|
40
|
+
$props();
|
|
41
|
+
|
|
42
|
+
// =========================
|
|
43
|
+
// DERIVED
|
|
44
|
+
// =========================
|
|
45
|
+
let facts = $derived((meta ?? []).filter((item): item is string => Boolean(item)));
|
|
46
|
+
// Actions stack under the meta when something else claims the right edge:
|
|
47
|
+
// a picture, or the corner. Pinned beside a corner, they left the title
|
|
48
|
+
// a third of the row.
|
|
49
|
+
let stackActions = $derived(Boolean(leading || corner));
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<!-- =========================== -->
|
|
53
|
+
<!-- MARKUP -->
|
|
54
|
+
<!-- =========================== -->
|
|
55
|
+
<section class={cn("overflow-hidden rounded-xl border border-border bg-card", className)}>
|
|
56
|
+
<div class="flex flex-wrap items-start justify-between gap-x-4 gap-y-3 p-4">
|
|
57
|
+
{#if leading}
|
|
58
|
+
<div class="shrink-0">{@render leading()}</div>
|
|
59
|
+
{/if}
|
|
60
|
+
<div class="min-w-0 flow-root flex-1 basis-64">
|
|
61
|
+
<!-- The corner floats, so only the lines beside it give way. Reserved
|
|
62
|
+
across the whole row it took ~150px off every line and wrapped a
|
|
63
|
+
normal title to three. -->
|
|
64
|
+
{#if corner}
|
|
65
|
+
<div class="float-end ms-3">{@render corner()}</div>
|
|
66
|
+
{/if}
|
|
67
|
+
<!-- The badge sits inline in the h2 so it trails the last word. As a
|
|
68
|
+
flex sibling it wrapped to its own row on every two-line title. -->
|
|
69
|
+
<!-- No text-balance: it evens the lines out, so a two-line title stopped
|
|
70
|
+
short of the corner instead of running the full width. -->
|
|
71
|
+
<h2 class="min-w-0 font-heading text-lg font-semibold leading-snug tracking-tight">
|
|
72
|
+
{title}{#if badge}<span class="ms-2 inline-flex align-middle">{@render badge()}</span
|
|
73
|
+
>{/if}
|
|
74
|
+
</h2>
|
|
75
|
+
{#if facts.length > 0}
|
|
76
|
+
<!-- Each dot hangs off the fact it leads, so a wrapped line's dot stays
|
|
77
|
+
in the gutter. A standalone flex-item dot dangled on the line above. -->
|
|
78
|
+
<div class="mt-1 overflow-hidden">
|
|
79
|
+
<p class="-ms-3 flex flex-wrap items-center text-sm text-muted-foreground">
|
|
80
|
+
{#each facts as fact, i (i)}
|
|
81
|
+
<span
|
|
82
|
+
class="relative ps-3 before:absolute before:start-1 before:opacity-40 before:content-['·']"
|
|
83
|
+
>{fact}</span
|
|
84
|
+
>
|
|
85
|
+
{/each}
|
|
86
|
+
</p>
|
|
87
|
+
</div>
|
|
88
|
+
{/if}
|
|
89
|
+
{#if note}
|
|
90
|
+
<p class="mt-1 text-sm text-muted-foreground">{note}</p>
|
|
91
|
+
{/if}
|
|
92
|
+
<!-- With a picture or a corner, actions sit in this column. Pinned right
|
|
93
|
+
they wrapped under the picture and read like its caption. -->
|
|
94
|
+
{#if actions && stackActions}
|
|
95
|
+
<div class="clear-both flex flex-wrap items-center gap-2 pt-3">
|
|
96
|
+
{@render actions()}
|
|
97
|
+
</div>
|
|
98
|
+
{/if}
|
|
99
|
+
</div>
|
|
100
|
+
{#if actions && !stackActions}
|
|
101
|
+
<div class="flex flex-wrap items-center gap-2">{@render actions()}</div>
|
|
102
|
+
{/if}
|
|
103
|
+
</div>
|
|
104
|
+
{#if children}
|
|
105
|
+
{@render children()}
|
|
106
|
+
{/if}
|
|
107
|
+
</section>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import type { Snippet } from "svelte";
|
|
6
|
+
import SectionLabel from "./section-label.svelte";
|
|
7
|
+
import { cn } from "@heyramzi/utils";
|
|
8
|
+
import { panelHeaderClass, panelShellClass } from "../../lib/panel.js";
|
|
9
|
+
import type { PhosphorIcon } from "../../lib/icon-types.js";
|
|
10
|
+
|
|
11
|
+
// =========================
|
|
12
|
+
// TYPES
|
|
13
|
+
// =========================
|
|
14
|
+
type Props = {
|
|
15
|
+
// One panel per idea in a detail pane. Hand-rolled copies drifted between
|
|
16
|
+
// 8px and 12px corners, so rounded-xl lives here once.
|
|
17
|
+
label?: string;
|
|
18
|
+
labelIcon?: PhosphorIcon;
|
|
19
|
+
// A mark the icon set does not carry (a platform logo), drawn ahead of the
|
|
20
|
+
// label at the same optical size.
|
|
21
|
+
labelLead?: Snippet;
|
|
22
|
+
// The right side of the header bar: a count, a hint, a button cluster.
|
|
23
|
+
actions?: Snippet;
|
|
24
|
+
// WHY: a panel whose body is a media grid or a full-bleed list supplies its
|
|
25
|
+
// own padding, so the shell must be able to step out of the way.
|
|
26
|
+
pad?: boolean;
|
|
27
|
+
// Takes the height the panels above it did not use, inside a `fill` stack.
|
|
28
|
+
// One panel per stack, and it is the one the reader works in.
|
|
29
|
+
grow?: boolean;
|
|
30
|
+
children: Snippet;
|
|
31
|
+
class?: string;
|
|
32
|
+
bodyClass?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// =========================
|
|
36
|
+
// PROPS
|
|
37
|
+
// =========================
|
|
38
|
+
let {
|
|
39
|
+
label,
|
|
40
|
+
labelIcon,
|
|
41
|
+
labelLead,
|
|
42
|
+
actions,
|
|
43
|
+
pad = true,
|
|
44
|
+
grow = false,
|
|
45
|
+
children,
|
|
46
|
+
class: className,
|
|
47
|
+
bodyClass,
|
|
48
|
+
}: Props = $props();
|
|
49
|
+
|
|
50
|
+
// =========================
|
|
51
|
+
// DERIVED
|
|
52
|
+
// =========================
|
|
53
|
+
let hasHeader = $derived(Boolean(label || labelLead || actions));
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<!-- =========================== -->
|
|
57
|
+
<!-- MARKUP -->
|
|
58
|
+
<!-- =========================== -->
|
|
59
|
+
<section
|
|
60
|
+
class={cn(
|
|
61
|
+
panelShellClass,
|
|
62
|
+
grow && "flex min-h-0 flex-1 flex-col",
|
|
63
|
+
className,
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
{#if hasHeader}
|
|
67
|
+
<div
|
|
68
|
+
class={panelHeaderClass}
|
|
69
|
+
>
|
|
70
|
+
{#if label || labelLead}
|
|
71
|
+
<SectionLabel icon={labelIcon}>
|
|
72
|
+
{#if labelLead}
|
|
73
|
+
{@render labelLead()}
|
|
74
|
+
{/if}
|
|
75
|
+
{label}
|
|
76
|
+
</SectionLabel>
|
|
77
|
+
{:else}
|
|
78
|
+
<span></span>
|
|
79
|
+
{/if}
|
|
80
|
+
{#if actions}
|
|
81
|
+
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2">
|
|
82
|
+
{@render actions()}
|
|
83
|
+
</div>
|
|
84
|
+
{/if}
|
|
85
|
+
</div>
|
|
86
|
+
{/if}
|
|
87
|
+
<div class={cn(pad && "p-4", grow && "flex min-h-0 flex-1 flex-col", bodyClass)}>
|
|
88
|
+
{@render children()}
|
|
89
|
+
</div>
|
|
90
|
+
</section>
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
// A hairline band inside `DetailHeader` or `DetailPanel` for the record's
|
|
13
|
+
// own facts, so the page never nests cards inside cards.
|
|
14
|
+
muted?: boolean;
|
|
15
|
+
children: Snippet;
|
|
16
|
+
class?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// =========================
|
|
20
|
+
// PROPS
|
|
21
|
+
// =========================
|
|
22
|
+
let { muted = false, children, class: className }: Props = $props();
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<!-- =========================== -->
|
|
26
|
+
<!-- MARKUP -->
|
|
27
|
+
<!-- =========================== -->
|
|
28
|
+
<div
|
|
29
|
+
class={cn(
|
|
30
|
+
"border-t border-border px-4 py-3 text-sm leading-relaxed",
|
|
31
|
+
muted && "text-xs text-muted-foreground",
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
{@render children()}
|
|
36
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
// A flat stack of panels at one gap. `xl:pr-1` keeps the top panel's
|
|
13
|
+
// corner clear of the shell's scrollbar.
|
|
14
|
+
|
|
15
|
+
// `fill` makes the stack at least as tall as its scroller, so a `grow`
|
|
16
|
+
// panel (like a writing box) takes the empty bottom third back.
|
|
17
|
+
fill?: boolean;
|
|
18
|
+
children: Snippet;
|
|
19
|
+
class?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// =========================
|
|
23
|
+
// PROPS
|
|
24
|
+
// =========================
|
|
25
|
+
let { fill = false, children, class: className }: Props = $props();
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<!-- =========================== -->
|
|
29
|
+
<!-- MARKUP -->
|
|
30
|
+
<!-- =========================== -->
|
|
31
|
+
<div class={cn("flex flex-col gap-4 xl:pr-1", fill && "min-h-full", className)}>
|
|
32
|
+
{@render children()}
|
|
33
|
+
</div>
|