@olwiba/ui 0.0.35 → 0.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +58 -3
- package/dist/index.js +90 -2
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
- package/src/blog/MdxContent.tsx +71 -0
- package/src/components/AppScreen.tsx +33 -0
- package/src/components/PhoneFrame.tsx +65 -0
- package/src/index.ts +5 -0
- package/src/marketing/HeroSection.tsx +22 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olwiba/ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.36",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -43,11 +43,18 @@
|
|
|
43
43
|
"zod": "^4.3.6"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
+
"@content-collections/mdx": ">=0.2.0",
|
|
46
47
|
"@olwiba/cn": ">=0.1.2",
|
|
47
48
|
"react": ">=18.0.0",
|
|
48
49
|
"react-dom": ">=18.0.0"
|
|
49
50
|
},
|
|
51
|
+
"peerDependenciesMeta": {
|
|
52
|
+
"@content-collections/mdx": {
|
|
53
|
+
"optional": true
|
|
54
|
+
}
|
|
55
|
+
},
|
|
50
56
|
"devDependencies": {
|
|
57
|
+
"@content-collections/mdx": "^0.2.2",
|
|
51
58
|
"@olwiba/cn": "^0.1.15",
|
|
52
59
|
"@tailwindcss/vite": "^4.1.18",
|
|
53
60
|
"@tanstack/react-router": "1.154.8",
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useMDXComponent } from '@content-collections/mdx/react';
|
|
3
|
+
import { cn } from '../lib/utils';
|
|
4
|
+
|
|
5
|
+
const components = {
|
|
6
|
+
h1: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
|
|
7
|
+
<h1 className={cn('mt-8 scroll-m-20 text-3xl font-bold tracking-tight text-foreground first:mt-0', className)} {...props} />
|
|
8
|
+
),
|
|
9
|
+
h2: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
|
|
10
|
+
<h2 className={cn('mt-10 scroll-m-20 text-2xl font-semibold tracking-tight text-foreground first:mt-0', className)} {...props} />
|
|
11
|
+
),
|
|
12
|
+
h3: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
|
|
13
|
+
<h3 className={cn('mt-8 scroll-m-20 text-xl font-semibold tracking-tight text-foreground', className)} {...props} />
|
|
14
|
+
),
|
|
15
|
+
h4: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
|
|
16
|
+
<h4 className={cn('mt-6 scroll-m-20 text-lg font-semibold tracking-tight text-foreground', className)} {...props} />
|
|
17
|
+
),
|
|
18
|
+
p: ({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => (
|
|
19
|
+
<p className={cn('leading-7 text-muted-foreground [&:not(:first-child)]:mt-6', className)} {...props} />
|
|
20
|
+
),
|
|
21
|
+
ul: ({ className, ...props }: React.HTMLAttributes<HTMLUListElement>) => (
|
|
22
|
+
<ul className={cn('my-6 ml-6 list-disc text-muted-foreground', className)} {...props} />
|
|
23
|
+
),
|
|
24
|
+
ol: ({ className, ...props }: React.HTMLAttributes<HTMLOListElement>) => (
|
|
25
|
+
<ol className={cn('my-6 ml-6 list-decimal text-muted-foreground', className)} {...props} />
|
|
26
|
+
),
|
|
27
|
+
li: ({ className, ...props }: React.HTMLAttributes<HTMLLIElement>) => (
|
|
28
|
+
<li className={cn('mt-2', className)} {...props} />
|
|
29
|
+
),
|
|
30
|
+
blockquote: ({ className, ...props }: React.HTMLAttributes<HTMLQuoteElement>) => (
|
|
31
|
+
<blockquote className={cn('mt-6 rounded-r-lg border-l-4 border-primary bg-muted py-4 pl-6 pr-4 italic text-muted-foreground', className)} {...props} />
|
|
32
|
+
),
|
|
33
|
+
hr: (props: React.HTMLAttributes<HTMLHRElement>) => (
|
|
34
|
+
<hr className="my-8 border-border" {...props} />
|
|
35
|
+
),
|
|
36
|
+
img: ({ className, alt, ...props }: React.ImgHTMLAttributes<HTMLImageElement>) => (
|
|
37
|
+
<img className={cn('rounded-lg border shadow-sm', className)} alt={alt} {...props} />
|
|
38
|
+
),
|
|
39
|
+
pre: ({ className, ...props }: React.HTMLAttributes<HTMLPreElement>) => (
|
|
40
|
+
<pre className={cn('mt-6 mb-4 overflow-x-auto rounded-lg bg-muted p-4 text-sm', className)} {...props} />
|
|
41
|
+
),
|
|
42
|
+
code: ({ className, ...props }: React.HTMLAttributes<HTMLElement>) => (
|
|
43
|
+
<code className={cn('relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm text-foreground', className)} {...props} />
|
|
44
|
+
),
|
|
45
|
+
table: ({ className, ...props }: React.HTMLAttributes<HTMLTableElement>) => (
|
|
46
|
+
<div className="my-6 w-full overflow-y-auto rounded-lg border border-border">
|
|
47
|
+
<table className={cn('w-full', className)} {...props} />
|
|
48
|
+
</div>
|
|
49
|
+
),
|
|
50
|
+
tr: ({ className, ...props }: React.HTMLAttributes<HTMLTableRowElement>) => (
|
|
51
|
+
<tr className={cn('m-0 border-t border-border p-0 even:bg-muted', className)} {...props} />
|
|
52
|
+
),
|
|
53
|
+
th: ({ className, ...props }: React.HTMLAttributes<HTMLTableCellElement>) => (
|
|
54
|
+
<th className={cn('border border-border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right', className)} {...props} />
|
|
55
|
+
),
|
|
56
|
+
td: ({ className, ...props }: React.HTMLAttributes<HTMLTableCellElement>) => (
|
|
57
|
+
<td className={cn('border border-border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right', className)} {...props} />
|
|
58
|
+
),
|
|
59
|
+
a: ({ className, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) => (
|
|
60
|
+
<a className={cn('font-medium text-primary underline underline-offset-4 hover:text-primary/80', className)} {...props} />
|
|
61
|
+
),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export interface MdxContentProps {
|
|
65
|
+
code: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function MdxContent({ code }: MdxContentProps) {
|
|
69
|
+
const MDXComponent = useMDXComponent(code);
|
|
70
|
+
return <MDXComponent components={components} />;
|
|
71
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cn } from '../lib/utils';
|
|
3
|
+
|
|
4
|
+
function AppScreenRoot({ children, className }: { children?: React.ReactNode; className?: string }) {
|
|
5
|
+
return (
|
|
6
|
+
<div className={cn('flex h-full flex-col', className)}>
|
|
7
|
+
{children}
|
|
8
|
+
</div>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function AppScreenHeader({ children, className }: { children?: React.ReactNode; className?: string }) {
|
|
13
|
+
return (
|
|
14
|
+
<div className={cn('flex items-center gap-2 border-b border-border px-4 py-3 shrink-0', className)}>
|
|
15
|
+
{children}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function AppScreenBody({ children, className }: { children?: React.ReactNode; className?: string }) {
|
|
21
|
+
return (
|
|
22
|
+
<div className={cn('flex-1 overflow-auto px-4 py-3', className)}>
|
|
23
|
+
{children}
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const AppScreen = Object.assign(AppScreenRoot, {
|
|
29
|
+
Header: AppScreenHeader,
|
|
30
|
+
Body: AppScreenBody,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export type { };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cn } from '../lib/utils';
|
|
3
|
+
|
|
4
|
+
const sizes = {
|
|
5
|
+
sm: { outer: 'w-44', height: 'h-80', notch: 'w-16 h-4', border: 'rounded-[2rem]', screen: 'rounded-[1.75rem]' },
|
|
6
|
+
md: { outer: 'w-56', height: 'h-[26rem]', notch: 'w-20 h-5', border: 'rounded-[2.5rem]', screen: 'rounded-[2.25rem]' },
|
|
7
|
+
lg: { outer: 'w-72', height: 'h-[34rem]', notch: 'w-24 h-6', border: 'rounded-[3rem]', screen: 'rounded-[2.75rem]' },
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
export interface PhoneFrameProps {
|
|
11
|
+
children?: React.ReactNode;
|
|
12
|
+
size?: keyof typeof sizes;
|
|
13
|
+
className?: string;
|
|
14
|
+
float?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function PhoneFrame({ children, size = 'md', className, float = true }: PhoneFrameProps) {
|
|
18
|
+
const s = sizes[size];
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
{float && (
|
|
22
|
+
<style>{`
|
|
23
|
+
@keyframes olwiba-phone-float {
|
|
24
|
+
0%, 100% { transform: translateY(0px); }
|
|
25
|
+
50% { transform: translateY(-10px); }
|
|
26
|
+
}
|
|
27
|
+
.olwiba-phone-float { animation: olwiba-phone-float 5s ease-in-out infinite; }
|
|
28
|
+
`}</style>
|
|
29
|
+
)}
|
|
30
|
+
<div
|
|
31
|
+
className={cn(
|
|
32
|
+
'relative mx-auto',
|
|
33
|
+
s.outer,
|
|
34
|
+
float && 'olwiba-phone-float',
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
>
|
|
38
|
+
{/* Device body */}
|
|
39
|
+
<div
|
|
40
|
+
className={cn(
|
|
41
|
+
'relative flex w-full flex-col overflow-hidden border-[3px] border-foreground/15 bg-muted shadow-2xl',
|
|
42
|
+
s.border,
|
|
43
|
+
s.height,
|
|
44
|
+
)}
|
|
45
|
+
>
|
|
46
|
+
{/* Status bar / notch area */}
|
|
47
|
+
<div className="relative flex justify-center pt-3 pb-1 shrink-0">
|
|
48
|
+
<div className={cn('rounded-full bg-foreground/10', s.notch)} />
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
{/* Screen */}
|
|
52
|
+
<div className={cn('mx-1.5 mb-1.5 flex-1 overflow-hidden bg-background', s.screen)}>
|
|
53
|
+
{children}
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
{/* Side buttons */}
|
|
58
|
+
<div className="absolute right-[-5px] top-24 h-12 w-[3px] rounded-l-sm bg-foreground/15" />
|
|
59
|
+
<div className="absolute left-[-5px] top-20 h-8 w-[3px] rounded-r-sm bg-foreground/15" />
|
|
60
|
+
<div className="absolute left-[-5px] top-32 h-8 w-[3px] rounded-r-sm bg-foreground/15" />
|
|
61
|
+
<div className="absolute left-[-5px] top-44 h-8 w-[3px] rounded-r-sm bg-foreground/15" />
|
|
62
|
+
</div>
|
|
63
|
+
</>
|
|
64
|
+
);
|
|
65
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -81,6 +81,10 @@ export { Dock, type DockProps, type DockItem } from './components/Dock';
|
|
|
81
81
|
export { ContextMenu, type ContextMenuProps, type ContextMenuDef } from './components/ContextMenu';
|
|
82
82
|
export { ConfirmDialog, type ConfirmDialogProps } from './components/ConfirmDialog';
|
|
83
83
|
|
|
84
|
+
// ─── Components — device mockups ────────────────────────────────────────────
|
|
85
|
+
export { PhoneFrame, type PhoneFrameProps } from './components/PhoneFrame';
|
|
86
|
+
export { AppScreen } from './components/AppScreen';
|
|
87
|
+
|
|
84
88
|
// ─── Components — cards ───────────────────────────────────────────────────────
|
|
85
89
|
export { GlassCard, type GlassCardProps } from './components/GlassCard';
|
|
86
90
|
export { FeatureCard, type FeatureCardProps } from './components/FeatureCard';
|
|
@@ -103,6 +107,7 @@ export { RootErrorFallback } from './components/RootErrorFallback';
|
|
|
103
107
|
// ─── Blog ────────────────────────────────────────────────────────────────────
|
|
104
108
|
export { PostCard, type PostCardProps } from './blog/PostCard';
|
|
105
109
|
export { PostList, type PostListProps } from './blog/PostList';
|
|
110
|
+
export { MdxContent, type MdxContentProps } from './blog/MdxContent';
|
|
106
111
|
|
|
107
112
|
// ─── Hooks ────────────────────────────────────────────────────────────────────
|
|
108
113
|
export { useMounted } from './hooks/use-mounted';
|
|
@@ -4,6 +4,7 @@ import * as React from 'react';
|
|
|
4
4
|
import { ArrowRight } from 'lucide-react';
|
|
5
5
|
import { Avatar, AvatarFallback, AvatarImage, Badge, Button } from '@olwiba/cn';
|
|
6
6
|
import { FadeIn } from '../motion/FadeIn';
|
|
7
|
+
import { PhoneFrame } from '../components/PhoneFrame';
|
|
7
8
|
import type { AppShellRenderLink } from '../app/AppShell';
|
|
8
9
|
|
|
9
10
|
export interface HeroSectionProps {
|
|
@@ -12,7 +13,9 @@ export interface HeroSectionProps {
|
|
|
12
13
|
description: string;
|
|
13
14
|
primaryCta: { label: string; href: string };
|
|
14
15
|
secondaryCta?: { label: string; href: string };
|
|
15
|
-
heroImage
|
|
16
|
+
heroImage?: React.ReactNode;
|
|
17
|
+
media?: 'image' | 'phone' | 'none';
|
|
18
|
+
phoneSize?: 'sm' | 'md' | 'lg';
|
|
16
19
|
avatarUrls?: string[];
|
|
17
20
|
socialProofText?: string;
|
|
18
21
|
renderLink?: AppShellRenderLink;
|
|
@@ -29,6 +32,8 @@ export function HeroSection({
|
|
|
29
32
|
primaryCta,
|
|
30
33
|
secondaryCta,
|
|
31
34
|
heroImage,
|
|
35
|
+
media = 'image',
|
|
36
|
+
phoneSize = 'lg',
|
|
32
37
|
avatarUrls,
|
|
33
38
|
socialProofText,
|
|
34
39
|
renderLink = defaultRenderLink,
|
|
@@ -74,12 +79,22 @@ export function HeroSection({
|
|
|
74
79
|
</div>
|
|
75
80
|
</FadeIn>
|
|
76
81
|
|
|
77
|
-
{/* Right -
|
|
78
|
-
|
|
79
|
-
<
|
|
80
|
-
{
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
{/* Right - media */}
|
|
83
|
+
{media !== 'none' && (
|
|
84
|
+
<FadeIn direction="right" delay={200}>
|
|
85
|
+
{media === 'phone' ? (
|
|
86
|
+
<div className="flex justify-center">
|
|
87
|
+
<PhoneFrame size={phoneSize}>
|
|
88
|
+
{heroImage}
|
|
89
|
+
</PhoneFrame>
|
|
90
|
+
</div>
|
|
91
|
+
) : (
|
|
92
|
+
<div className="overflow-hidden rounded-2xl">
|
|
93
|
+
{heroImage}
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
</FadeIn>
|
|
97
|
+
)}
|
|
83
98
|
</div>
|
|
84
99
|
|
|
85
100
|
{/* Avatar social proof row */}
|