@nimblegiant/stilts 0.2.0-alpha.1 → 0.2.0-alpha.4
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 +734 -107
- package/dist/index.cjs +125 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -5
- package/dist/index.d.ts +56 -5
- package/dist/index.js +124 -12
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/layout/MainNav.tsx +39 -17
- package/src/components/patterns/ContentPage.tsx +15 -6
- package/src/components/patterns/ListCard.tsx +134 -0
- package/src/components/patterns/index.ts +26 -20
- package/src/components/ui/AuthorMeta.tsx +44 -0
- package/src/components/ui/index.ts +3 -1
- /package/dist/styles/{styles/animations.css → animations.css} +0 -0
- /package/dist/styles/{styles/base.css → base.css} +0 -0
- /package/dist/styles/{styles/index.css → index.css} +0 -0
- /package/dist/styles/{styles/tokens.css → tokens.css} +0 -0
- /package/dist/styles/{styles/utilities.css → utilities.css} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimblegiant/stilts",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.4",
|
|
4
4
|
"description": "Nimble Giant's whimsical yet disciplined design system",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"package.json"
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
35
|
-
"build": "tsup &&
|
|
35
|
+
"build": "tsup && mkdir -p dist/styles && cp src/styles/*.css dist/styles/",
|
|
36
36
|
"dev": "tsup --watch",
|
|
37
37
|
"typecheck": "tsc --noEmit",
|
|
38
38
|
"lint": "eslint src --ext .ts,.tsx",
|
|
@@ -82,4 +82,4 @@
|
|
|
82
82
|
"access": "public",
|
|
83
83
|
"registry": "https://registry.npmjs.org"
|
|
84
84
|
}
|
|
85
|
-
}
|
|
85
|
+
}
|
|
@@ -4,13 +4,22 @@ import { Sun, Moon, Menu, X, ArrowRight } from "lucide-react";
|
|
|
4
4
|
interface MainNavProps {
|
|
5
5
|
currentPath: string;
|
|
6
6
|
isPortfolioPage?: boolean;
|
|
7
|
+
navLinks?: { href: string; label: string }[];
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
export default function MainNav({
|
|
10
|
+
export default function MainNav({
|
|
11
|
+
currentPath,
|
|
12
|
+
isPortfolioPage = false,
|
|
13
|
+
navLinks = [],
|
|
14
|
+
}: MainNavProps) {
|
|
10
15
|
const [theme, setTheme] = useState<"light" | "dark">(() => {
|
|
11
16
|
// Initialize theme from document during first render (client-side only)
|
|
12
17
|
if (typeof document !== "undefined") {
|
|
13
|
-
return (
|
|
18
|
+
return (
|
|
19
|
+
(document.documentElement.getAttribute("data-theme") as
|
|
20
|
+
| "light"
|
|
21
|
+
| "dark") || "light"
|
|
22
|
+
);
|
|
14
23
|
}
|
|
15
24
|
return "light";
|
|
16
25
|
});
|
|
@@ -64,13 +73,6 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
|
|
|
64
73
|
localStorage.setItem("theme", newTheme);
|
|
65
74
|
}, [theme]);
|
|
66
75
|
|
|
67
|
-
const navLinks = [
|
|
68
|
-
{ href: "/", label: "HOME" },
|
|
69
|
-
{ href: "/about", label: "ABOUT" },
|
|
70
|
-
{ href: "/services", label: "SERVICES" },
|
|
71
|
-
{ href: "/contact", label: "CONTACT" },
|
|
72
|
-
];
|
|
73
|
-
|
|
74
76
|
const isActive = (href: string) => {
|
|
75
77
|
if (href === "/") return currentPath === "/";
|
|
76
78
|
return currentPath.startsWith(href);
|
|
@@ -90,7 +92,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
|
|
|
90
92
|
role="navigation"
|
|
91
93
|
aria-label="Main navigation"
|
|
92
94
|
className={`fixed top-0 right-0 left-0 z-[var(--z-nav)] border-b border-[var(--color-border)]/0 transition-all duration-300 ease-out ${
|
|
93
|
-
isScrolled
|
|
95
|
+
isScrolled
|
|
96
|
+
? "bg-[var(--color-surface-overlay)]/0 backdrop-blur-sm"
|
|
97
|
+
: "bg-transparent"
|
|
94
98
|
} `}
|
|
95
99
|
>
|
|
96
100
|
<div className="container-xl container flex h-20 items-center justify-between sm:p-6 lg:p-14">
|
|
@@ -102,7 +106,11 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
|
|
|
102
106
|
>
|
|
103
107
|
{/* Light mode logo (dark logo on light bg) */}
|
|
104
108
|
<img
|
|
105
|
-
src={
|
|
109
|
+
src={
|
|
110
|
+
showLightText
|
|
111
|
+
? "/images/logo-light.svg"
|
|
112
|
+
: "/images/logo-dark.svg"
|
|
113
|
+
}
|
|
106
114
|
alt=""
|
|
107
115
|
aria-hidden="true"
|
|
108
116
|
className="h-10 w-auto"
|
|
@@ -173,7 +181,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
|
|
|
173
181
|
? "text-white/70 hover:text-white"
|
|
174
182
|
: "text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]"
|
|
175
183
|
} `}
|
|
176
|
-
aria-label={
|
|
184
|
+
aria-label={
|
|
185
|
+
isMenuOpen ? "Close navigation menu" : "Open navigation menu"
|
|
186
|
+
}
|
|
177
187
|
aria-expanded={isMenuOpen}
|
|
178
188
|
aria-controls="mobile-menu"
|
|
179
189
|
>
|
|
@@ -193,7 +203,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
|
|
|
193
203
|
aria-modal="true"
|
|
194
204
|
aria-label="Navigation menu"
|
|
195
205
|
className={`fixed inset-0 z-[calc(var(--z-nav)-1)] transition-all duration-300 ease-out md:hidden ${
|
|
196
|
-
isMenuOpen
|
|
206
|
+
isMenuOpen
|
|
207
|
+
? "pointer-events-auto opacity-100"
|
|
208
|
+
: "pointer-events-none opacity-0"
|
|
197
209
|
} `}
|
|
198
210
|
>
|
|
199
211
|
{/* Backdrop */}
|
|
@@ -215,9 +227,15 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
|
|
|
215
227
|
<li
|
|
216
228
|
key={link.href}
|
|
217
229
|
className={`transition-all duration-300 ${
|
|
218
|
-
isMenuOpen
|
|
230
|
+
isMenuOpen
|
|
231
|
+
? "translate-x-0 opacity-100"
|
|
232
|
+
: "translate-x-8 opacity-0"
|
|
219
233
|
} `}
|
|
220
|
-
style={{
|
|
234
|
+
style={{
|
|
235
|
+
transitionDelay: isMenuOpen
|
|
236
|
+
? `${index * 50 + 100}ms`
|
|
237
|
+
: "0ms",
|
|
238
|
+
}}
|
|
221
239
|
>
|
|
222
240
|
<a
|
|
223
241
|
href={link.href}
|
|
@@ -232,7 +250,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
|
|
|
232
250
|
{link.label}
|
|
233
251
|
<ArrowRight
|
|
234
252
|
className={`h-4 w-4 transition-all duration-200 ${
|
|
235
|
-
isActive(link.href)
|
|
253
|
+
isActive(link.href)
|
|
254
|
+
? "opacity-50"
|
|
255
|
+
: "opacity-0 group-hover:opacity-30"
|
|
236
256
|
} `}
|
|
237
257
|
aria-hidden="true"
|
|
238
258
|
/>
|
|
@@ -245,7 +265,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
|
|
|
245
265
|
{/* Footer section with theme toggle */}
|
|
246
266
|
<div
|
|
247
267
|
className={`border-t border-[var(--color-border)] pt-6 transition-all duration-300 ${
|
|
248
|
-
isMenuOpen
|
|
268
|
+
isMenuOpen
|
|
269
|
+
? "translate-y-0 opacity-100"
|
|
270
|
+
: "translate-y-4 opacity-0"
|
|
249
271
|
} `}
|
|
250
272
|
style={{ transitionDelay: isMenuOpen ? "300ms" : "0ms" }}
|
|
251
273
|
>
|
|
@@ -10,26 +10,35 @@ interface ContentPageProps {
|
|
|
10
10
|
children: React.ReactNode;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export default function ContentPage({
|
|
13
|
+
export default function ContentPage({
|
|
14
|
+
title,
|
|
15
|
+
subtitle,
|
|
16
|
+
lastUpdated,
|
|
17
|
+
children,
|
|
18
|
+
}: ContentPageProps) {
|
|
14
19
|
return (
|
|
15
20
|
<>
|
|
16
21
|
<section className="pt-32 pb-12">
|
|
17
|
-
<div className="container-
|
|
22
|
+
<div className="container-md container">
|
|
18
23
|
<h1 className="mb-4 text-4xl font-bold text-[var(--color-text-primary)] md:text-5xl">
|
|
19
24
|
{title}
|
|
20
25
|
</h1>
|
|
21
26
|
{subtitle && (
|
|
22
|
-
<h2 className="mb-2 text-xl text-[var(--color-text-secondary)]">
|
|
27
|
+
<h2 className="mb-2 text-xl text-[var(--color-text-secondary)]">
|
|
28
|
+
{subtitle}
|
|
29
|
+
</h2>
|
|
23
30
|
)}
|
|
24
31
|
{lastUpdated && (
|
|
25
|
-
<p className="text-[var(--color-text-muted)]">
|
|
32
|
+
<p className="text-[var(--color-text-muted)]">
|
|
33
|
+
Last updated: {lastUpdated}
|
|
34
|
+
</p>
|
|
26
35
|
)}
|
|
27
36
|
</div>
|
|
28
37
|
</section>
|
|
29
38
|
|
|
30
39
|
<section className="py-12">
|
|
31
|
-
<div className="container-
|
|
32
|
-
<div className="prose
|
|
40
|
+
<div className="container-md container">
|
|
41
|
+
<div className="prose max-w-full">{children}</div>
|
|
33
42
|
</div>
|
|
34
43
|
</section>
|
|
35
44
|
</>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { AuthorMeta, Author } from "../ui/AuthorMeta";
|
|
3
|
+
|
|
4
|
+
export interface ListCardProps {
|
|
5
|
+
href: string;
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
tagline?: string;
|
|
9
|
+
thumbnail?: string;
|
|
10
|
+
tags?: string[];
|
|
11
|
+
meta?: {
|
|
12
|
+
date?: string;
|
|
13
|
+
author?: Author;
|
|
14
|
+
};
|
|
15
|
+
imageAlt?: string;
|
|
16
|
+
animationDelay?: number;
|
|
17
|
+
specialImageComponent?: boolean;
|
|
18
|
+
imageBgClass?: string;
|
|
19
|
+
imageDark?: string;
|
|
20
|
+
imageLight?: string;
|
|
21
|
+
children?: ReactNode;
|
|
22
|
+
footerSlot?: ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* ListCard Component
|
|
27
|
+
* Displays a card with optional image, title, description, meta, and tags
|
|
28
|
+
* Used for products, blog posts, and other list items
|
|
29
|
+
*/
|
|
30
|
+
export function ListCard({
|
|
31
|
+
href,
|
|
32
|
+
title,
|
|
33
|
+
description,
|
|
34
|
+
tagline,
|
|
35
|
+
thumbnail,
|
|
36
|
+
tags,
|
|
37
|
+
meta,
|
|
38
|
+
imageAlt = "Card image",
|
|
39
|
+
animationDelay,
|
|
40
|
+
specialImageComponent = false,
|
|
41
|
+
imageBgClass = "bg-[var(--color-surface-muted)]",
|
|
42
|
+
children,
|
|
43
|
+
imageDark,
|
|
44
|
+
imageLight,
|
|
45
|
+
footerSlot,
|
|
46
|
+
}: ListCardProps) {
|
|
47
|
+
const animationStyle = animationDelay
|
|
48
|
+
? { animationDelay: `${animationDelay}s` }
|
|
49
|
+
: undefined;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<a
|
|
53
|
+
href={href}
|
|
54
|
+
className="hero-animate group flex flex-col overflow-hidden rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] opacity-0 shadow-sm transition-all duration-300 hover:border-[var(--color-primary)] hover:shadow-lg"
|
|
55
|
+
style={animationStyle}
|
|
56
|
+
>
|
|
57
|
+
{/* Image/Thumbnail */}
|
|
58
|
+
{thumbnail && !specialImageComponent && (
|
|
59
|
+
<div
|
|
60
|
+
className={`relative aspect-video w-full overflow-hidden rounded-t-lg ${imageBgClass}`}
|
|
61
|
+
>
|
|
62
|
+
<img
|
|
63
|
+
src={thumbnail}
|
|
64
|
+
alt={imageAlt}
|
|
65
|
+
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
{/* Special image component slot */}
|
|
71
|
+
<div className="relative flex aspect-video w-full items-center justify-center overflow-hidden rounded-t-lg bg-gradient-to-br from-[var(--color-dot-magenta)] via-[var(--color-dot-purple)] to-[var(--color-dot-turquoise)] p-12">
|
|
72
|
+
<img
|
|
73
|
+
src={imageDark}
|
|
74
|
+
alt={title}
|
|
75
|
+
className="dark-mode-hidden h-full w-full object-contain transition-transform duration-300 group-hover:scale-105"
|
|
76
|
+
/>
|
|
77
|
+
<img
|
|
78
|
+
src={imageLight}
|
|
79
|
+
alt={title}
|
|
80
|
+
className="dark-mode-visible h-full w-full object-contain transition-transform duration-300 group-hover:scale-105"
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
{/* Content */}
|
|
85
|
+
<div className="flex flex-1 flex-col p-6">
|
|
86
|
+
{/* Meta (optional - for date, author) */}
|
|
87
|
+
{meta && (
|
|
88
|
+
<div className="mb-2 flex items-center gap-2 text-xs text-[var(--color-text-muted)]">
|
|
89
|
+
{meta.author && <AuthorMeta author={meta.author} />}
|
|
90
|
+
{meta.date && meta.author && <span>•</span>}
|
|
91
|
+
{meta.date && <span>{meta.date}</span>}
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
|
|
95
|
+
{/* Title */}
|
|
96
|
+
<h3 className="font-heading mb-2 text-xl font-semibold text-[var(--color-text-primary)]">
|
|
97
|
+
{title}
|
|
98
|
+
</h3>
|
|
99
|
+
{tagline && (
|
|
100
|
+
<p className="mb-3 text-sm font-medium text-[var(--color-primary)]">
|
|
101
|
+
{tagline}
|
|
102
|
+
</p>
|
|
103
|
+
)}
|
|
104
|
+
|
|
105
|
+
{/* Description */}
|
|
106
|
+
<p className="mb-4 flex-1 text-sm leading-relaxed text-[var(--color-text-secondary)]">
|
|
107
|
+
{description}
|
|
108
|
+
</p>
|
|
109
|
+
|
|
110
|
+
{/* Tags */}
|
|
111
|
+
{tags && tags.length > 0 && (
|
|
112
|
+
<div className="mt-auto flex flex-wrap gap-2 pt-4">
|
|
113
|
+
{tags.map((tag) => (
|
|
114
|
+
<span
|
|
115
|
+
key={tag}
|
|
116
|
+
className="rounded-full border border-[var(--color-border)] bg-[var(--color-surface-muted)] px-3 py-1 text-xs font-medium text-[var(--color-text-secondary)]"
|
|
117
|
+
>
|
|
118
|
+
{tag}
|
|
119
|
+
</span>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
|
|
124
|
+
{/* Custom content slot */}
|
|
125
|
+
{children}
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
{/* Footer slot */}
|
|
129
|
+
{footerSlot}
|
|
130
|
+
</a>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export default ListCard;
|
|
@@ -2,23 +2,29 @@
|
|
|
2
2
|
* Pattern Components
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
export { default as PageHero } from
|
|
6
|
-
export { default as VideoHero } from
|
|
7
|
-
export { default as TeamGrid } from
|
|
8
|
-
export { default as ServiceSection } from
|
|
9
|
-
export { default as ProcessSteps } from
|
|
10
|
-
export { default as StatBar } from
|
|
11
|
-
export { default as IndustryGrid } from
|
|
12
|
-
export { default as ContentPage } from
|
|
13
|
-
export { TerminalWindow, TerminalChrome } from
|
|
14
|
-
export type {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
export {
|
|
20
|
-
export type {
|
|
21
|
-
export {
|
|
22
|
-
export type {
|
|
23
|
-
export {
|
|
24
|
-
export type {
|
|
5
|
+
export { default as PageHero } from "./PageHero";
|
|
6
|
+
export { default as VideoHero } from "./VideoHero";
|
|
7
|
+
export { default as TeamGrid } from "./TeamGrid";
|
|
8
|
+
export { default as ServiceSection } from "./ServiceSection";
|
|
9
|
+
export { default as ProcessSteps } from "./ProcessSteps";
|
|
10
|
+
export { default as StatBar } from "./StatBar";
|
|
11
|
+
export { default as IndustryGrid } from "./IndustryGrid";
|
|
12
|
+
export { default as ContentPage } from "./ContentPage";
|
|
13
|
+
export { TerminalWindow, TerminalChrome } from "./TerminalWindow";
|
|
14
|
+
export type {
|
|
15
|
+
TerminalLine,
|
|
16
|
+
TerminalWindowProps,
|
|
17
|
+
TerminalChromeProps,
|
|
18
|
+
} from "./TerminalWindow";
|
|
19
|
+
export { default as TerminalHero } from "./TerminalHero";
|
|
20
|
+
export type { TerminalHeroProps } from "./TerminalHero";
|
|
21
|
+
export { CTAButton } from "./CTAButton";
|
|
22
|
+
export type { CTAButtonProps } from "./CTAButton";
|
|
23
|
+
export { ProductCard } from "./ProductCard";
|
|
24
|
+
export type { ProductCardProps } from "./ProductCard";
|
|
25
|
+
export { CodeBlock } from "./CodeBlock";
|
|
26
|
+
export type { CodeBlockProps } from "./CodeBlock";
|
|
27
|
+
export { FeatureShowcase } from "./FeatureShowcase";
|
|
28
|
+
export type { FeatureShowcaseProps, Feature } from "./FeatureShowcase";
|
|
29
|
+
export { ListCard } from "./ListCard";
|
|
30
|
+
export type { ListCardProps } from "./ListCard";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface Author {
|
|
2
|
+
name: string;
|
|
3
|
+
url?: string;
|
|
4
|
+
avatar?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface AuthorMetaProps {
|
|
8
|
+
author: Author;
|
|
9
|
+
linkable?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* AuthorMeta Component
|
|
14
|
+
* Displays author information with optional avatar
|
|
15
|
+
*/
|
|
16
|
+
export function AuthorMeta({ author, linkable = false }: AuthorMetaProps) {
|
|
17
|
+
const content = (
|
|
18
|
+
<span className="flex items-center gap-1">
|
|
19
|
+
{author.avatar && (
|
|
20
|
+
<img
|
|
21
|
+
src={author.avatar}
|
|
22
|
+
alt={author.name}
|
|
23
|
+
className="h-4 w-4 rounded-full object-cover"
|
|
24
|
+
/>
|
|
25
|
+
)}
|
|
26
|
+
<span>{author.name}</span>
|
|
27
|
+
</span>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (linkable && author.url) {
|
|
31
|
+
return (
|
|
32
|
+
<a
|
|
33
|
+
href={author.url}
|
|
34
|
+
className="flex items-center gap-1 transition-colors hover:text-[var(--color-primary)]"
|
|
35
|
+
>
|
|
36
|
+
{content}
|
|
37
|
+
</a>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return content;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default AuthorMeta;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|