@ponti-studios/ui 2.0.1 → 3.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/README.md +36 -3
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.js +3 -0
- package/dist/components/data-display/accordion.d.ts +10 -0
- package/dist/components/data-display/accordion.js +53 -0
- package/{src/components/data-display/index.ts → dist/components/data-display/index.d.ts} +1 -10
- package/dist/components/data-display/index.js +8 -0
- package/dist/components/data-display/metric-card.d.ts +9 -0
- package/dist/components/data-display/metric-card.js +25 -0
- package/dist/components/data-display/sort-controls.d.ts +13 -0
- package/dist/components/data-display/sort-controls.js +57 -0
- package/dist/components/data-display/sort-row.d.ts +10 -0
- package/dist/components/data-display/sort-row.js +52 -0
- package/dist/components/data-display/table.d.ts +10 -0
- package/dist/components/data-display/table.js +57 -0
- package/dist/components/feedback/alert.d.ts +9 -0
- package/dist/components/feedback/alert.js +38 -0
- package/dist/components/feedback/empty-state.d.ts +18 -0
- package/dist/components/feedback/empty-state.js +36 -0
- package/{src/components/feedback/index.ts → dist/components/feedback/index.d.ts} +2 -0
- package/dist/components/feedback/index.js +7 -0
- package/dist/components/feedback/progress.d.ts +6 -0
- package/dist/components/feedback/progress.js +25 -0
- package/dist/components/feedback/skeleton.d.ts +2 -0
- package/dist/components/feedback/skeleton.js +11 -0
- package/dist/components/feedback/spinner.d.ts +14 -0
- package/dist/components/feedback/spinner.js +27 -0
- package/dist/components/feedback/update-guard.d.ts +13 -0
- package/dist/components/feedback/update-guard.js +38 -0
- package/dist/components/filters/active-filters-bar.d.ts +14 -0
- package/dist/components/filters/active-filters-bar.js +44 -0
- package/dist/components/filters/filter-chip.d.ts +11 -0
- package/dist/components/filters/filter-chip.js +33 -0
- package/dist/components/filters/filter-select.d.ts +17 -0
- package/dist/components/filters/filter-select.js +46 -0
- package/dist/components/filters/index.js +4 -0
- package/dist/components/forms/checkbox.d.ts +4 -0
- package/dist/components/forms/checkbox.js +24 -0
- package/dist/components/forms/date-month-select.d.ts +11 -0
- package/dist/components/forms/date-month-select.js +47 -0
- package/dist/components/forms/date-picker.d.ts +60 -0
- package/dist/components/forms/date-picker.js +193 -0
- package/dist/components/forms/date-utils.d.ts +14 -0
- package/dist/components/forms/date-utils.js +18 -0
- package/dist/components/forms/drop-zone.d.ts +42 -0
- package/dist/components/forms/drop-zone.js +184 -0
- package/{src/components/forms/index.ts → dist/components/forms/index.d.ts} +3 -9
- package/dist/components/forms/index.js +17 -0
- package/dist/components/forms/input.d.ts +3 -0
- package/dist/components/forms/input.js +15 -0
- package/dist/components/forms/label.d.ts +3 -0
- package/dist/components/forms/label.js +14 -0
- package/dist/components/forms/passkey-management.d.ts +14 -0
- package/dist/components/forms/passkey-management.js +132 -0
- package/dist/components/forms/radio-group.d.ts +6 -0
- package/dist/components/forms/radio-group.js +28 -0
- package/dist/components/forms/select.d.ts +18 -0
- package/dist/components/forms/select.js +103 -0
- package/dist/components/forms/slider.d.ts +9 -0
- package/dist/components/forms/slider.js +25 -0
- package/dist/components/forms/stepper.d.ts +15 -0
- package/dist/components/forms/stepper.js +41 -0
- package/dist/components/forms/switch.d.ts +6 -0
- package/dist/components/forms/switch.js +21 -0
- package/dist/components/forms/text-field.d.ts +15 -0
- package/dist/components/forms/text-field.js +46 -0
- package/dist/components/forms/text-field.types.d.ts +8 -0
- package/dist/components/forms/textarea.d.ts +3 -0
- package/dist/components/forms/textarea.js +14 -0
- package/dist/components/layout/index.js +5 -0
- package/dist/components/layout/particle-background.d.ts +19 -0
- package/dist/components/layout/particle-background.js +171 -0
- package/dist/components/layout/scroll-area.d.ts +9 -0
- package/dist/components/layout/scroll-area.js +35 -0
- package/dist/components/layout/section-intro.d.ts +12 -0
- package/dist/components/layout/section-intro.js +28 -0
- package/dist/components/layout/surface-panel.d.ts +7 -0
- package/dist/components/layout/surface-panel.js +11 -0
- package/dist/components/navigation/app-navigation.d.ts +67 -0
- package/dist/components/navigation/app-navigation.js +106 -0
- package/{src/components/navigation/index.ts → dist/components/navigation/index.d.ts} +1 -7
- package/dist/components/navigation/index.js +4 -0
- package/dist/components/navigation/pagination-controls.d.ts +7 -0
- package/dist/components/navigation/pagination-controls.js +50 -0
- package/dist/components/navigation/tabs.d.ts +7 -0
- package/dist/components/navigation/tabs.js +35 -0
- package/dist/components/overlays/bottom-sheet.d.ts +20 -0
- package/dist/components/overlays/bottom-sheet.js +101 -0
- package/dist/components/overlays/command.d.ts +29 -0
- package/dist/components/overlays/command.js +123 -0
- package/dist/components/overlays/dialog.d.ts +30 -0
- package/dist/components/overlays/dialog.js +105 -0
- package/dist/components/overlays/dropdown-menu.d.ts +29 -0
- package/dist/components/overlays/dropdown-menu.js +128 -0
- package/dist/components/overlays/index.d.ts +7 -0
- package/dist/components/overlays/index.js +8 -0
- package/dist/components/overlays/popover.d.ts +14 -0
- package/dist/components/overlays/popover.js +56 -0
- package/dist/components/overlays/sheet.d.ts +27 -0
- package/dist/components/overlays/sheet.js +81 -0
- package/dist/components/overlays/tooltip.d.ts +10 -0
- package/dist/components/overlays/tooltip.js +23 -0
- package/dist/components/primitives/avatar.d.ts +14 -0
- package/dist/components/primitives/avatar.js +72 -0
- package/dist/components/primitives/badge.d.ts +7 -0
- package/dist/components/primitives/badge.js +27 -0
- package/dist/components/primitives/button.d.ts +13 -0
- package/dist/components/primitives/button.js +113 -0
- package/{src/components/primitives/button.types.ts → dist/components/primitives/button.types.d.ts} +4 -6
- package/dist/components/primitives/card.d.ts +9 -0
- package/dist/components/primitives/card.js +56 -0
- package/{src/components/primitives/index.ts → dist/components/primitives/index.d.ts} +1 -9
- package/dist/components/primitives/index.js +7 -0
- package/dist/components/primitives/segmented-control.d.ts +20 -0
- package/dist/components/primitives/segmented-control.js +27 -0
- package/dist/components/primitives/separator.d.ts +4 -0
- package/dist/components/primitives/separator.js +16 -0
- package/dist/constants/chart-colors.d.ts +33 -0
- package/dist/constants/chart-colors.js +31 -0
- package/dist/hooks/index.js +9 -0
- package/{src/hooks/sort.types.ts → dist/hooks/sort.types.d.ts} +2 -4
- package/dist/hooks/use-api-client.d.ts +30 -0
- package/dist/hooks/use-api-client.js +106 -0
- package/dist/hooks/use-debounced-value.d.ts +1 -0
- package/dist/hooks/use-debounced-value.js +12 -0
- package/dist/hooks/use-derived-filter-state.d.ts +20 -0
- package/dist/hooks/use-derived-filter-state.js +39 -0
- package/dist/hooks/use-filter-state.d.ts +12 -0
- package/dist/hooks/use-filter-state.js +37 -0
- package/dist/hooks/use-media-query.d.ts +6 -0
- package/dist/hooks/use-media-query.js +27 -0
- package/dist/hooks/use-mobile.d.ts +1 -0
- package/dist/hooks/use-mobile.js +14 -0
- package/dist/hooks/use-sort.d.ts +11 -0
- package/dist/hooks/use-sort.js +14 -0
- package/{src/index.ts → dist/index.d.ts} +4 -32
- package/dist/index.js +15 -0
- package/dist/integrations/service-worker.d.ts +2 -0
- package/dist/integrations/service-worker.js +2 -0
- package/dist/lib/auth.d.ts +2 -0
- package/dist/lib/auth.js +7 -0
- package/dist/lib/clipboard.d.ts +1 -0
- package/dist/lib/clipboard.js +10 -0
- package/dist/lib/index.js +4 -0
- package/{src/lib/slot.tsx → dist/lib/slot.d.ts} +2 -15
- package/dist/lib/slot.js +14 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +8 -0
- package/dist/native/components.d.ts +41 -0
- package/dist/native/components.js +135 -0
- package/dist/native/index.js +3 -0
- package/dist/native/theme.d.ts +19 -0
- package/dist/native/theme.js +58 -0
- package/dist/styles/tokens/css-vars.d.ts +7 -0
- package/dist/styles/tokens/css-vars.js +5 -0
- package/dist/styles/tokens/generated/dark.colors.generated.d.ts +40 -0
- package/dist/styles/tokens/generated/dark.colors.generated.js +39 -0
- package/dist/styles/tokens/generated/foundations.generated.d.ts +323 -0
- package/dist/styles/tokens/generated/foundations.generated.js +453 -0
- package/dist/styles/tokens/generated/light.colors.generated.d.ts +40 -0
- package/dist/styles/tokens/generated/light.colors.generated.js +39 -0
- package/dist/styles/tokens/index.d.ts +317 -0
- package/dist/styles/tokens/index.js +14 -0
- package/dist/styles.css +1 -1
- package/dist/translations/en.d.ts +34 -0
- package/dist/translations/index.d.ts +39 -0
- package/dist/translations/ui-provider.d.ts +9 -0
- package/dist/translations/ui-provider.js +16 -0
- package/dist/types/upload.d.ts +36 -0
- package/package.json +50 -33
- package/src/components/data-display/accordion.stories.tsx +0 -57
- package/src/components/data-display/accordion.tsx +0 -86
- package/src/components/data-display/metric-card.stories.tsx +0 -48
- package/src/components/data-display/metric-card.tsx +0 -20
- package/src/components/data-display/sort-controls.stories.tsx +0 -45
- package/src/components/data-display/sort-controls.tsx +0 -96
- package/src/components/data-display/sort-row.tsx +0 -88
- package/src/components/data-display/table.stories.tsx +0 -75
- package/src/components/data-display/table.tsx +0 -80
- package/src/components/feedback/alert.stories.tsx +0 -51
- package/src/components/feedback/alert.tsx +0 -64
- package/src/components/feedback/empty-state.tsx +0 -76
- package/src/components/feedback/progress.stories.tsx +0 -54
- package/src/components/feedback/progress.tsx +0 -41
- package/src/components/feedback/skeleton.stories.tsx +0 -36
- package/src/components/feedback/skeleton.tsx +0 -7
- package/src/components/feedback/spinner.stories.tsx +0 -52
- package/src/components/feedback/spinner.tsx +0 -47
- package/src/components/feedback/update-guard.stories.tsx +0 -28
- package/src/components/feedback/update-guard.tsx +0 -57
- package/src/components/filters/active-filters-bar.stories.tsx +0 -114
- package/src/components/filters/active-filters-bar.tsx +0 -61
- package/src/components/filters/filter-chip.tsx +0 -57
- package/src/components/filters/filter-select.stories.tsx +0 -71
- package/src/components/filters/filter-select.tsx +0 -68
- package/src/components/forms/checkbox.stories.tsx +0 -35
- package/src/components/forms/checkbox.tsx +0 -38
- package/src/components/forms/date-month-select.stories.tsx +0 -26
- package/src/components/forms/date-month-select.tsx +0 -79
- package/src/components/forms/date-picker.stories.tsx +0 -188
- package/src/components/forms/date-picker.tsx +0 -303
- package/src/components/forms/date-utils.ts +0 -37
- package/src/components/forms/drop-zone.stories.tsx +0 -140
- package/src/components/forms/drop-zone.tsx +0 -298
- package/src/components/forms/input.stories.tsx +0 -47
- package/src/components/forms/input.tsx +0 -24
- package/src/components/forms/label.stories.tsx +0 -16
- package/src/components/forms/label.tsx +0 -25
- package/src/components/forms/passkey-management.stories.tsx +0 -85
- package/src/components/forms/passkey-management.tsx +0 -168
- package/src/components/forms/radio-group.stories.tsx +0 -46
- package/src/components/forms/radio-group.tsx +0 -44
- package/src/components/forms/select.stories.tsx +0 -207
- package/src/components/forms/select.tsx +0 -182
- package/src/components/forms/slider.stories.tsx +0 -70
- package/src/components/forms/slider.tsx +0 -50
- package/src/components/forms/stepper.stories.tsx +0 -111
- package/src/components/forms/stepper.tsx +0 -66
- package/src/components/forms/switch.stories.tsx +0 -144
- package/src/components/forms/switch.tsx +0 -39
- package/src/components/forms/text-field.stories.tsx +0 -87
- package/src/components/forms/text-field.tsx +0 -75
- package/src/components/forms/text-field.types.ts +0 -9
- package/src/components/forms/textarea.stories.tsx +0 -150
- package/src/components/forms/textarea.tsx +0 -22
- package/src/components/layout/particle-background.stories.tsx +0 -38
- package/src/components/layout/particle-background.tsx +0 -378
- package/src/components/layout/scroll-area.stories.tsx +0 -91
- package/src/components/layout/scroll-area.tsx +0 -79
- package/src/components/layout/section-intro.stories.tsx +0 -29
- package/src/components/layout/section-intro.tsx +0 -31
- package/src/components/layout/surface-panel.stories.tsx +0 -32
- package/src/components/layout/surface-panel.tsx +0 -23
- package/src/components/navigation/app-navigation.stories.tsx +0 -217
- package/src/components/navigation/app-navigation.tsx +0 -234
- package/src/components/navigation/pagination-controls.stories.tsx +0 -41
- package/src/components/navigation/pagination-controls.tsx +0 -46
- package/src/components/navigation/tabs.stories.tsx +0 -48
- package/src/components/navigation/tabs.tsx +0 -52
- package/src/components/overlays/bottom-sheet.stories.tsx +0 -52
- package/src/components/overlays/bottom-sheet.tsx +0 -179
- package/src/components/overlays/command.stories.tsx +0 -65
- package/src/components/overlays/command.tsx +0 -227
- package/src/components/overlays/dialog.stories.tsx +0 -148
- package/src/components/overlays/dialog.tsx +0 -203
- package/src/components/overlays/dropdown-menu.stories.tsx +0 -74
- package/src/components/overlays/dropdown-menu.tsx +0 -236
- package/src/components/overlays/index.ts +0 -56
- package/src/components/overlays/popover.stories.tsx +0 -51
- package/src/components/overlays/popover.tsx +0 -78
- package/src/components/overlays/sheet.stories.tsx +0 -68
- package/src/components/overlays/sheet.tsx +0 -155
- package/src/components/overlays/tooltip.stories.tsx +0 -29
- package/src/components/overlays/tooltip.tsx +0 -41
- package/src/components/primitives/avatar-placeholder.svg +0 -13
- package/src/components/primitives/avatar.stories.tsx +0 -72
- package/src/components/primitives/avatar.tsx +0 -136
- package/src/components/primitives/badge.stories.tsx +0 -43
- package/src/components/primitives/badge.tsx +0 -43
- package/src/components/primitives/button.stories.tsx +0 -113
- package/src/components/primitives/button.tsx +0 -131
- package/src/components/primitives/card.stories.tsx +0 -59
- package/src/components/primitives/card.tsx +0 -66
- package/src/components/primitives/segmented-control.stories.tsx +0 -54
- package/src/components/primitives/segmented-control.tsx +0 -73
- package/src/components/primitives/separator.stories.tsx +0 -34
- package/src/components/primitives/separator.tsx +0 -27
- package/src/constants/chart-colors.ts +0 -39
- package/src/env.d.ts +0 -9
- package/src/hooks/use-api-client.ts +0 -122
- package/src/hooks/use-debounced-value.ts +0 -12
- package/src/hooks/use-derived-filter-state.ts +0 -70
- package/src/hooks/use-filter-state.ts +0 -66
- package/src/hooks/use-media-query.ts +0 -69
- package/src/hooks/use-mobile.tsx +0 -19
- package/src/hooks/use-sort.ts +0 -20
- package/src/lib/auth.ts +0 -5
- package/src/lib/clipboard.ts +0 -8
- package/src/lib/utils.ts +0 -6
- package/src/native/components.tsx +0 -198
- package/src/native/theme.tsx +0 -69
- package/src/storybook/controls.ts +0 -117
- package/src/storybook/options.ts +0 -52
- package/src/styles/build.ts +0 -1
- package/src/styles/index.css +0 -423
- package/src/styles/nokia.css +0 -36
- package/src/styles/package.css +0 -3
- package/src/styles/tokens/chart-colors.stories.tsx +0 -189
- package/src/styles/tokens/color-systems.stories.tsx +0 -190
- package/src/styles/tokens/css-vars.ts +0 -15
- package/src/styles/tokens/generated/dark-class.css +0 -39
- package/src/styles/tokens/generated/dark.colors.generated.ts +0 -39
- package/src/styles/tokens/generated/dark.css +0 -39
- package/src/styles/tokens/generated/foundations.css +0 -149
- package/src/styles/tokens/generated/foundations.generated.ts +0 -454
- package/src/styles/tokens/generated/light.colors.generated.ts +0 -39
- package/src/styles/tokens/generated/light.css +0 -39
- package/src/styles/tokens/index.ts +0 -70
- package/src/styles/tokens/layout.stories.tsx +0 -50
- package/src/styles/tokens/scripts/check-foundations.mjs +0 -126
- package/src/styles/tokens/scripts/check-source.mjs +0 -53
- package/src/styles/tokens/source/dark.tokens.json +0 -138
- package/src/styles/tokens/source/foundations.tokens.json +0 -344
- package/src/styles/tokens/source/light.tokens.json +0 -138
- package/src/styles/tokens/style-dictionary.dark-class.json +0 -16
- package/src/styles/tokens/style-dictionary.dark-storybook.json +0 -16
- package/src/styles/tokens/style-dictionary.dark.json +0 -21
- package/src/styles/tokens/style-dictionary.foundations.json +0 -21
- package/src/styles/tokens/style-dictionary.light-storybook.json +0 -16
- package/src/styles/tokens/style-dictionary.light.json +0 -21
- package/src/styles/tokens/typography.stories.tsx +0 -157
- package/src/translations/en.ts +0 -34
- package/src/translations/index.ts +0 -32
- package/src/types/upload.ts +0 -42
- /package/{src/components/filters/index.ts → dist/components/filters/index.d.ts} +0 -0
- /package/{src/components/layout/index.ts → dist/components/layout/index.d.ts} +0 -0
- /package/{src/hooks/index.ts → dist/hooks/index.d.ts} +0 -0
- /package/{src/lib/index.ts → dist/lib/index.d.ts} +0 -0
- /package/{src/native/index.ts → dist/native/index.d.ts} +0 -0
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
|
|
3
|
-
import { ParticleBackground } from "./particle-background";
|
|
4
|
-
|
|
5
|
-
const meta = {
|
|
6
|
-
title: "Layout/ParticleBackground",
|
|
7
|
-
component: ParticleBackground,
|
|
8
|
-
tags: ["autodocs"],
|
|
9
|
-
} satisfies Meta<typeof ParticleBackground>;
|
|
10
|
-
|
|
11
|
-
export default meta;
|
|
12
|
-
type Story = StoryObj<typeof meta>;
|
|
13
|
-
|
|
14
|
-
export const Default: Story = {
|
|
15
|
-
render: (args) => (
|
|
16
|
-
<div className="bg-background relative h-80 w-[640px] overflow-hidden">
|
|
17
|
-
<ParticleBackground {...args} />
|
|
18
|
-
</div>
|
|
19
|
-
),
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export const Static: Story = {
|
|
23
|
-
args: { interactive: false },
|
|
24
|
-
render: (args) => (
|
|
25
|
-
<div className="bg-background relative h-80 w-[640px] overflow-hidden">
|
|
26
|
-
<ParticleBackground {...args} />
|
|
27
|
-
</div>
|
|
28
|
-
),
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export const Disabled: Story = {
|
|
32
|
-
args: { enabled: false },
|
|
33
|
-
render: (args) => (
|
|
34
|
-
<div className="bg-background relative h-80 w-[640px] overflow-hidden">
|
|
35
|
-
<ParticleBackground {...args} />
|
|
36
|
-
</div>
|
|
37
|
-
),
|
|
38
|
-
};
|
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
|
|
5
|
-
import { useMediaQuery } from "../../hooks/use-media-query";
|
|
6
|
-
import { cn } from "../../lib/utils";
|
|
7
|
-
|
|
8
|
-
interface Particle {
|
|
9
|
-
x: number;
|
|
10
|
-
y: number;
|
|
11
|
-
vx: number;
|
|
12
|
-
vy: number;
|
|
13
|
-
opacity: number;
|
|
14
|
-
depth: number;
|
|
15
|
-
twinklePhase: number;
|
|
16
|
-
twinkleSpeed: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface ParticleBackgroundPalette {
|
|
20
|
-
particle: string;
|
|
21
|
-
link: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface ParticleBackgroundProps extends React.ComponentProps<"div"> {
|
|
25
|
-
enabled?: boolean;
|
|
26
|
-
interactive?: boolean;
|
|
27
|
-
particleCount?: number;
|
|
28
|
-
particleDensity?: number;
|
|
29
|
-
maxLinkDistance?: number;
|
|
30
|
-
attractRadius?: number;
|
|
31
|
-
attractStrength?: number;
|
|
32
|
-
velocity?: number;
|
|
33
|
-
showGradient?: boolean;
|
|
34
|
-
palette?: ParticleBackgroundPalette;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const DEFAULT_PARTICLE_COUNT = 320;
|
|
38
|
-
const DEFAULT_PARTICLE_DENSITY = 2800;
|
|
39
|
-
const DEFAULT_LINK_DISTANCE = 90;
|
|
40
|
-
const DEFAULT_ATTRACT_STRENGTH = 0.003;
|
|
41
|
-
const DEFAULT_ATTRACT_RADIUS = 150;
|
|
42
|
-
const DEFAULT_VELOCITY = 0.06;
|
|
43
|
-
const PARALLAX_STRENGTH = 0.015;
|
|
44
|
-
const TWINKLE_MIN_SPEED = 0.0002;
|
|
45
|
-
const TWINKLE_MAX_SPEED = 0.0008;
|
|
46
|
-
const PARTICLE_BASE_OPACITY = 0.2;
|
|
47
|
-
|
|
48
|
-
const DEFAULT_PALETTE = {
|
|
49
|
-
particle: "15,23,42",
|
|
50
|
-
link: "15,23,42",
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
function readCssVar(name: string, fallback: string) {
|
|
54
|
-
if (typeof window === "undefined") return fallback;
|
|
55
|
-
const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
56
|
-
return value || fallback;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function toRgbTriplet(color: string): string {
|
|
60
|
-
if (color.startsWith("#")) {
|
|
61
|
-
let hex = color.slice(1);
|
|
62
|
-
if (hex.length === 3) {
|
|
63
|
-
hex = hex
|
|
64
|
-
.split("")
|
|
65
|
-
.map((c) => c + c)
|
|
66
|
-
.join("");
|
|
67
|
-
}
|
|
68
|
-
const num = Number.parseInt(hex, 16);
|
|
69
|
-
return `${(num >> 16) & 255},${(num >> 8) & 255},${num & 255}`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const match = color.match(/rgba?\(([^)]+)\)/);
|
|
73
|
-
if (match) {
|
|
74
|
-
return match[1]
|
|
75
|
-
.split(",")
|
|
76
|
-
.slice(0, 3)
|
|
77
|
-
.map((part) => part.trim())
|
|
78
|
-
.join(",");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return DEFAULT_PALETTE.particle;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function useThemePalette(): ParticleBackgroundPalette {
|
|
85
|
-
const [palette, setPalette] = React.useState<ParticleBackgroundPalette>(DEFAULT_PALETTE);
|
|
86
|
-
|
|
87
|
-
React.useEffect(() => {
|
|
88
|
-
const resolve = () => {
|
|
89
|
-
const accent = toRgbTriplet(readCssVar("--color-primary", "#0969da"));
|
|
90
|
-
const foreground = toRgbTriplet(readCssVar("--color-foreground", "#25292e"));
|
|
91
|
-
setPalette({ particle: accent, link: foreground });
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
resolve();
|
|
95
|
-
|
|
96
|
-
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
97
|
-
media.addEventListener("change", resolve);
|
|
98
|
-
|
|
99
|
-
return () => {
|
|
100
|
-
media.removeEventListener("change", resolve);
|
|
101
|
-
};
|
|
102
|
-
}, []);
|
|
103
|
-
|
|
104
|
-
return palette;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function useResolvedTheme() {
|
|
108
|
-
const [resolvedTheme, setResolvedTheme] = React.useState<"light" | "dark">("light");
|
|
109
|
-
|
|
110
|
-
React.useEffect(() => {
|
|
111
|
-
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
112
|
-
const resolve = () => setResolvedTheme(media.matches ? "dark" : "light");
|
|
113
|
-
|
|
114
|
-
resolve();
|
|
115
|
-
media.addEventListener("change", resolve);
|
|
116
|
-
|
|
117
|
-
return () => {
|
|
118
|
-
media.removeEventListener("change", resolve);
|
|
119
|
-
};
|
|
120
|
-
}, []);
|
|
121
|
-
|
|
122
|
-
return resolvedTheme;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** Animated canvas background of drifting, linked particles. Skips rendering under `prefers-reduced-motion`. */
|
|
126
|
-
export function ParticleBackground({
|
|
127
|
-
className,
|
|
128
|
-
enabled = true,
|
|
129
|
-
interactive = true,
|
|
130
|
-
particleCount,
|
|
131
|
-
particleDensity = DEFAULT_PARTICLE_DENSITY,
|
|
132
|
-
maxLinkDistance = DEFAULT_LINK_DISTANCE,
|
|
133
|
-
attractRadius = DEFAULT_ATTRACT_RADIUS,
|
|
134
|
-
attractStrength = DEFAULT_ATTRACT_STRENGTH,
|
|
135
|
-
velocity = DEFAULT_VELOCITY,
|
|
136
|
-
showGradient: _showGradient = true,
|
|
137
|
-
palette,
|
|
138
|
-
...props
|
|
139
|
-
}: ParticleBackgroundProps) {
|
|
140
|
-
const prefersReduced = useMediaQuery("(prefers-reduced-motion: reduce)");
|
|
141
|
-
const canvasRef = React.useRef<HTMLCanvasElement>(null);
|
|
142
|
-
const mouse = React.useRef({ x: -9999, y: -9999 });
|
|
143
|
-
const particles = React.useRef<Particle[]>([]);
|
|
144
|
-
const raf = React.useRef<number>(0);
|
|
145
|
-
const scrollOffset = React.useRef(typeof window === "undefined" ? 0 : window.scrollY);
|
|
146
|
-
const resolvedTheme = useResolvedTheme();
|
|
147
|
-
const themePalette = useThemePalette();
|
|
148
|
-
const resolvedPalette = palette ?? themePalette;
|
|
149
|
-
|
|
150
|
-
React.useEffect(() => {
|
|
151
|
-
if (prefersReduced || !enabled) return;
|
|
152
|
-
|
|
153
|
-
const canvasEl = canvasRef.current;
|
|
154
|
-
if (!canvasEl) return;
|
|
155
|
-
|
|
156
|
-
const ctx = canvasEl.getContext("2d");
|
|
157
|
-
if (!ctx) return;
|
|
158
|
-
|
|
159
|
-
const canvas = canvasEl;
|
|
160
|
-
const context = ctx;
|
|
161
|
-
|
|
162
|
-
function resize() {
|
|
163
|
-
const rect = canvas.getBoundingClientRect();
|
|
164
|
-
const dpr = window.devicePixelRatio || 1;
|
|
165
|
-
|
|
166
|
-
canvas.width = Math.max(1, Math.round(rect.width * dpr));
|
|
167
|
-
canvas.height = Math.max(1, Math.round(rect.height * dpr));
|
|
168
|
-
context.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
169
|
-
|
|
170
|
-
const targetCount =
|
|
171
|
-
particleCount ??
|
|
172
|
-
Math.max(
|
|
173
|
-
80,
|
|
174
|
-
Math.min(
|
|
175
|
-
DEFAULT_PARTICLE_COUNT,
|
|
176
|
-
Math.round((rect.width * rect.height) / particleDensity),
|
|
177
|
-
),
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
particles.current = Array.from({ length: targetCount }, () => {
|
|
181
|
-
const depth = Math.random();
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
x: Math.random() * rect.width,
|
|
185
|
-
y: Math.random() * rect.height,
|
|
186
|
-
vx: (Math.random() - 0.5) * velocity * (0.4 + depth * 0.8),
|
|
187
|
-
vy: (Math.random() - 0.5) * velocity * (0.4 + depth * 0.8),
|
|
188
|
-
opacity: PARTICLE_BASE_OPACITY * (0.5 + depth * 0.5),
|
|
189
|
-
depth,
|
|
190
|
-
twinklePhase: Math.random() * Math.PI * 2,
|
|
191
|
-
twinkleSpeed: TWINKLE_MIN_SPEED + Math.random() * (TWINKLE_MAX_SPEED - TWINKLE_MIN_SPEED),
|
|
192
|
-
};
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
let isRunning = false;
|
|
197
|
-
let inView = true;
|
|
198
|
-
let tabVisible = !document.hidden;
|
|
199
|
-
const renderPositions: { x: number; y: number }[] = [];
|
|
200
|
-
|
|
201
|
-
function draw(time: number) {
|
|
202
|
-
const width = canvas.clientWidth;
|
|
203
|
-
const height = canvas.clientHeight;
|
|
204
|
-
|
|
205
|
-
context.clearRect(0, 0, width, height);
|
|
206
|
-
context.lineCap = "round";
|
|
207
|
-
|
|
208
|
-
const ps = particles.current;
|
|
209
|
-
const parallaxY = scrollOffset.current * PARALLAX_STRENGTH;
|
|
210
|
-
|
|
211
|
-
for (let i = 0; i < ps.length; i++) {
|
|
212
|
-
const p = ps[i];
|
|
213
|
-
|
|
214
|
-
const mdx = mouse.current.x - p.x;
|
|
215
|
-
const mdy = mouse.current.y - p.y;
|
|
216
|
-
const mdist = Math.sqrt(mdx * mdx + mdy * mdy);
|
|
217
|
-
|
|
218
|
-
if (interactive && mdist < attractRadius) {
|
|
219
|
-
const force = (1 - mdist / attractRadius) * attractStrength;
|
|
220
|
-
p.vx += mdx * force;
|
|
221
|
-
p.vy += mdy * force;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
p.vx *= 0.98;
|
|
225
|
-
p.vy *= 0.98;
|
|
226
|
-
|
|
227
|
-
p.x += p.vx;
|
|
228
|
-
p.y += p.vy;
|
|
229
|
-
|
|
230
|
-
if (p.x < 0) p.x = width;
|
|
231
|
-
if (p.x > width) p.x = 0;
|
|
232
|
-
if (p.y < 0) p.y = height;
|
|
233
|
-
if (p.y > height) p.y = 0;
|
|
234
|
-
|
|
235
|
-
const renderY = (((p.y + parallaxY * p.depth) % height) + height) % height;
|
|
236
|
-
renderPositions[i] = { x: p.x, y: renderY };
|
|
237
|
-
|
|
238
|
-
const twinkle = 0.6 + 0.4 * Math.sin(time * p.twinkleSpeed + p.twinklePhase);
|
|
239
|
-
const radius = 0.6 + p.depth * 1.4;
|
|
240
|
-
|
|
241
|
-
context.beginPath();
|
|
242
|
-
context.arc(p.x, renderY, radius, 0, Math.PI * 2);
|
|
243
|
-
context.fillStyle = `rgba(${resolvedPalette.particle},${p.opacity * twinkle})`;
|
|
244
|
-
context.fill();
|
|
245
|
-
|
|
246
|
-
for (let j = i + 1; j < ps.length; j++) {
|
|
247
|
-
const q = ps[j];
|
|
248
|
-
const qRender = renderPositions[j] ?? { x: q.x, y: q.y };
|
|
249
|
-
const dx = p.x - q.x;
|
|
250
|
-
const dy = renderY - qRender.y;
|
|
251
|
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
252
|
-
|
|
253
|
-
if (dist < maxLinkDistance) {
|
|
254
|
-
const depthFactor = (p.depth + q.depth) / 2;
|
|
255
|
-
|
|
256
|
-
// Links are more transparent in dark mode to reduce contrast and visual noise, and slightly more opaque in light mode to add some subtlety.
|
|
257
|
-
const alpha =
|
|
258
|
-
(1 - dist / maxLinkDistance) *
|
|
259
|
-
(resolvedTheme === "dark" ? 0.015 : 0.01) *
|
|
260
|
-
depthFactor;
|
|
261
|
-
context.beginPath();
|
|
262
|
-
context.moveTo(p.x, renderY);
|
|
263
|
-
context.lineTo(qRender.x, qRender.y);
|
|
264
|
-
context.strokeStyle = `rgba(${resolvedPalette.link},${alpha})`;
|
|
265
|
-
context.lineWidth = 0.5;
|
|
266
|
-
context.stroke();
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (isRunning) {
|
|
272
|
-
raf.current = requestAnimationFrame(draw);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function syncRunning() {
|
|
277
|
-
const shouldRun = inView && tabVisible;
|
|
278
|
-
|
|
279
|
-
if (shouldRun && !isRunning) {
|
|
280
|
-
isRunning = true;
|
|
281
|
-
raf.current = requestAnimationFrame(draw);
|
|
282
|
-
} else if (!shouldRun && isRunning) {
|
|
283
|
-
isRunning = false;
|
|
284
|
-
cancelAnimationFrame(raf.current);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Resizing the canvas element clears its pixels, so repaint a frame
|
|
289
|
-
// whenever the loop isn't already running to redraw for it.
|
|
290
|
-
function handleResize() {
|
|
291
|
-
resize();
|
|
292
|
-
if (!isRunning) {
|
|
293
|
-
draw(performance.now());
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const ro = new ResizeObserver(handleResize);
|
|
298
|
-
ro.observe(canvas);
|
|
299
|
-
resize();
|
|
300
|
-
// Paint one frame immediately so the canvas is never left blank while
|
|
301
|
-
// waiting on the intersection/visibility observers to report in.
|
|
302
|
-
draw(performance.now());
|
|
303
|
-
syncRunning();
|
|
304
|
-
|
|
305
|
-
const onMouseMove = (event: MouseEvent) => {
|
|
306
|
-
const rect = canvas.getBoundingClientRect();
|
|
307
|
-
mouse.current = { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
const onMouseLeave = () => {
|
|
311
|
-
mouse.current = { x: -9999, y: -9999 };
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
if (interactive) {
|
|
315
|
-
canvas.addEventListener("mousemove", onMouseMove);
|
|
316
|
-
canvas.addEventListener("mouseleave", onMouseLeave);
|
|
317
|
-
document.addEventListener("mousemove", onMouseMove);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const onScroll = () => {
|
|
321
|
-
scrollOffset.current = window.scrollY;
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
window.addEventListener("scroll", onScroll, { passive: true });
|
|
325
|
-
|
|
326
|
-
const io = new IntersectionObserver(
|
|
327
|
-
([entry]) => {
|
|
328
|
-
inView = entry?.isIntersecting ?? true;
|
|
329
|
-
syncRunning();
|
|
330
|
-
},
|
|
331
|
-
{ threshold: 0 },
|
|
332
|
-
);
|
|
333
|
-
io.observe(canvas);
|
|
334
|
-
|
|
335
|
-
const onVisibilityChange = () => {
|
|
336
|
-
tabVisible = !document.hidden;
|
|
337
|
-
syncRunning();
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
341
|
-
|
|
342
|
-
return () => {
|
|
343
|
-
isRunning = false;
|
|
344
|
-
cancelAnimationFrame(raf.current);
|
|
345
|
-
ro.disconnect();
|
|
346
|
-
io.disconnect();
|
|
347
|
-
document.removeEventListener("mousemove", onMouseMove);
|
|
348
|
-
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
349
|
-
canvas.removeEventListener("mousemove", onMouseMove);
|
|
350
|
-
canvas.removeEventListener("mouseleave", onMouseLeave);
|
|
351
|
-
window.removeEventListener("scroll", onScroll);
|
|
352
|
-
};
|
|
353
|
-
}, [
|
|
354
|
-
attractRadius,
|
|
355
|
-
attractStrength,
|
|
356
|
-
enabled,
|
|
357
|
-
interactive,
|
|
358
|
-
maxLinkDistance,
|
|
359
|
-
resolvedPalette,
|
|
360
|
-
particleCount,
|
|
361
|
-
particleDensity,
|
|
362
|
-
prefersReduced,
|
|
363
|
-
resolvedTheme,
|
|
364
|
-
velocity,
|
|
365
|
-
]);
|
|
366
|
-
|
|
367
|
-
if (!enabled || prefersReduced) return null;
|
|
368
|
-
|
|
369
|
-
return (
|
|
370
|
-
<div
|
|
371
|
-
className={cn("pointer-events-none absolute inset-0 overflow-hidden", className)}
|
|
372
|
-
aria-hidden="true"
|
|
373
|
-
{...props}
|
|
374
|
-
>
|
|
375
|
-
<canvas ref={canvasRef} className="absolute inset-0 h-full w-full" />
|
|
376
|
-
</div>
|
|
377
|
-
);
|
|
378
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
import { ScrollArea } from "./scroll-area";
|
|
3
|
-
|
|
4
|
-
const meta = {
|
|
5
|
-
title: "Layout/ScrollArea",
|
|
6
|
-
component: ScrollArea,
|
|
7
|
-
tags: ["autodocs"],
|
|
8
|
-
} satisfies Meta<typeof ScrollArea>;
|
|
9
|
-
|
|
10
|
-
export default meta;
|
|
11
|
-
type Story = StoryObj<typeof meta>;
|
|
12
|
-
|
|
13
|
-
const placeholderItems = Array.from({ length: 8 }, (_, i) => ({
|
|
14
|
-
id: i + 1,
|
|
15
|
-
label: `Item ${i + 1}`,
|
|
16
|
-
bg: `hsl(${i * 45}, 70%, 80%)`,
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
export const Default: Story = {
|
|
20
|
-
args: {
|
|
21
|
-
className: "gap-4 p-4 max-w-lg",
|
|
22
|
-
children: placeholderItems.map((item) => (
|
|
23
|
-
<div
|
|
24
|
-
key={item.id}
|
|
25
|
-
className="flex size-40 shrink-0 items-center justify-center rounded-lg"
|
|
26
|
-
style={{ backgroundColor: item.bg }}
|
|
27
|
-
>
|
|
28
|
-
<span className="font-medium" style={{ color: "#171714" }}>
|
|
29
|
-
{item.label}
|
|
30
|
-
</span>
|
|
31
|
-
</div>
|
|
32
|
-
)),
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export const WithSnap: Story = {
|
|
37
|
-
args: {
|
|
38
|
-
className: "gap-4 p-4 max-w-lg",
|
|
39
|
-
snap: "start",
|
|
40
|
-
children: placeholderItems.map((item) => (
|
|
41
|
-
<div
|
|
42
|
-
key={item.id}
|
|
43
|
-
className="flex size-48 shrink-0 items-center justify-center rounded-lg"
|
|
44
|
-
style={{ backgroundColor: item.bg }}
|
|
45
|
-
>
|
|
46
|
-
<span className="font-medium" style={{ color: "#171714" }}>
|
|
47
|
-
{item.label}
|
|
48
|
-
</span>
|
|
49
|
-
</div>
|
|
50
|
-
)),
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export const Vertical: Story = {
|
|
55
|
-
args: {
|
|
56
|
-
orientation: "vertical",
|
|
57
|
-
className: "gap-3 p-4 max-h-64",
|
|
58
|
-
children: placeholderItems.map((item) => (
|
|
59
|
-
<div
|
|
60
|
-
key={item.id}
|
|
61
|
-
className="flex h-24 shrink-0 items-center justify-center rounded-lg"
|
|
62
|
-
style={{ backgroundColor: item.bg }}
|
|
63
|
-
>
|
|
64
|
-
<span className="font-medium" style={{ color: "#171714" }}>
|
|
65
|
-
{item.label}
|
|
66
|
-
</span>
|
|
67
|
-
</div>
|
|
68
|
-
)),
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export const ScreenshotsGallery: Story = {
|
|
73
|
-
render: () => (
|
|
74
|
-
<div className="flex max-w-2xl flex-col gap-3">
|
|
75
|
-
<h2 className="text-lg font-semibold">Screenshots</h2>
|
|
76
|
-
<ScrollArea className="gap-4" snap="start">
|
|
77
|
-
{placeholderItems.map((item) => (
|
|
78
|
-
<div
|
|
79
|
-
key={item.id}
|
|
80
|
-
className="flex aspect-video w-72 shrink-0 items-center justify-center overflow-hidden rounded-xl border"
|
|
81
|
-
style={{ backgroundColor: item.bg }}
|
|
82
|
-
>
|
|
83
|
-
<span className="text-sm" style={{ color: "#171714" }}>
|
|
84
|
-
{item.label}
|
|
85
|
-
</span>
|
|
86
|
-
</div>
|
|
87
|
-
))}
|
|
88
|
-
</ScrollArea>
|
|
89
|
-
</div>
|
|
90
|
-
),
|
|
91
|
-
};
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
|
|
4
|
-
import { cn } from "../../lib/utils";
|
|
5
|
-
|
|
6
|
-
interface ScrollAreaProps extends React.ComponentProps<typeof ScrollAreaPrimitive.Root> {
|
|
7
|
-
orientation?: "horizontal" | "vertical";
|
|
8
|
-
snap?: "start" | "center" | "none";
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Full literal class names, not `snap-${snap}` — Tailwind's scanner only
|
|
13
|
-
* picks up complete strings present in the source, so an interpolated class
|
|
14
|
-
* name never generates any CSS.
|
|
15
|
-
*/
|
|
16
|
-
const SNAP_ALIGN_CLASS_NAME = {
|
|
17
|
-
start: "snap-start",
|
|
18
|
-
center: "snap-center",
|
|
19
|
-
none: undefined,
|
|
20
|
-
} as const satisfies Record<NonNullable<ScrollAreaProps["snap"]>, string | undefined>;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Overlay scrollbar: invisible at rest, fades in on hover/scroll and back
|
|
24
|
-
* out on idle (data-hovering / data-scrolling from Base UI). Opacity-only,
|
|
25
|
-
* transitions (not keyframes) since hover/scroll toggle rapidly.
|
|
26
|
-
*/
|
|
27
|
-
const scrollbarClassName = cn(
|
|
28
|
-
"flex touch-none p-0.5 opacity-0 transition-opacity duration-150 ease-out",
|
|
29
|
-
"data-[hovering]:opacity-100 data-[scrolling]:opacity-100",
|
|
30
|
-
"data-[orientation=vertical]:w-2.5",
|
|
31
|
-
"data-[orientation=horizontal]:h-2.5 data-[orientation=horizontal]:flex-col",
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(
|
|
35
|
-
({ className, children, orientation = "horizontal", snap = "none", ...props }, ref) => {
|
|
36
|
-
return (
|
|
37
|
-
<ScrollAreaPrimitive.Root
|
|
38
|
-
ref={ref}
|
|
39
|
-
className={cn(
|
|
40
|
-
"relative flex overflow-hidden",
|
|
41
|
-
orientation === "horizontal" ? "flex-row" : "flex-col",
|
|
42
|
-
className,
|
|
43
|
-
)}
|
|
44
|
-
{...props}
|
|
45
|
-
>
|
|
46
|
-
<ScrollAreaPrimitive.Viewport
|
|
47
|
-
className={cn(
|
|
48
|
-
"scrollbar-hide flex overflow-auto",
|
|
49
|
-
orientation === "horizontal" ? "min-w-0 flex-1 flex-row" : "min-h-0 flex-1 flex-col",
|
|
50
|
-
snap !== "none" && (orientation === "horizontal" ? "snap-x" : "snap-y"),
|
|
51
|
-
snap !== "none" && "snap-mandatory",
|
|
52
|
-
)}
|
|
53
|
-
>
|
|
54
|
-
{React.Children.map(children, (child) => {
|
|
55
|
-
if (!React.isValidElement(child)) return child;
|
|
56
|
-
return React.cloneElement(child as React.ReactElement<{ className?: string }>, {
|
|
57
|
-
className: cn(
|
|
58
|
-
SNAP_ALIGN_CLASS_NAME[snap],
|
|
59
|
-
(child.props as { className?: string }).className,
|
|
60
|
-
),
|
|
61
|
-
});
|
|
62
|
-
})}
|
|
63
|
-
</ScrollAreaPrimitive.Viewport>
|
|
64
|
-
<ScrollAreaPrimitive.Scrollbar orientation="horizontal" className={scrollbarClassName}>
|
|
65
|
-
<ScrollAreaPrimitive.Thumb className="bg-foreground/40 hover:bg-foreground/60 relative flex-1 rounded-full transition-colors duration-150 ease" />
|
|
66
|
-
</ScrollAreaPrimitive.Scrollbar>
|
|
67
|
-
<ScrollAreaPrimitive.Scrollbar orientation="vertical" className={scrollbarClassName}>
|
|
68
|
-
<ScrollAreaPrimitive.Thumb className="bg-foreground/40 hover:bg-foreground/60 relative flex-1 rounded-full transition-colors duration-150 ease" />
|
|
69
|
-
</ScrollAreaPrimitive.Scrollbar>
|
|
70
|
-
<ScrollAreaPrimitive.Corner />
|
|
71
|
-
</ScrollAreaPrimitive.Root>
|
|
72
|
-
);
|
|
73
|
-
},
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
ScrollArea.displayName = "ScrollArea";
|
|
77
|
-
|
|
78
|
-
export { ScrollArea };
|
|
79
|
-
export type { ScrollAreaProps };
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
|
|
3
|
-
import { Button } from "../primitives/button";
|
|
4
|
-
import { SectionIntro } from "./section-intro";
|
|
5
|
-
|
|
6
|
-
const meta = {
|
|
7
|
-
title: "Layout/SectionIntro",
|
|
8
|
-
component: SectionIntro,
|
|
9
|
-
tags: ["autodocs"],
|
|
10
|
-
} satisfies Meta<typeof SectionIntro>;
|
|
11
|
-
|
|
12
|
-
export default meta;
|
|
13
|
-
type Story = StoryObj<typeof meta>;
|
|
14
|
-
|
|
15
|
-
export const Default: Story = {
|
|
16
|
-
args: {
|
|
17
|
-
title: "Accounts",
|
|
18
|
-
description: "Connect and manage the accounts you track.",
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export const WithEyebrowAndActions: Story = {
|
|
23
|
-
args: {
|
|
24
|
-
eyebrow: "Settings",
|
|
25
|
-
title: "Security",
|
|
26
|
-
description: "Manage how you sign in and keep your account secure.",
|
|
27
|
-
actions: <Button variant="outline">Add passkey</Button>,
|
|
28
|
-
},
|
|
29
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
export interface SectionIntroProps {
|
|
4
|
-
eyebrow?: ReactNode;
|
|
5
|
-
title: ReactNode;
|
|
6
|
-
description?: ReactNode;
|
|
7
|
-
actions?: ReactNode;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Page-level heading composition: eyebrow label, title, description, and an
|
|
12
|
-
* optional trailing actions slot.
|
|
13
|
-
*/
|
|
14
|
-
export function SectionIntro({ eyebrow, title, description, actions }: SectionIntroProps) {
|
|
15
|
-
return (
|
|
16
|
-
<div className="flex items-start justify-between gap-6">
|
|
17
|
-
<div className="min-w-0 flex-1">
|
|
18
|
-
{eyebrow ? (
|
|
19
|
-
<div className="text-muted-foreground mb-3 text-[11px] font-medium tracking-[0.18em] uppercase">
|
|
20
|
-
{eyebrow}
|
|
21
|
-
</div>
|
|
22
|
-
) : null}
|
|
23
|
-
<div className="space-y-3">
|
|
24
|
-
<h1 className="text-2xl text-foreground font-semibold tracking-tight">{title}</h1>
|
|
25
|
-
{description ? <p className="text-muted-foreground text-sm">{description}</p> : null}
|
|
26
|
-
</div>
|
|
27
|
-
</div>
|
|
28
|
-
{actions ? <div className="shrink-0">{actions}</div> : null}
|
|
29
|
-
</div>
|
|
30
|
-
);
|
|
31
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
|
|
3
|
-
import { SurfacePanel } from "./surface-panel";
|
|
4
|
-
|
|
5
|
-
const meta = {
|
|
6
|
-
title: "Layout/SurfacePanel",
|
|
7
|
-
component: SurfacePanel,
|
|
8
|
-
tags: ["autodocs"],
|
|
9
|
-
} satisfies Meta<typeof SurfacePanel>;
|
|
10
|
-
|
|
11
|
-
export default meta;
|
|
12
|
-
type Story = StoryObj<typeof meta>;
|
|
13
|
-
|
|
14
|
-
export const Default: Story = {
|
|
15
|
-
render: () => (
|
|
16
|
-
<div className="w-80">
|
|
17
|
-
<SurfacePanel>
|
|
18
|
-
<p className="text-muted-foreground text-sm">Panel content goes here.</p>
|
|
19
|
-
</SurfacePanel>
|
|
20
|
-
</div>
|
|
21
|
-
),
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const AsSection: Story = {
|
|
25
|
-
render: () => (
|
|
26
|
-
<div className="w-80">
|
|
27
|
-
<SurfacePanel as="section" aria-label="Example panel">
|
|
28
|
-
<p className="text-muted-foreground text-sm">Rendered as a semantic <section>.</p>
|
|
29
|
-
</SurfacePanel>
|
|
30
|
-
</div>
|
|
31
|
-
),
|
|
32
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { ComponentPropsWithoutRef, ElementType } from "react";
|
|
2
|
-
|
|
3
|
-
import { cn } from "../../lib/utils";
|
|
4
|
-
|
|
5
|
-
type SurfacePanelProps<T extends ElementType = "div"> = { as?: T } & Omit<
|
|
6
|
-
ComponentPropsWithoutRef<T>,
|
|
7
|
-
"as"
|
|
8
|
-
>;
|
|
9
|
-
|
|
10
|
-
/** Rounded, bordered surface panel — a generic content container. */
|
|
11
|
-
export function SurfacePanel<T extends ElementType = "div">({
|
|
12
|
-
as,
|
|
13
|
-
className,
|
|
14
|
-
...props
|
|
15
|
-
}: SurfacePanelProps<T>) {
|
|
16
|
-
const Component = as ?? "div";
|
|
17
|
-
return (
|
|
18
|
-
<Component
|
|
19
|
-
className={cn("border-border bg-card rounded-3xl border p-5", className)}
|
|
20
|
-
{...props}
|
|
21
|
-
/>
|
|
22
|
-
);
|
|
23
|
-
}
|