@olwiba/ui 0.2.25 → 0.2.27
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/content/docs/app/app-shell.mdx +71 -0
- package/content/docs/app/auth-section.mdx +73 -0
- package/content/docs/app/billing-panel.mdx +38 -0
- package/content/docs/app/empty-state.mdx +47 -0
- package/content/docs/app/error-page.mdx +31 -0
- package/content/docs/app/index.mdx +13 -0
- package/content/docs/app/meta.json +4 -0
- package/content/docs/app/onboarding-wizard.mdx +41 -0
- package/content/docs/app/settings-section.mdx +37 -0
- package/content/docs/app/team-members-panel.mdx +35 -0
- package/content/docs/app/update-banner.mdx +43 -0
- package/content/docs/components/activity-feed.mdx +42 -0
- package/content/docs/components/chart.mdx +47 -0
- package/content/docs/components/command-menu.mdx +41 -0
- package/content/docs/components/confirm-dialog.mdx +26 -0
- package/content/docs/components/context-menu.mdx +26 -0
- package/content/docs/components/data-table.mdx +40 -0
- package/content/docs/components/dock.mdx +26 -0
- package/content/docs/components/feature-card.mdx +26 -0
- package/content/docs/components/file-upload.mdx +35 -0
- package/content/docs/components/full-page-spinner.mdx +44 -0
- package/content/docs/components/glass-card.mdx +26 -0
- package/content/docs/components/image-card.mdx +26 -0
- package/content/docs/components/index.mdx +49 -0
- package/content/docs/components/load-more.mdx +34 -0
- package/content/docs/components/meta.json +35 -0
- package/content/docs/components/notifications-popover.mdx +34 -0
- package/content/docs/components/notify.mdx +40 -0
- package/content/docs/components/olwiba-ui-context.mdx +69 -0
- package/content/docs/components/page-header.mdx +42 -0
- package/content/docs/components/pricing-card.mdx +26 -0
- package/content/docs/components/register-hotkeys.mdx +50 -0
- package/content/docs/components/root-error-fallback.mdx +39 -0
- package/content/docs/components/spotlight.mdx +26 -0
- package/content/docs/components/stat-card.mdx +26 -0
- package/content/docs/components/suspensed.mdx +42 -0
- package/content/docs/components/testimonial-card.mdx +26 -0
- package/content/docs/components/theme-color-updater.mdx +40 -0
- package/content/docs/components/theme-switch-minimal.mdx +32 -0
- package/content/docs/components/version-banner.mdx +44 -0
- package/content/docs/email/action-email.mdx +62 -0
- package/content/docs/email/email-layout.mdx +58 -0
- package/content/docs/email/email-link-fallback.mdx +46 -0
- package/content/docs/email/index.mdx +32 -0
- package/content/docs/email/meta.json +10 -0
- package/content/docs/email/notice-email.mdx +51 -0
- package/content/docs/hooks/index.mdx +21 -0
- package/content/docs/hooks/meta.json +16 -0
- package/content/docs/hooks/use-confirm.mdx +81 -0
- package/content/docs/hooks/use-controlled-open.mdx +53 -0
- package/content/docs/hooks/use-copy-to-clipboard.mdx +26 -0
- package/content/docs/hooks/use-debounce.mdx +26 -0
- package/content/docs/hooks/use-intersection-observer.mdx +26 -0
- package/content/docs/hooks/use-local-storage.mdx +26 -0
- package/content/docs/hooks/use-media-query.mdx +26 -0
- package/content/docs/hooks/use-mounted.mdx +39 -0
- package/content/docs/hooks/use-pagination.mdx +26 -0
- package/content/docs/hooks/use-scrolled-past.mdx +44 -0
- package/content/docs/index.mdx +68 -0
- package/content/docs/marketing/contact-section.mdx +16 -0
- package/content/docs/marketing/cta-section.mdx +31 -0
- package/content/docs/marketing/faq-section.mdx +16 -0
- package/content/docs/marketing/features-section.mdx +23 -0
- package/content/docs/marketing/footer.mdx +16 -0
- package/content/docs/marketing/hero-section.mdx +16 -0
- package/content/docs/marketing/index.mdx +22 -0
- package/content/docs/marketing/meta.json +20 -0
- package/content/docs/marketing/navbar.mdx +16 -0
- package/content/docs/marketing/newsletter-section.mdx +16 -0
- package/content/docs/marketing/post-list.mdx +32 -0
- package/content/docs/marketing/pricing-section.mdx +16 -0
- package/content/docs/marketing/stats-section.mdx +16 -0
- package/content/docs/marketing/steps-section.mdx +60 -0
- package/content/docs/marketing/team-section.mdx +16 -0
- package/content/docs/marketing/testimonials-section.mdx +16 -0
- package/content/docs/mechanics/carousel.mdx +42 -0
- package/content/docs/mechanics/index.mdx +11 -0
- package/content/docs/mechanics/meta.json +4 -0
- package/content/docs/mechanics/sortable.mdx +44 -0
- package/content/docs/meta.json +4 -0
- package/content/docs/motion/animated-swap.mdx +75 -0
- package/content/docs/motion/count-up.mdx +31 -0
- package/content/docs/motion/fade-in.mdx +34 -0
- package/content/docs/motion/index.mdx +12 -0
- package/content/docs/motion/meta.json +4 -0
- package/content/docs/motion/page-transition.mdx +29 -0
- package/content/docs/motion/stagger-children.mdx +31 -0
- package/content/docs/overlays/index.mdx +9 -0
- package/content/docs/overlays/meta.json +4 -0
- package/content/docs/overlays/overlay.mdx +60 -0
- package/content/docs/overlays/underlay.mdx +56 -0
- package/dist/email/index.d.ts +8 -1
- package/dist/email/index.js +3 -3
- package/dist/email/index.js.map +1 -1
- package/docs-manifest.json +253 -0
- package/package.json +18 -9
- package/site/demos/action-email.tsx +6 -0
- package/site/demos/activity-feed.tsx +42 -0
- package/site/demos/animated-swap.tsx +42 -0
- package/site/demos/app-shell.tsx +47 -0
- package/site/demos/app-ui-page.tsx +9 -0
- package/site/demos/application-sidebar-block.tsx +9 -0
- package/site/demos/auth-section.tsx +15 -0
- package/site/demos/auth-split-block.tsx +9 -0
- package/site/demos/billing-page.tsx +157 -0
- package/site/demos/billing-panel.tsx +26 -0
- package/site/demos/carousel.tsx +34 -0
- package/site/demos/chart.tsx +55 -0
- package/site/demos/command-menu.tsx +39 -0
- package/site/demos/confirm-dialog.tsx +27 -0
- package/site/demos/contact-form-block.tsx +3 -0
- package/site/demos/contact-section.tsx +2 -0
- package/site/demos/context-menu.tsx +30 -0
- package/site/demos/count-up.tsx +23 -0
- package/site/demos/cta-section.tsx +6 -0
- package/site/demos/dashboard-overview-block.tsx +9 -0
- package/site/demos/dashboard-shell-block.tsx +10 -0
- package/site/demos/data-table.tsx +47 -0
- package/site/demos/demo-brand.tsx +23 -0
- package/site/demos/dock.tsx +20 -0
- package/site/demos/document-sidebar-block.tsx +9 -0
- package/site/demos/email-examples.tsx +58 -0
- package/site/demos/email-frame.tsx +50 -0
- package/site/demos/email-layout.tsx +6 -0
- package/site/demos/email-link-fallback.tsx +6 -0
- package/site/demos/email-previews.generated.ts +11 -0
- package/site/demos/empty-state-block.tsx +3 -0
- package/site/demos/empty-state.tsx +23 -0
- package/site/demos/error-page.tsx +5 -0
- package/site/demos/fade-in.tsx +18 -0
- package/site/demos/faq-section.tsx +6 -0
- package/site/demos/feature-card.tsx +13 -0
- package/site/demos/features-section.tsx +6 -0
- package/site/demos/file-upload.tsx +16 -0
- package/site/demos/footer.tsx +42 -0
- package/site/demos/full-page-spinner.tsx +9 -0
- package/site/demos/glass-card.tsx +14 -0
- package/site/demos/glow-effect.tsx +3 -0
- package/site/demos/gradient-background.tsx +3 -0
- package/site/demos/grid-pattern.tsx +3 -0
- package/site/demos/hero-section.tsx +6 -0
- package/site/demos/image-card.tsx +17 -0
- package/site/demos/login-block.tsx +9 -0
- package/site/demos/marketing-cta-block.tsx +3 -0
- package/site/demos/marketing-demo-data.tsx +114 -0
- package/site/demos/marketing-faq-block.tsx +3 -0
- package/site/demos/marketing-features-block.tsx +3 -0
- package/site/demos/marketing-footer-block.tsx +3 -0
- package/site/demos/marketing-hero-block.tsx +10 -0
- package/site/demos/marketing-nav-block.tsx +3 -0
- package/site/demos/marketing-newsletter-block.tsx +3 -0
- package/site/demos/marketing-page.tsx +52 -0
- package/site/demos/marketing-pricing-block.tsx +3 -0
- package/site/demos/marketing-stats-block.tsx +3 -0
- package/site/demos/marketing-team-block.tsx +3 -0
- package/site/demos/marketing-testimonials-block.tsx +3 -0
- package/site/demos/navbar.tsx +32 -0
- package/site/demos/newsletter-section.tsx +2 -0
- package/site/demos/noise-overlay.tsx +3 -0
- package/site/demos/not-found-block.tsx +3 -0
- package/site/demos/notice-email.tsx +6 -0
- package/site/demos/notifications-popover.tsx +27 -0
- package/site/demos/notify.tsx +44 -0
- package/site/demos/olwiba-ui-context.tsx +51 -0
- package/site/demos/onboarding-block.tsx +3 -0
- package/site/demos/onboarding-wizard.tsx +43 -0
- package/site/demos/overlay.tsx +72 -0
- package/site/demos/page-header.tsx +15 -0
- package/site/demos/page-transition.tsx +34 -0
- package/site/demos/post-list.tsx +24 -0
- package/site/demos/pricing-card.tsx +27 -0
- package/site/demos/pricing-section.tsx +6 -0
- package/site/demos/register-hotkeys.tsx +39 -0
- package/site/demos/root-error-fallback.tsx +28 -0
- package/site/demos/settings-layout-block.tsx +3 -0
- package/site/demos/settings-section.tsx +22 -0
- package/site/demos/sign-in-page.tsx +10 -0
- package/site/demos/sortable.tsx +30 -0
- package/site/demos/spotlight.tsx +29 -0
- package/site/demos/stagger-children.tsx +16 -0
- package/site/demos/stat-card.tsx +12 -0
- package/site/demos/stats-section.tsx +6 -0
- package/site/demos/steps-section.tsx +66 -0
- package/site/demos/suspensed.tsx +31 -0
- package/site/demos/team-members-panel.tsx +26 -0
- package/site/demos/team-section.tsx +2 -0
- package/site/demos/testimonial-card.tsx +17 -0
- package/site/demos/testimonials-section.tsx +6 -0
- package/site/demos/theme-color-updater.tsx +32 -0
- package/site/demos/theme-switch-minimal.tsx +10 -0
- package/site/demos/underlay.tsx +74 -0
- package/site/demos/update-banner.tsx +31 -0
- package/site/demos/use-copy-to-clipboard.tsx +21 -0
- package/site/demos/use-debounce.tsx +25 -0
- package/site/demos/use-intersection-observer.tsx +32 -0
- package/site/demos/use-local-storage.tsx +22 -0
- package/site/demos/use-media-query.tsx +31 -0
- package/site/demos/use-pagination.tsx +31 -0
- package/site/demos/user-profile-block.tsx +3 -0
- package/site/demos/version-banner.tsx +27 -0
- package/site/lib/sandboxes.ts +1647 -0
- package/src/email/EmailLinkFallback.tsx +7 -1
- package/src/email/index.ts +3 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Overlay, type OverlayVariant } from '@olwiba/ui';
|
|
5
|
+
import { DemoControls, useUsageCode } from '~/components/ComponentPreview';
|
|
6
|
+
import { Button, Badge } from '@olwiba/cn';
|
|
7
|
+
|
|
8
|
+
const variants: OverlayVariant[] = ['dim', 'scrim', 'loading'];
|
|
9
|
+
|
|
10
|
+
function MockContent() {
|
|
11
|
+
return (
|
|
12
|
+
<div className="relative z-10 flex w-full flex-col gap-4 p-8">
|
|
13
|
+
<div className="flex items-center justify-between">
|
|
14
|
+
<div>
|
|
15
|
+
<p className="text-xs text-muted-foreground">Total revenue</p>
|
|
16
|
+
<p className="text-2xl font-semibold">$48,295</p>
|
|
17
|
+
</div>
|
|
18
|
+
<Badge variant="secondary">+12.4%</Badge>
|
|
19
|
+
</div>
|
|
20
|
+
<div className="flex gap-3">
|
|
21
|
+
{['Mon', 'Tue', 'Wed', 'Thu', 'Fri'].map((d, i) => (
|
|
22
|
+
<div key={d} className="flex flex-1 flex-col items-center gap-1">
|
|
23
|
+
<div
|
|
24
|
+
className="w-full rounded bg-primary/20"
|
|
25
|
+
style={{ height: `${[40, 65, 50, 80, 55][i]}px` }}
|
|
26
|
+
/>
|
|
27
|
+
<span className="text-[10px] text-muted-foreground">{d}</span>
|
|
28
|
+
</div>
|
|
29
|
+
))}
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default function OverlayDemo() {
|
|
36
|
+
const [variant, setVariant] = React.useState<OverlayVariant>('loading');
|
|
37
|
+
|
|
38
|
+
useUsageCode(
|
|
39
|
+
`<div className="relative overflow-hidden rounded-xl">\n {/* content */}\n <Overlay variant="${variant}"${variant === 'loading' ? ' label="Loading..."' : ''} />\n</div>`,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<>
|
|
44
|
+
<div className="relative w-full overflow-hidden bg-card">
|
|
45
|
+
<MockContent />
|
|
46
|
+
<Overlay
|
|
47
|
+
key={variant}
|
|
48
|
+
variant={variant}
|
|
49
|
+
{...(variant === 'loading' ? { label: 'Loading...' } : {})}
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<DemoControls>
|
|
54
|
+
<div className="space-y-1.5">
|
|
55
|
+
<span className="text-xs font-medium text-muted-foreground">Variant</span>
|
|
56
|
+
<div className="flex flex-wrap gap-1.5">
|
|
57
|
+
{variants.map((v) => (
|
|
58
|
+
<Button
|
|
59
|
+
key={v}
|
|
60
|
+
variant={variant === v ? 'default' : 'secondary'}
|
|
61
|
+
size="sm"
|
|
62
|
+
onClick={() => setVariant(v)}
|
|
63
|
+
>
|
|
64
|
+
{v}
|
|
65
|
+
</Button>
|
|
66
|
+
))}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</DemoControls>
|
|
70
|
+
</>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PageHeader, Button } from '@olwiba/ui';
|
|
2
|
+
|
|
3
|
+
export default function PageHeaderDemo() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="w-full max-w-lg rounded-lg border border-dashed p-6">
|
|
6
|
+
<PageHeader
|
|
7
|
+
title="Dashboard"
|
|
8
|
+
description="Manage your projects and settings."
|
|
9
|
+
>
|
|
10
|
+
<Button size="sm">New Project</Button>
|
|
11
|
+
</PageHeader>
|
|
12
|
+
<div className="h-8 rounded bg-muted" />
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { PageTransition } from '@olwiba/ui';
|
|
4
|
+
import { Button } from '@olwiba/cn';
|
|
5
|
+
|
|
6
|
+
const variants = ['fade', 'slide-up', 'slide-down'] as const;
|
|
7
|
+
type Variant = typeof variants[number];
|
|
8
|
+
|
|
9
|
+
export default function Demo() {
|
|
10
|
+
const [key, setKey] = React.useState(0);
|
|
11
|
+
const [variant, setVariant] = React.useState<Variant>('slide-up');
|
|
12
|
+
|
|
13
|
+
function play(v: Variant) {
|
|
14
|
+
setVariant(v);
|
|
15
|
+
setKey((k) => k + 1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex flex-col items-center gap-6 p-8">
|
|
20
|
+
<div className="flex flex-wrap justify-center gap-2">
|
|
21
|
+
{variants.map((v) => (
|
|
22
|
+
<Button key={v} size="sm" variant={variant === v ? 'default' : 'outline'} onClick={() => play(v)}>
|
|
23
|
+
{v}
|
|
24
|
+
</Button>
|
|
25
|
+
))}
|
|
26
|
+
</div>
|
|
27
|
+
<PageTransition key={key} variant={variant} duration={400} className="w-full max-w-xs rounded-2xl border bg-card p-6 text-center">
|
|
28
|
+
<p className="font-semibold">Animated content</p>
|
|
29
|
+
<p className="mt-1 text-sm text-muted-foreground">Using <strong>{variant}</strong></p>
|
|
30
|
+
</PageTransition>
|
|
31
|
+
<Button variant="ghost" size="sm" onClick={() => setKey((k) => k + 1)}>↺ Replay</Button>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PostList } from '@olwiba/ui';
|
|
2
|
+
|
|
3
|
+
const posts = [
|
|
4
|
+
{
|
|
5
|
+
title: 'A calmer path from idea to release',
|
|
6
|
+
description:
|
|
7
|
+
'How a small set of product conventions helps teams ship without redesigning every page.',
|
|
8
|
+
date: '2026-08-24',
|
|
9
|
+
slug: 'calmer-path-to-release',
|
|
10
|
+
tags: ['Product'],
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
title: 'Designing useful defaults',
|
|
14
|
+
description:
|
|
15
|
+
'Why good component defaults remove decisions while preserving deliberate escape hatches.',
|
|
16
|
+
date: '2026-08-12',
|
|
17
|
+
slug: 'designing-useful-defaults',
|
|
18
|
+
tags: ['Design systems'],
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export default function Demo() {
|
|
23
|
+
return <PostList posts={posts} />;
|
|
24
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { PricingCard } from '@olwiba/ui';
|
|
3
|
+
|
|
4
|
+
const free = [
|
|
5
|
+
{ label: '3 projects', included: true },
|
|
6
|
+
{ label: 'Basic analytics', included: true },
|
|
7
|
+
{ label: 'Community support', included: true },
|
|
8
|
+
{ label: 'Custom domains', included: false },
|
|
9
|
+
{ label: 'Priority support', included: false },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const pro = [
|
|
13
|
+
{ label: 'Unlimited projects', included: true },
|
|
14
|
+
{ label: 'Advanced analytics', included: true },
|
|
15
|
+
{ label: 'Priority support', included: true },
|
|
16
|
+
{ label: 'Custom domains', included: true },
|
|
17
|
+
{ label: 'SLA guarantee', included: true },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export default function Demo() {
|
|
21
|
+
return (
|
|
22
|
+
<div className="grid gap-4 p-6 sm:grid-cols-2">
|
|
23
|
+
<PricingCard name="Starter" price="Free" period="" description="For personal projects and exploration." features={free} cta="Get started" />
|
|
24
|
+
<PricingCard name="Pro" price="£15" description="For teams shipping production apps." features={pro} cta="Upgrade to Pro" highlighted badge="Most popular" onSelect={() => {}} />
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { RegisterHotkeys } from '@olwiba/ui';
|
|
2
|
+
import type { Hotkey } from '@olwiba/ui';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
export default function RegisterHotkeysDemo() {
|
|
6
|
+
const [lastKey, setLastKey] = React.useState<string | null>(null);
|
|
7
|
+
|
|
8
|
+
const hotkeys: Hotkey[] = [
|
|
9
|
+
{ key: 'k', meta: true, handler: () => setLastKey('⌘K — Open search'), description: 'Open search' },
|
|
10
|
+
{ key: 'Escape', handler: () => setLastKey('Esc — Close'), description: 'Close' },
|
|
11
|
+
{ key: '?', shift: true, handler: () => setLastKey('Shift+? — Show help'), description: 'Show help' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="flex flex-col items-center gap-4 text-center">
|
|
16
|
+
<RegisterHotkeys hotkeys={hotkeys} />
|
|
17
|
+
<p className="text-sm text-muted-foreground">
|
|
18
|
+
Focus this area and try:
|
|
19
|
+
</p>
|
|
20
|
+
<div className="flex gap-2 flex-wrap justify-center">
|
|
21
|
+
{[
|
|
22
|
+
{ label: '⌘K', desc: 'Search' },
|
|
23
|
+
{ label: 'Esc', desc: 'Close' },
|
|
24
|
+
{ label: 'Shift+?', desc: 'Help' },
|
|
25
|
+
].map(({ label, desc }) => (
|
|
26
|
+
<div key={label} className="flex flex-col items-center gap-1">
|
|
27
|
+
<kbd className="rounded bg-muted px-2 py-1 font-mono text-xs">{label}</kbd>
|
|
28
|
+
<span className="text-xs text-muted-foreground">{desc}</span>
|
|
29
|
+
</div>
|
|
30
|
+
))}
|
|
31
|
+
</div>
|
|
32
|
+
{lastKey && (
|
|
33
|
+
<div className="rounded-lg bg-muted px-4 py-2 text-sm font-mono">
|
|
34
|
+
{lastKey}
|
|
35
|
+
</div>
|
|
36
|
+
)}
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { RootErrorFallback } from '@olwiba/ui';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
export default function RootErrorFallbackDemo() {
|
|
5
|
+
const [triggered, setTriggered] = React.useState(false);
|
|
6
|
+
|
|
7
|
+
const error = new Error('Something went wrong while loading the page.');
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div className="w-full max-w-lg rounded-lg border overflow-hidden">
|
|
11
|
+
{triggered ? (
|
|
12
|
+
<RootErrorFallback
|
|
13
|
+
error={error}
|
|
14
|
+
resetErrorBoundary={() => setTriggered(false)}
|
|
15
|
+
/>
|
|
16
|
+
) : (
|
|
17
|
+
<div className="flex items-center justify-center py-8">
|
|
18
|
+
<button
|
|
19
|
+
onClick={() => setTriggered(true)}
|
|
20
|
+
className="rounded-md bg-destructive px-4 py-2 text-sm text-destructive-foreground"
|
|
21
|
+
>
|
|
22
|
+
Simulate error
|
|
23
|
+
</button>
|
|
24
|
+
</div>
|
|
25
|
+
)}
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { SettingsSection } from '@olwiba/ui';
|
|
3
|
+
import { Button, Input, Label } from '@olwiba/cn';
|
|
4
|
+
|
|
5
|
+
export default function Demo() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="mx-auto max-w-2xl divide-y divide-border p-8">
|
|
8
|
+
<SettingsSection title="Profile" description="This appears on your public Nexus profile.">
|
|
9
|
+
<form className="max-w-sm space-y-4">
|
|
10
|
+
<div className="space-y-2">
|
|
11
|
+
<Label htmlFor="settings-name">Full name</Label>
|
|
12
|
+
<Input id="settings-name" defaultValue="Kiwiana Ngata" />
|
|
13
|
+
</div>
|
|
14
|
+
<Button type="submit" size="sm">Save</Button>
|
|
15
|
+
</form>
|
|
16
|
+
</SettingsSection>
|
|
17
|
+
<SettingsSection title="Delete workspace" description="Once deleted, all projects and data are gone for good." danger>
|
|
18
|
+
<Button variant="destructive" size="sm">Delete workspace</Button>
|
|
19
|
+
</SettingsSection>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { Sortable } from '@olwiba/ui';
|
|
4
|
+
import { GripVertical } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
const initialTasks = [
|
|
7
|
+
{ id: 'design', title: 'Design the billing page', tag: 'Design' },
|
|
8
|
+
{ id: 'api', title: 'Wire up usage metering API', tag: 'Backend' },
|
|
9
|
+
{ id: 'emails', title: 'Dunning email sequence', tag: 'Growth' },
|
|
10
|
+
{ id: 'qa', title: 'QA pass on checkout flow', tag: 'QA' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export default function Demo() {
|
|
14
|
+
const [order, setOrder] = React.useState(initialTasks.map((t) => t.id));
|
|
15
|
+
const tasks = order.map((id) => initialTasks.find((t) => t.id === id)!);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="mx-auto max-w-md p-8">
|
|
19
|
+
<Sortable items={order} onReorder={setOrder}>
|
|
20
|
+
{tasks.map((task) => (
|
|
21
|
+
<div key={task.id} className="flex items-center gap-3 rounded-lg border bg-card px-3 py-2.5">
|
|
22
|
+
<GripVertical className="size-4 shrink-0 text-muted-foreground" />
|
|
23
|
+
<p className="flex-1 text-sm font-medium">{task.title}</p>
|
|
24
|
+
<span className="rounded-md bg-muted px-1.5 py-0.5 text-xs text-muted-foreground">{task.tag}</span>
|
|
25
|
+
</div>
|
|
26
|
+
))}
|
|
27
|
+
</Sortable>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { Spotlight } from '@olwiba/ui';
|
|
4
|
+
import { FileText, LayoutDashboard, Settings, Users } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
const groups = [
|
|
7
|
+
{
|
|
8
|
+
heading: 'Pages',
|
|
9
|
+
items: [
|
|
10
|
+
{ id: 'dashboard', label: 'Dashboard', icon: <LayoutDashboard className="size-4" />, onSelect: () => {} },
|
|
11
|
+
{ id: 'team', label: 'Team', icon: <Users className="size-4" />, onSelect: () => {} },
|
|
12
|
+
{ id: 'docs', label: 'Documents', icon: <FileText className="size-4" />, onSelect: () => {} },
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
heading: 'Settings',
|
|
17
|
+
items: [
|
|
18
|
+
{ id: 'settings', label: 'Settings', icon: <Settings className="size-4" />, description: 'Manage your account', onSelect: () => {} },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export default function Demo() {
|
|
24
|
+
return (
|
|
25
|
+
<div className="flex items-center justify-center p-8">
|
|
26
|
+
<Spotlight groups={groups} placeholder="Search pages..." />
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { StaggerChildren } from '@olwiba/ui';
|
|
3
|
+
|
|
4
|
+
const items = ['Dashboard', 'Analytics', 'Projects', 'Team', 'Settings'];
|
|
5
|
+
|
|
6
|
+
export default function Demo() {
|
|
7
|
+
return (
|
|
8
|
+
<StaggerChildren className="flex flex-col gap-2 p-6" stagger={80}>
|
|
9
|
+
{items.map((item) => (
|
|
10
|
+
<div key={item} className="rounded-xl border bg-card px-4 py-3 text-sm font-medium">
|
|
11
|
+
{item}
|
|
12
|
+
</div>
|
|
13
|
+
))}
|
|
14
|
+
</StaggerChildren>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { StatCard } from '@olwiba/ui';
|
|
3
|
+
|
|
4
|
+
export default function Demo() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="grid gap-4 p-6 sm:grid-cols-3">
|
|
7
|
+
<StatCard label="Monthly revenue" value="£24,200" delta="+12%" trend="up" description="vs last month" />
|
|
8
|
+
<StatCard label="Active users" value="3,841" delta="+4%" trend="up" description="past 30 days" />
|
|
9
|
+
<StatCard label="Churn rate" value="1.8%" delta="+0.3%" trend="down" description="rolling 90 days" />
|
|
10
|
+
</div>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { StepsSection } from '@olwiba/ui';
|
|
2
|
+
|
|
3
|
+
export default function Demo() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="space-y-6">
|
|
6
|
+
<StepsSection
|
|
7
|
+
variant="timeline"
|
|
8
|
+
badge="How it works"
|
|
9
|
+
title="Up and running in minutes"
|
|
10
|
+
description="Numbered timeline with a fading accent line, chainable step groups, and rich step content."
|
|
11
|
+
groups={[
|
|
12
|
+
{
|
|
13
|
+
steps: [
|
|
14
|
+
{
|
|
15
|
+
title: 'Create your account',
|
|
16
|
+
description: 'Sign up with your email to get started. Free while in beta.',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
title: 'Connect your channel',
|
|
20
|
+
description: (
|
|
21
|
+
<>
|
|
22
|
+
Add the bot to a group chat or DM, then send{' '}
|
|
23
|
+
<code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs text-primary">!bot start</code>{' '}
|
|
24
|
+
to activate.
|
|
25
|
+
</>
|
|
26
|
+
),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
title: 'Add your first monitor',
|
|
30
|
+
description: 'Paste a search URL and the bot checks for new results every few minutes.',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
after: (
|
|
34
|
+
<div className="rounded-lg border border-primary/30 bg-muted/40 p-6">
|
|
35
|
+
<h3 className="mb-1 text-lg font-semibold text-primary">Ready to get started?</h3>
|
|
36
|
+
<p className="text-sm text-muted-foreground">Set up your first alert — free.</p>
|
|
37
|
+
</div>
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
steps: [
|
|
42
|
+
{
|
|
43
|
+
title: 'Found what you needed?',
|
|
44
|
+
description: 'Cancel anytime — one command removes every monitor.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
title: 'Share it forward 💖',
|
|
48
|
+
description: 'Know someone searching? Send them an invite.',
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
]}
|
|
53
|
+
/>
|
|
54
|
+
|
|
55
|
+
<StepsSection
|
|
56
|
+
badge="Default variant"
|
|
57
|
+
title="The original horizontal layout"
|
|
58
|
+
steps={[
|
|
59
|
+
{ emoji: '✍️', title: 'Sign up', description: 'Create an account in seconds.' },
|
|
60
|
+
{ emoji: '🔌', title: 'Connect', description: 'Link your tools and data.' },
|
|
61
|
+
{ emoji: '🚀', title: 'Ship', description: 'Go live with confidence.' },
|
|
62
|
+
]}
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Suspensed } from '@olwiba/ui';
|
|
2
|
+
import { Spinner } from '@olwiba/cn';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
const DelayedContent = React.lazy(
|
|
6
|
+
() =>
|
|
7
|
+
new Promise<{ default: React.FC }>((resolve) => {
|
|
8
|
+
setTimeout(
|
|
9
|
+
() => resolve({ default: () => <p className="text-sm text-muted-foreground">Content loaded!</p> }),
|
|
10
|
+
1500
|
|
11
|
+
);
|
|
12
|
+
})
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export default function SuspensedDemo() {
|
|
16
|
+
const [key, setKey] = React.useState(0);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex flex-col items-center gap-4">
|
|
20
|
+
<Suspensed key={key} fallback={<Spinner size="sm" />}>
|
|
21
|
+
<DelayedContent />
|
|
22
|
+
</Suspensed>
|
|
23
|
+
<button
|
|
24
|
+
onClick={() => setKey((k) => k + 1)}
|
|
25
|
+
className="text-xs text-muted-foreground underline"
|
|
26
|
+
>
|
|
27
|
+
Replay
|
|
28
|
+
</button>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { TeamMembersPanel, type TeamMemberRecord } from '@olwiba/ui';
|
|
4
|
+
|
|
5
|
+
const initialMembers: TeamMemberRecord[] = [
|
|
6
|
+
{ id: '1', name: 'Kiwiana Ngata', email: 'kiwiana@nexus.dev', role: 'Owner' },
|
|
7
|
+
{ id: '2', name: 'Olwiba Rangi', email: 'olwiba@nexus.dev', role: 'Admin' },
|
|
8
|
+
{ id: '3', name: 'Genesis Tane', email: 'genesis@nexus.dev', role: 'Member' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
export default function Demo() {
|
|
12
|
+
const [members, setMembers] = React.useState(initialMembers);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="mx-auto max-w-3xl p-8">
|
|
16
|
+
<TeamMembersPanel
|
|
17
|
+
members={members}
|
|
18
|
+
onInvite={(email, role) =>
|
|
19
|
+
setMembers((prev) => [...prev, { id: crypto.randomUUID(), name: email.split('@')[0], email, role }])
|
|
20
|
+
}
|
|
21
|
+
onRoleChange={(id, role) => setMembers((prev) => prev.map((m) => (m.id === id ? { ...m, role } : m)))}
|
|
22
|
+
onRemove={(id) => setMembers((prev) => prev.filter((m) => m.id !== id))}
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { TestimonialCard } from '@olwiba/ui';
|
|
3
|
+
|
|
4
|
+
export default function Demo() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="flex items-center justify-center p-6">
|
|
7
|
+
<TestimonialCard
|
|
8
|
+
quote="The component library cut our design system setup time in half. Everything just fits together."
|
|
9
|
+
name="Sarah Chen"
|
|
10
|
+
role="Product Engineer"
|
|
11
|
+
company="Vercel"
|
|
12
|
+
initials="SC"
|
|
13
|
+
rating={5}
|
|
14
|
+
/>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ThemeColorUpdater } from '@olwiba/ui';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
const themes = ['blue', 'green', 'red', 'orange', 'purple'];
|
|
5
|
+
|
|
6
|
+
export default function ThemeColorUpdaterDemo() {
|
|
7
|
+
const [color, setColor] = React.useState('blue');
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div className="flex flex-col items-center gap-4">
|
|
11
|
+
<ThemeColorUpdater colorTheme={color} />
|
|
12
|
+
<div className="flex flex-wrap gap-2 justify-center">
|
|
13
|
+
{themes.map((c) => (
|
|
14
|
+
<button
|
|
15
|
+
key={c}
|
|
16
|
+
onClick={() => setColor(c)}
|
|
17
|
+
className={`rounded-md px-3 py-1.5 text-sm capitalize transition-colors ${
|
|
18
|
+
color === c
|
|
19
|
+
? 'bg-primary text-primary-foreground'
|
|
20
|
+
: 'bg-muted text-muted-foreground hover:bg-muted/80'
|
|
21
|
+
}`}
|
|
22
|
+
>
|
|
23
|
+
{c}
|
|
24
|
+
</button>
|
|
25
|
+
))}
|
|
26
|
+
</div>
|
|
27
|
+
<p className="text-xs text-muted-foreground font-mono">
|
|
28
|
+
document.documentElement[data-theme="{color}"]
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ThemeSwitchMinimal } from '@olwiba/ui';
|
|
2
|
+
|
|
3
|
+
export default function ThemeSwitchMinimalDemo() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="flex items-center gap-4">
|
|
6
|
+
<ThemeSwitchMinimal />
|
|
7
|
+
<span className="text-sm text-muted-foreground">Click to toggle dark / light mode</span>
|
|
8
|
+
</div>
|
|
9
|
+
);
|
|
10
|
+
}
|