@inferencesh/ui 0.0.0-stage → 0.1.1
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/LICENSE +22 -0
- package/README.md +68 -2
- package/components.json +22 -0
- package/package.json +231 -4
- package/src/components/audio-player.tsx +549 -0
- package/src/components/button.tsx +13 -0
- package/src/components/chart.tsx +403 -0
- package/src/components/circuit.tsx +276 -0
- package/src/components/code-block/code-block.tsx +260 -0
- package/src/components/code-block/index.ts +18 -0
- package/src/components/code-block/languages.ts +311 -0
- package/src/components/code-block/styles.ts +39 -0
- package/src/components/code-block/tokenizer.ts +290 -0
- package/src/components/code-block/types.ts +38 -0
- package/src/components/code-block/utils.ts +39 -0
- package/src/components/code.tsx +48 -0
- package/src/components/cookie-consent.tsx +137 -0
- package/src/components/copy-button.tsx +44 -0
- package/src/components/country-dropdown.tsx +188 -0
- package/src/components/glow-loader-fast.tsx +32 -0
- package/src/components/glow-loader-mono.tsx +33 -0
- package/src/components/glow-loader-sm-fast.tsx +32 -0
- package/src/components/glow-loader-sm-mono.tsx +34 -0
- package/src/components/glow-loader-sm.tsx +32 -0
- package/src/components/glow-loader.tsx +32 -0
- package/src/components/glow-logo.tsx +32 -0
- package/src/components/gradient.tsx +80 -0
- package/src/components/index.ts +45 -0
- package/src/components/inline-sidebar.tsx +351 -0
- package/src/components/input-color.tsx +349 -0
- package/src/components/logo-base.tsx +213 -0
- package/src/components/logo-color.tsx +23 -0
- package/src/components/logo-full.tsx +71 -0
- package/src/components/logo-svg.tsx +23 -0
- package/src/components/menu-navigation.tsx +359 -0
- package/src/components/multi-select.tsx +721 -0
- package/src/components/pastel-gradient.tsx +25 -0
- package/src/components/pixel-symmetry.tsx +151 -0
- package/src/components/progress.tsx +28 -0
- package/src/components/scroll-more.tsx +131 -0
- package/src/components/select.tsx +85 -0
- package/src/components/shimmering-text.tsx +108 -0
- package/src/components/sidebar-light.tsx +102 -0
- package/src/components/sonner.tsx +38 -0
- package/src/components/theme-switcher-sidebar.tsx +56 -0
- package/src/components/theme-switcher-simple.tsx +110 -0
- package/src/components/theme-switcher.tsx +47 -0
- package/src/components/toggle-group.tsx +55 -0
- package/src/components/tooltip-button.tsx +91 -0
- package/src/components/tooltip.tsx +71 -0
- package/src/components/tree-view.tsx +499 -0
- package/src/components/unglow-loader.tsx +33 -0
- package/src/components/waveform.tsx +1632 -0
- package/src/components/zoomable-image.tsx +76 -0
- package/src/docs/api-docs.tsx +763 -0
- package/src/docs/callout.tsx +160 -0
- package/src/docs/code-group.tsx +215 -0
- package/src/docs/copy-menu.tsx +143 -0
- package/src/docs/doc-tabs.tsx +84 -0
- package/src/docs/index.ts +16 -0
- package/src/docs/steps.tsx +105 -0
- package/src/docs/table-of-contents.tsx +174 -0
- package/src/examples/guide-markdown.ts +129 -0
- package/src/examples/index.ts +1153 -0
- package/src/global.d.ts +1 -0
- package/src/hooks/use-copy-to-clipboard.ts +36 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/lib/display-name.ts +15 -0
- package/src/lib/example-input.ts +130 -0
- package/src/lib/plans.ts +44 -0
- package/src/lib/safe-content.ts +137 -0
- package/src/lib/schema.ts +320 -0
- package/src/lib/storage.ts +108 -0
- package/src/markdown/docs-markdown-renderer.tsx +518 -0
- package/src/markdown/helpers.ts +95 -0
- package/src/markdown/index.ts +24 -0
- package/src/markdown/markdown-renderer.tsx +232 -0
- package/src/markdown/youtube-embed.tsx +43 -0
- package/src/og/image.ts +312 -0
- package/src/og/index.tsx +430 -0
- package/src/og/query.ts +26 -0
- package/src/shadcn/accordion.tsx +71 -0
- package/src/shadcn/alert-dialog.tsx +187 -0
- package/src/shadcn/alert.tsx +75 -0
- package/src/shadcn/aspect-ratio.tsx +22 -0
- package/src/shadcn/attachment.tsx +207 -0
- package/src/shadcn/avatar.tsx +106 -0
- package/src/shadcn/badge.tsx +51 -0
- package/src/shadcn/breadcrumb.tsx +124 -0
- package/src/shadcn/bubble.tsx +127 -0
- package/src/shadcn/button-group.tsx +87 -0
- package/src/shadcn/button.tsx +57 -0
- package/src/shadcn/calendar.tsx +219 -0
- package/src/shadcn/card.tsx +102 -0
- package/src/shadcn/carousel.tsx +242 -0
- package/src/shadcn/chart.tsx +372 -0
- package/src/shadcn/checkbox.tsx +28 -0
- package/src/shadcn/collapsible.tsx +19 -0
- package/src/shadcn/combobox.tsx +297 -0
- package/src/shadcn/command.tsx +194 -0
- package/src/shadcn/context-menu.tsx +270 -0
- package/src/shadcn/dialog.tsx +158 -0
- package/src/shadcn/direction.tsx +4 -0
- package/src/shadcn/drawer.tsx +227 -0
- package/src/shadcn/dropdown-menu.tsx +265 -0
- package/src/shadcn/empty.tsx +103 -0
- package/src/shadcn/field.tsx +238 -0
- package/src/shadcn/form.tsx +173 -0
- package/src/shadcn/hover-card.tsx +50 -0
- package/src/shadcn/index.ts +53 -0
- package/src/shadcn/input-group.tsx +156 -0
- package/src/shadcn/input-otp.tsx +84 -0
- package/src/shadcn/input.tsx +19 -0
- package/src/shadcn/item.tsx +201 -0
- package/src/shadcn/kbd.tsx +26 -0
- package/src/shadcn/label.tsx +19 -0
- package/src/shadcn/marker.tsx +70 -0
- package/src/shadcn/menubar.tsx +278 -0
- package/src/shadcn/message-scroller.tsx +131 -0
- package/src/shadcn/message.tsx +91 -0
- package/src/shadcn/native-select.tsx +60 -0
- package/src/shadcn/navigation-menu.tsx +167 -0
- package/src/shadcn/pagination.tsx +130 -0
- package/src/shadcn/popover.tsx +87 -0
- package/src/shadcn/progress.tsx +82 -0
- package/src/shadcn/radio-group.tsx +35 -0
- package/src/shadcn/resizable.tsx +49 -0
- package/src/shadcn/scroll-area.tsx +52 -0
- package/src/shadcn/select.tsx +200 -0
- package/src/shadcn/separator.tsx +22 -0
- package/src/shadcn/sheet.tsx +136 -0
- package/src/shadcn/sidebar.tsx +721 -0
- package/src/shadcn/skeleton.tsx +13 -0
- package/src/shadcn/slider.tsx +51 -0
- package/src/shadcn/sonner.tsx +49 -0
- package/src/shadcn/spinner.tsx +10 -0
- package/src/shadcn/switch.tsx +29 -0
- package/src/shadcn/table.tsx +115 -0
- package/src/shadcn/tabs.tsx +79 -0
- package/src/shadcn/textarea.tsx +17 -0
- package/src/shadcn/toggle-group.tsx +89 -0
- package/src/shadcn/toggle.tsx +44 -0
- package/src/shadcn/tooltip.tsx +63 -0
- package/src/theme/config.ts +48 -0
- package/src/theme/css/base.css +92 -0
- package/src/theme/css/bitboy.css +75 -0
- package/src/theme/css/dark.css +47 -0
- package/src/theme/css/designer.css +122 -0
- package/src/theme/css/fonts.css +33 -0
- package/src/theme/css/globals.css +14 -0
- package/src/theme/css/hackintosh.css +57 -0
- package/src/theme/css/llama.css +103 -0
- package/src/theme/css/safelist.css +39 -0
- package/src/theme/css/shadcn.css +77 -0
- package/src/theme/css/theme.css +216 -0
- package/src/theme/embed-theme.ts +146 -0
- package/src/theme/fonts/FiraCode-VF.woff2 +0 -0
- package/src/theme/fonts/Hack-Regular.woff2 +0 -0
- package/src/theme/fonts/LICENSE-FiraCode.txt +93 -0
- package/src/theme/fonts/LICENSE-Hack.txt +45 -0
- package/src/theme/fonts/LICENSE-MonaSans.txt +93 -0
- package/src/theme/fonts/Mona-Sans.woff2 +0 -0
- package/src/theme/index.ts +15 -0
- package/src/utils/cn.ts +3 -0
- package/src/utils/index.ts +2 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// CANONICAL SVG PATH DATA - Single source of truth for all logo variants
|
|
7
|
+
// =============================================================================
|
|
8
|
+
|
|
9
|
+
/** Inner triangle paths (the three internal colored strokes) */
|
|
10
|
+
export const INNER_PATHS = {
|
|
11
|
+
green: "M201.89,131.89l98.4,170.21c.77,1.33-.19,3-1.73,3H2",
|
|
12
|
+
purple: "M126.96,261.83v-.02l98.28-170.19c.77-1.33,2.69-1.33,3.46,0l148.31,256.81",
|
|
13
|
+
yellow: "M277.02,261.81H80.44c-1.54,0-2.5-1.67-1.73-3L227,2",
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
/** Outer border paths (the pink/coral perimeter strokes) */
|
|
17
|
+
export const OUTER_PATHS = {
|
|
18
|
+
a: "M2,305.1l16.37,28.32c5.36,9.27,15.26,14.99,25.97,14.99l332.67.03",
|
|
19
|
+
b: "M227,2l-32.72.02c-10.71,0-20.61,5.72-25.96,15L2,305.1",
|
|
20
|
+
c: "M377.01,348.44l16.35-28.33c5.35-9.28,5.35-20.71,0-29.99L227,2",
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
/** Canonical viewBox for the logo SVG */
|
|
24
|
+
export const LOGO_VIEWBOX = "0 0 399.37 350.44";
|
|
25
|
+
|
|
26
|
+
/** Brand colors */
|
|
27
|
+
export const COLORS = {
|
|
28
|
+
green: "#00D08A",
|
|
29
|
+
purple: "#5415FF",
|
|
30
|
+
yellow: "#FFF14C",
|
|
31
|
+
pink: "#FF569D",
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// LOGO BASE COMPONENT
|
|
36
|
+
// =============================================================================
|
|
37
|
+
|
|
38
|
+
export interface LogoBaseProps {
|
|
39
|
+
/** Width of the SVG (default: 240) */
|
|
40
|
+
width?: number;
|
|
41
|
+
/** Height of the SVG (default: 240) */
|
|
42
|
+
height?: number;
|
|
43
|
+
/** Additional className for the container */
|
|
44
|
+
className?: string;
|
|
45
|
+
/** Stroke width for all paths (default: 4) */
|
|
46
|
+
strokeWidth?: number;
|
|
47
|
+
/** Enable glow animation (default: true) */
|
|
48
|
+
animated?: boolean;
|
|
49
|
+
/** Animation duration in seconds (default: 2) */
|
|
50
|
+
animationDuration?: number;
|
|
51
|
+
/** Stagger delay between path animations in seconds (default: 0.05) */
|
|
52
|
+
animationStagger?: number;
|
|
53
|
+
/** Use colored strokes vs monochrome (default: true) */
|
|
54
|
+
colored?: boolean;
|
|
55
|
+
/** Custom monochrome color (CSS value, e.g. "hsl(var(--muted-foreground) / 0.2)") */
|
|
56
|
+
monoColor?: string;
|
|
57
|
+
/** Enable blur/glow effect layer (default: true) */
|
|
58
|
+
withGlow?: boolean;
|
|
59
|
+
/** Unique ID prefix for filters (auto-generated if not provided) */
|
|
60
|
+
filterId?: string;
|
|
61
|
+
/** Announces the logo as a status (e.g. "loading") to assistive tech */
|
|
62
|
+
label?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const LogoBase: React.FC<LogoBaseProps> = ({
|
|
66
|
+
width = 240,
|
|
67
|
+
height = 240,
|
|
68
|
+
className,
|
|
69
|
+
strokeWidth = 4,
|
|
70
|
+
animated = true,
|
|
71
|
+
animationDuration = 2,
|
|
72
|
+
animationStagger = 0.02,
|
|
73
|
+
colored = true,
|
|
74
|
+
monoColor,
|
|
75
|
+
withGlow = true,
|
|
76
|
+
filterId,
|
|
77
|
+
label,
|
|
78
|
+
}) => {
|
|
79
|
+
// Generate unique filter ID to avoid conflicts when multiple logos are rendered
|
|
80
|
+
const rawId = React.useId();
|
|
81
|
+
const uniqueId = rawId.replace(/:/g, '');
|
|
82
|
+
const blurFilterId = filterId ?? `logo-blur-${uniqueId}`;
|
|
83
|
+
|
|
84
|
+
// Determine colors: mono with custom color, mono with currentColor, or full brand colors
|
|
85
|
+
const effectiveMonoColor = monoColor ?? "currentColor";
|
|
86
|
+
const innerColors = colored
|
|
87
|
+
? COLORS
|
|
88
|
+
: { green: effectiveMonoColor, purple: effectiveMonoColor, yellow: effectiveMonoColor };
|
|
89
|
+
const outerColor = colored ? COLORS.pink : effectiveMonoColor;
|
|
90
|
+
|
|
91
|
+
// CSS for animations (scoped to this instance)
|
|
92
|
+
const styles = `
|
|
93
|
+
/* Base stroke styling */
|
|
94
|
+
.logo-outer-${uniqueId},
|
|
95
|
+
.logo-inner-${uniqueId} {
|
|
96
|
+
fill: none;
|
|
97
|
+
stroke-width: ${strokeWidth};
|
|
98
|
+
stroke-miterlimit: 100;
|
|
99
|
+
stroke-linecap: round;
|
|
100
|
+
will-change: stroke-dashoffset, stroke-dasharray;
|
|
101
|
+
transform: translateZ(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.logo-outer-${uniqueId} {
|
|
105
|
+
stroke: ${outerColor};
|
|
106
|
+
${animated ? `
|
|
107
|
+
stroke-dasharray: 500, 500;
|
|
108
|
+
stroke-dashoffset: -500;
|
|
109
|
+
animation: logo-outside-${uniqueId} ${animationDuration}s ease-in-out infinite;
|
|
110
|
+
` : ""}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.logo-inner-${uniqueId} {
|
|
114
|
+
${animated ? `
|
|
115
|
+
stroke-dasharray: 500, 500;
|
|
116
|
+
stroke-dashoffset: -500;
|
|
117
|
+
animation: logo-inside-${uniqueId} ${animationDuration}s ease-in-out infinite;
|
|
118
|
+
` : ""}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.logo-outer-b-${uniqueId} { animation-delay: ${animationStagger}s; }
|
|
122
|
+
.logo-outer-c-${uniqueId} { animation-delay: ${animationStagger * 2}s; }
|
|
123
|
+
.logo-inner-purple-${uniqueId} { animation-delay: ${animationStagger}s; }
|
|
124
|
+
.logo-inner-yellow-${uniqueId} { animation-delay: ${animationStagger * 2}s; }
|
|
125
|
+
|
|
126
|
+
${animated ? `
|
|
127
|
+
@keyframes logo-outside-${uniqueId} {
|
|
128
|
+
0% { stroke-dashoffset: -500; }
|
|
129
|
+
45% { stroke-dashoffset: 0; }
|
|
130
|
+
55% { stroke-dashoffset: 0; }
|
|
131
|
+
100% { stroke-dashoffset: 500; }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@keyframes logo-inside-${uniqueId} {
|
|
135
|
+
0% { stroke-dashoffset: -500; }
|
|
136
|
+
45% { stroke-dashoffset: 0; }
|
|
137
|
+
55% { stroke-dashoffset: 0; }
|
|
138
|
+
100% { stroke-dashoffset: 500; }
|
|
139
|
+
}
|
|
140
|
+
` : ""}
|
|
141
|
+
|
|
142
|
+
.logo-blur-group-${uniqueId} {
|
|
143
|
+
will-change: filter;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.dark .logo-blur-group-${uniqueId},
|
|
147
|
+
[data-theme="dark"] .logo-blur-group-${uniqueId} {
|
|
148
|
+
mix-blend-mode: screen;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.dark .logo-blend-overlay-${uniqueId},
|
|
152
|
+
[data-theme="dark"] .logo-blend-overlay-${uniqueId} {
|
|
153
|
+
mix-blend-mode: lighten;
|
|
154
|
+
}
|
|
155
|
+
`;
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div className={`flex items-center justify-center ${className ?? ""}`} role={label ? "status" : undefined} aria-label={label}>
|
|
159
|
+
<svg
|
|
160
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
161
|
+
viewBox={LOGO_VIEWBOX}
|
|
162
|
+
width={width}
|
|
163
|
+
height={height}
|
|
164
|
+
style={{ overflow: "visible" }}
|
|
165
|
+
>
|
|
166
|
+
<defs>
|
|
167
|
+
<filter id={blurFilterId}>
|
|
168
|
+
<feGaussianBlur stdDeviation="5" />
|
|
169
|
+
<feColorMatrix
|
|
170
|
+
type="matrix"
|
|
171
|
+
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.8 0"
|
|
172
|
+
/>
|
|
173
|
+
</filter>
|
|
174
|
+
</defs>
|
|
175
|
+
|
|
176
|
+
{/* Static base layer — always visible so the logo never goes blank */}
|
|
177
|
+
{animated && (
|
|
178
|
+
<g opacity={withGlow ? "0.15" : "1"}>
|
|
179
|
+
{[INNER_PATHS.green, INNER_PATHS.purple, INNER_PATHS.yellow, OUTER_PATHS.a, OUTER_PATHS.b, OUTER_PATHS.c].map((d, i) => (
|
|
180
|
+
<path key={i} d={d} fill="none" stroke={withGlow ? effectiveMonoColor : "hsl(var(--border))"} strokeWidth={strokeWidth} strokeMiterlimit={100} strokeLinecap="round" />
|
|
181
|
+
))}
|
|
182
|
+
</g>
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
{/* Glow/blur layer */}
|
|
186
|
+
{withGlow && (
|
|
187
|
+
<g filter={`url(#${blurFilterId})`} className={`logo-blur-group-${uniqueId}`}>
|
|
188
|
+
<path className={`logo-inner-${uniqueId}`} style={{ stroke: innerColors.green }} d={INNER_PATHS.green} />
|
|
189
|
+
<path className={`logo-inner-${uniqueId} logo-inner-purple-${uniqueId}`} style={{ stroke: innerColors.purple }} d={INNER_PATHS.purple} />
|
|
190
|
+
<path className={`logo-inner-${uniqueId} logo-inner-yellow-${uniqueId}`} style={{ stroke: innerColors.yellow }} d={INNER_PATHS.yellow} />
|
|
191
|
+
<path className={`logo-outer-${uniqueId}`} d={OUTER_PATHS.a} />
|
|
192
|
+
<path className={`logo-outer-${uniqueId} logo-outer-b-${uniqueId}`} d={OUTER_PATHS.b} />
|
|
193
|
+
<path className={`logo-outer-${uniqueId} logo-outer-c-${uniqueId}`} d={OUTER_PATHS.c} />
|
|
194
|
+
</g>
|
|
195
|
+
)}
|
|
196
|
+
|
|
197
|
+
{/* Main visible layer */}
|
|
198
|
+
<g>
|
|
199
|
+
<path className={`logo-inner-${uniqueId}${withGlow ? ` logo-blend-overlay-${uniqueId}` : ""}`} style={{ stroke: innerColors.green }} d={INNER_PATHS.green} />
|
|
200
|
+
<path className={`logo-inner-${uniqueId} logo-inner-purple-${uniqueId}${withGlow ? ` logo-blend-overlay-${uniqueId}` : ""}`} style={{ stroke: innerColors.purple }} d={INNER_PATHS.purple} />
|
|
201
|
+
<path className={`logo-inner-${uniqueId} logo-inner-yellow-${uniqueId}${withGlow ? ` logo-blend-overlay-${uniqueId}` : ""}`} style={{ stroke: innerColors.yellow }} d={INNER_PATHS.yellow} />
|
|
202
|
+
<path className={`logo-outer-${uniqueId}`} d={OUTER_PATHS.a} />
|
|
203
|
+
<path className={`logo-outer-${uniqueId} logo-outer-b-${uniqueId}`} d={OUTER_PATHS.b} />
|
|
204
|
+
<path className={`logo-outer-${uniqueId} logo-outer-c-${uniqueId}`} d={OUTER_PATHS.c} />
|
|
205
|
+
</g>
|
|
206
|
+
</svg>
|
|
207
|
+
|
|
208
|
+
<style>{styles}</style>
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export default LogoBase;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import LogoBase from "./logo-base";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Static colored logo (no animation).
|
|
6
|
+
* Used for nav headers, small brand marks, etc.
|
|
7
|
+
*/
|
|
8
|
+
function LogoColor(props: React.SVGProps<SVGSVGElement> & { size?: number }) {
|
|
9
|
+
const { size = 18, className, ...rest } = props;
|
|
10
|
+
return (
|
|
11
|
+
<LogoBase
|
|
12
|
+
width={size}
|
|
13
|
+
height={size}
|
|
14
|
+
className={className}
|
|
15
|
+
strokeWidth={0.5}
|
|
16
|
+
animated={false}
|
|
17
|
+
colored={true}
|
|
18
|
+
withGlow={false}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default LogoColor;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
function LogoSvg(props: React.SVGProps<SVGSVGElement>) {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
width={18}
|
|
7
|
+
height={16}
|
|
8
|
+
viewBox="0 0 385.87 339"
|
|
9
|
+
fill="none"
|
|
10
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
11
|
+
{...props}
|
|
12
|
+
>
|
|
13
|
+
<defs>
|
|
14
|
+
<style>
|
|
15
|
+
{`.logo-full-1{fill:url(#linear-gradient-3)}
|
|
16
|
+
.logo-full-2{fill:url(#linear-gradient-2)}
|
|
17
|
+
.logo-full-3{fill:url(#linear-gradient)}`}
|
|
18
|
+
</style>
|
|
19
|
+
<linearGradient
|
|
20
|
+
id="linear-gradient"
|
|
21
|
+
x1=".1"
|
|
22
|
+
y1="145.5"
|
|
23
|
+
x2="278.64"
|
|
24
|
+
y2="145.5"
|
|
25
|
+
gradientUnits="userSpaceOnUse"
|
|
26
|
+
>
|
|
27
|
+
<stop offset="0" stopColor="#fff14c" />
|
|
28
|
+
<stop offset="1" stopColor="#ff569d" />
|
|
29
|
+
</linearGradient>
|
|
30
|
+
<linearGradient
|
|
31
|
+
id="linear-gradient-2"
|
|
32
|
+
x1="235.28"
|
|
33
|
+
y1="411.28"
|
|
34
|
+
x2="95.92"
|
|
35
|
+
y2="169.91"
|
|
36
|
+
gradientUnits="userSpaceOnUse"
|
|
37
|
+
>
|
|
38
|
+
<stop offset="0" stopColor="#f08aff" />
|
|
39
|
+
<stop offset="1" stopColor="#5415ff" />
|
|
40
|
+
</linearGradient>
|
|
41
|
+
<linearGradient
|
|
42
|
+
id="linear-gradient-3"
|
|
43
|
+
x1="349.77"
|
|
44
|
+
y1="72.37"
|
|
45
|
+
x2="210.48"
|
|
46
|
+
y2="313.61"
|
|
47
|
+
gradientUnits="userSpaceOnUse"
|
|
48
|
+
>
|
|
49
|
+
<stop offset="0" stopColor="#fff14c" />
|
|
50
|
+
<stop offset="1" stopColor="#00d08a" />
|
|
51
|
+
</linearGradient>
|
|
52
|
+
</defs>
|
|
53
|
+
<g id="Layer_1-2">
|
|
54
|
+
<path
|
|
55
|
+
className="logo-full-3"
|
|
56
|
+
d="M.1,291C27.32,243.84,129.02,67.7,159.45,14.99c5.36-9.28,15.26-14.99,25.98-14.99h33.41c-32.88,56.95-126.96,219.91-140.88,244.01-.77,1.33.2,2.99,1.74,2.99h173.54l25.4,44H.1Z"
|
|
57
|
+
/>
|
|
58
|
+
<path
|
|
59
|
+
className="logo-full-2"
|
|
60
|
+
d="M16.69,323.97L0,295h282.12c1.54,0,2.5-1.67,1.73-3l-86.84-150.43,25.37-43.94,42.16,73.03c33.95,58.81,73.13,126.68,97.19,168.34H42.69c-10.73,0-20.64-5.73-26-15.03Z"
|
|
61
|
+
/>
|
|
62
|
+
<path
|
|
63
|
+
className="logo-full-1"
|
|
64
|
+
d="M220.64,92.63c-10.85,18.78-69.6,120.54-86.82,150.37h-50.73C119.17,180.52,187.08,62.88,222.37,1.76c24.91,43.15,72.98,126.41,113.71,196.97l45.76,79.27c5.35,9.27,5.36,20.69.01,29.97l-16.7,29c-21.16-36.64-126.27-218.71-141.06-244.33-.77-1.33-2.69-1.34-3.46,0Z"
|
|
65
|
+
/>
|
|
66
|
+
</g>
|
|
67
|
+
</svg>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default LogoSvg
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import LogoBase from "./logo-base";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Monochrome outline logo (uses currentColor, no animation).
|
|
6
|
+
* Adapts to text color context.
|
|
7
|
+
*/
|
|
8
|
+
function LogoSvg(props: React.SVGProps<SVGSVGElement> & { size?: number, strokeWidth?: number }) {
|
|
9
|
+
const { size = 18, strokeWidth = 2, className, ...rest } = props;
|
|
10
|
+
return (
|
|
11
|
+
<LogoBase
|
|
12
|
+
width={size}
|
|
13
|
+
height={size}
|
|
14
|
+
className={className}
|
|
15
|
+
strokeWidth={strokeWidth}
|
|
16
|
+
animated={false}
|
|
17
|
+
colored={false}
|
|
18
|
+
withGlow={false}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default LogoSvg;
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import type { MenuDTO, MenuItem } from "@inferencesh/sdk";
|
|
2
|
+
|
|
3
|
+
/** The part of a menu the navigation reads. */
|
|
4
|
+
export type Menu = Pick<MenuDTO, "items">;
|
|
5
|
+
import { cn } from "cn";
|
|
6
|
+
import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
|
|
7
|
+
import React, { ComponentType, ReactNode, useState } from "react";
|
|
8
|
+
|
|
9
|
+
export interface LinkProps {
|
|
10
|
+
href: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MenuNavigationProps {
|
|
16
|
+
menu?: Menu;
|
|
17
|
+
basePath: string;
|
|
18
|
+
className?: string;
|
|
19
|
+
currentSlug: string;
|
|
20
|
+
LinkComponent: ComponentType<LinkProps>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check if any descendant has the current slug (for auto-expanding)
|
|
24
|
+
function hasActiveDescendant(item: MenuItem, currentSlug: string): boolean {
|
|
25
|
+
if (item.slug === currentSlug) return true;
|
|
26
|
+
if (item.children) {
|
|
27
|
+
return item.children.some(child => hasActiveDescendant(child, currentSlug));
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Recursive menu item renderer for nested items
|
|
33
|
+
interface MenuItemRendererProps {
|
|
34
|
+
item: MenuItem;
|
|
35
|
+
basePath: string;
|
|
36
|
+
currentSlug: string;
|
|
37
|
+
LinkComponent: ComponentType<LinkProps>;
|
|
38
|
+
depth: number; // 0 = top level, 1 = first children, 2 = second children, etc.
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function MenuItemRenderer({
|
|
42
|
+
item,
|
|
43
|
+
basePath,
|
|
44
|
+
currentSlug,
|
|
45
|
+
LinkComponent: Link,
|
|
46
|
+
depth,
|
|
47
|
+
}: MenuItemRendererProps) {
|
|
48
|
+
const hasChildren = item.children && item.children.length > 0;
|
|
49
|
+
const isActive = currentSlug === item.slug;
|
|
50
|
+
const hasActiveChild = hasChildren && hasActiveDescendant(item, currentSlug);
|
|
51
|
+
|
|
52
|
+
// Auto-expand if this item or any descendant is active
|
|
53
|
+
const [isExpanded, setIsExpanded] = useState(
|
|
54
|
+
item.expanded !== undefined ? item.expanded : hasActiveChild
|
|
55
|
+
);
|
|
56
|
+
const panelId = `menu-${React.useId()}`;
|
|
57
|
+
|
|
58
|
+
// Calculate indentation based on depth
|
|
59
|
+
const paddingLeft = depth === 0 ? "px-3" : depth === 1 ? "pl-3 pr-3" : "pl-6 pr-3";
|
|
60
|
+
|
|
61
|
+
// Items with children act as collapsible groups
|
|
62
|
+
if (hasChildren) {
|
|
63
|
+
return (
|
|
64
|
+
<li>
|
|
65
|
+
<button
|
|
66
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
67
|
+
aria-expanded={isExpanded}
|
|
68
|
+
aria-controls={panelId}
|
|
69
|
+
className={cn(
|
|
70
|
+
"cursor-pointer w-full group flex items-center justify-between gap-2 text-sm py-2 rounded-lg transition-all duration-150 hover:bg-muted/70",
|
|
71
|
+
paddingLeft,
|
|
72
|
+
isActive
|
|
73
|
+
? "bg-brand-pink/10 text-brand-pink font-medium"
|
|
74
|
+
: hasActiveChild
|
|
75
|
+
? "text-foreground font-medium"
|
|
76
|
+
: "text-muted-foreground hover:text-foreground hover:bg-muted/70"
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
79
|
+
<span className="flex items-center gap-2">
|
|
80
|
+
<span
|
|
81
|
+
className={cn(
|
|
82
|
+
"w-1 h-1 rounded-full transition-all",
|
|
83
|
+
isActive
|
|
84
|
+
? "bg-brand-pink w-1.5 h-1.5"
|
|
85
|
+
: hasActiveChild
|
|
86
|
+
? "bg-foreground/50"
|
|
87
|
+
: "bg-muted-foreground/30 group-hover:bg-muted-foreground/50"
|
|
88
|
+
)}
|
|
89
|
+
/>
|
|
90
|
+
{item.label}
|
|
91
|
+
</span>
|
|
92
|
+
<ChevronDown
|
|
93
|
+
className={cn(
|
|
94
|
+
"w-4 h-4 text-muted-foreground transition-transform duration-200",
|
|
95
|
+
isExpanded ? "rotate-0" : "-rotate-90"
|
|
96
|
+
)}
|
|
97
|
+
/>
|
|
98
|
+
</button>
|
|
99
|
+
{/* Rendered whether or not the group is open, and hidden with
|
|
100
|
+
CSS when it is closed. Dropping the children from the markup
|
|
101
|
+
also drops them from the page a crawler sees: a page in a
|
|
102
|
+
closed group is then linked from nowhere but the sitemap. */}
|
|
103
|
+
<ul id={panelId} hidden={!isExpanded} className="mt-0.5 space-y-0.5">
|
|
104
|
+
{item.children!.map((child) => (
|
|
105
|
+
<MenuItemRenderer
|
|
106
|
+
key={child.id || child.slug}
|
|
107
|
+
item={child}
|
|
108
|
+
basePath={basePath}
|
|
109
|
+
currentSlug={currentSlug}
|
|
110
|
+
LinkComponent={Link}
|
|
111
|
+
depth={depth + 1}
|
|
112
|
+
/>
|
|
113
|
+
))}
|
|
114
|
+
</ul>
|
|
115
|
+
</li>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Regular link item (no children)
|
|
120
|
+
return (
|
|
121
|
+
<li>
|
|
122
|
+
<Link
|
|
123
|
+
href={`${basePath}/${item.slug}`}
|
|
124
|
+
className={cn(
|
|
125
|
+
"group flex items-center gap-2 text-sm py-2 rounded-lg transition-all duration-150",
|
|
126
|
+
paddingLeft,
|
|
127
|
+
isActive
|
|
128
|
+
? "bg-brand-pink/10 text-brand-pink font-medium"
|
|
129
|
+
: "text-muted-foreground hover:text-foreground hover:bg-muted/70"
|
|
130
|
+
)}
|
|
131
|
+
>
|
|
132
|
+
<span
|
|
133
|
+
className={cn(
|
|
134
|
+
"w-1 h-1 rounded-full transition-all",
|
|
135
|
+
isActive
|
|
136
|
+
? "bg-brand-pink w-1.5 h-1.5"
|
|
137
|
+
: "bg-muted-foreground/30 group-hover:bg-muted-foreground/50"
|
|
138
|
+
)}
|
|
139
|
+
/>
|
|
140
|
+
{item.label}
|
|
141
|
+
</Link>
|
|
142
|
+
</li>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Sidebar navigation component
|
|
147
|
+
export function MenuNavigation({
|
|
148
|
+
menu,
|
|
149
|
+
basePath,
|
|
150
|
+
className,
|
|
151
|
+
currentSlug,
|
|
152
|
+
LinkComponent: Link,
|
|
153
|
+
}: MenuNavigationProps) {
|
|
154
|
+
if (!menu?.items) return null;
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<nav className={cn("space-y-6", className)}>
|
|
158
|
+
{menu.items.map((group: MenuItem, index: number) => (
|
|
159
|
+
<div key={group.id || group.label}>
|
|
160
|
+
{group.is_group ? (
|
|
161
|
+
<div className="space-y-1">
|
|
162
|
+
<div className="font-semibold text-xs tracking-wider text-muted-foreground/70">
|
|
163
|
+
{group.slug ? (() => {
|
|
164
|
+
const isActive = currentSlug === group.slug;
|
|
165
|
+
return (
|
|
166
|
+
<Link
|
|
167
|
+
href={`${basePath}/${group.slug}`}
|
|
168
|
+
className={cn(
|
|
169
|
+
"group flex items-center gap-2 text-sm rounded-lg transition-all duration-150 p-2",
|
|
170
|
+
isActive
|
|
171
|
+
? "text-brand-pink hover:bg-brand-pink/10"
|
|
172
|
+
: "text-muted-foreground hover:text-foreground hover:bg-muted/70"
|
|
173
|
+
)}
|
|
174
|
+
>
|
|
175
|
+
{group.label}
|
|
176
|
+
</Link>
|
|
177
|
+
);
|
|
178
|
+
})() : (
|
|
179
|
+
<span>{group.label}</span>
|
|
180
|
+
)}
|
|
181
|
+
|
|
182
|
+
</div>
|
|
183
|
+
<ul className="space-y-0.5">
|
|
184
|
+
{group.children?.map((item: MenuItem) => (
|
|
185
|
+
<MenuItemRenderer
|
|
186
|
+
key={item.id || item.slug}
|
|
187
|
+
item={item}
|
|
188
|
+
basePath={basePath}
|
|
189
|
+
currentSlug={currentSlug}
|
|
190
|
+
LinkComponent={Link}
|
|
191
|
+
depth={1}
|
|
192
|
+
/>
|
|
193
|
+
))}
|
|
194
|
+
</ul>
|
|
195
|
+
</div>
|
|
196
|
+
) : (
|
|
197
|
+
<Link
|
|
198
|
+
href={`${basePath}/${group.slug}`}
|
|
199
|
+
className={cn(
|
|
200
|
+
"group flex items-center gap-2 text-sm font-medium py-2 px-3 rounded-lg transition-all duration-150",
|
|
201
|
+
currentSlug === group.slug
|
|
202
|
+
? "bg-brand-pink/10 text-brand-pink"
|
|
203
|
+
: "text-muted-foreground hover:text-foreground hover:bg-muted/70"
|
|
204
|
+
)}
|
|
205
|
+
>
|
|
206
|
+
<span
|
|
207
|
+
className={cn(
|
|
208
|
+
"w-1.5 h-1.5 rounded-full transition-all",
|
|
209
|
+
currentSlug === group.slug
|
|
210
|
+
? "bg-brand-pink"
|
|
211
|
+
: "bg-muted-foreground/30 group-hover:bg-muted-foreground/50"
|
|
212
|
+
)}
|
|
213
|
+
/>
|
|
214
|
+
{group.label}
|
|
215
|
+
</Link>
|
|
216
|
+
)}
|
|
217
|
+
{/* Subtle divider between groups */}
|
|
218
|
+
{index < menu.items.length - 1 && (
|
|
219
|
+
<div className="mt-6 border-t border-border/50" />
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
222
|
+
))
|
|
223
|
+
}
|
|
224
|
+
</nav >
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Get all pages in order from menu (recursive to support any nesting depth)
|
|
229
|
+
function collectPagesFromItems(items: MenuItem[], pages: { slug: string; label: string }[]) {
|
|
230
|
+
for (const item of items) {
|
|
231
|
+
if (item.slug) {
|
|
232
|
+
pages.push({ slug: item.slug, label: item.label });
|
|
233
|
+
}
|
|
234
|
+
if (item.children && item.children.length > 0) {
|
|
235
|
+
collectPagesFromItems(item.children, pages);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function getAllPagesFromMenu(menu: Menu): { slug: string; label: string }[] {
|
|
241
|
+
if (!menu?.items) return [];
|
|
242
|
+
|
|
243
|
+
const pages: { slug: string; label: string }[] = [];
|
|
244
|
+
|
|
245
|
+
for (const group of menu.items) {
|
|
246
|
+
if (group.slug) {
|
|
247
|
+
pages.push({ slug: group.slug, label: group.label });
|
|
248
|
+
}
|
|
249
|
+
if (group.children && group.children.length > 0) {
|
|
250
|
+
collectPagesFromItems(group.children, pages);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return pages;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface PageNavigationProps {
|
|
258
|
+
menu?: Menu;
|
|
259
|
+
basePath: string;
|
|
260
|
+
currentSlug: string;
|
|
261
|
+
LinkComponent: ComponentType<LinkProps>;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Prev/Next navigation component
|
|
265
|
+
export function PageNavigation({
|
|
266
|
+
menu,
|
|
267
|
+
basePath,
|
|
268
|
+
currentSlug,
|
|
269
|
+
LinkComponent: Link,
|
|
270
|
+
}: PageNavigationProps) {
|
|
271
|
+
if (!menu) return null;
|
|
272
|
+
|
|
273
|
+
const pages = getAllPagesFromMenu(menu);
|
|
274
|
+
const currentIndex = pages.findIndex((p) => p.slug === currentSlug);
|
|
275
|
+
|
|
276
|
+
const prevPage = currentIndex > 0 ? pages[currentIndex - 1] : null;
|
|
277
|
+
const nextPage =
|
|
278
|
+
currentIndex < pages.length - 1 ? pages[currentIndex + 1] : null;
|
|
279
|
+
|
|
280
|
+
if (!prevPage && !nextPage) return null;
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<div className="grid grid-cols-2 gap-4 pt-10 mt-10 border-t border-border">
|
|
284
|
+
{prevPage ? (
|
|
285
|
+
<Link
|
|
286
|
+
href={`${basePath}/${prevPage.slug}`}
|
|
287
|
+
className="group flex flex-col gap-2 p-4 rounded-xl border border-border hover:border-brand-pink/40 hover:bg-brand-pink/[0.02] transition-all"
|
|
288
|
+
>
|
|
289
|
+
<span className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
290
|
+
<ChevronLeft className="w-3 h-3" />
|
|
291
|
+
previous
|
|
292
|
+
</span>
|
|
293
|
+
<span className="text-sm font-medium text-foreground group-hover:text-brand-pink transition-colors">
|
|
294
|
+
{prevPage.label}
|
|
295
|
+
</span>
|
|
296
|
+
</Link>
|
|
297
|
+
) : (
|
|
298
|
+
<div />
|
|
299
|
+
)}
|
|
300
|
+
|
|
301
|
+
{nextPage ? (
|
|
302
|
+
<Link
|
|
303
|
+
href={`${basePath}/${nextPage.slug}`}
|
|
304
|
+
className="group flex flex-col gap-2 p-4 rounded-xl border border-border hover:border-brand-pink/40 hover:bg-brand-pink/[0.02] transition-all text-right"
|
|
305
|
+
>
|
|
306
|
+
<span className="flex items-center justify-end gap-1 text-xs text-muted-foreground">
|
|
307
|
+
next
|
|
308
|
+
<ChevronRight className="w-3 h-3" />
|
|
309
|
+
</span>
|
|
310
|
+
<span className="text-sm font-medium text-foreground group-hover:text-brand-pink transition-colors">
|
|
311
|
+
{nextPage.label}
|
|
312
|
+
</span>
|
|
313
|
+
</Link>
|
|
314
|
+
) : (
|
|
315
|
+
<div />
|
|
316
|
+
)}
|
|
317
|
+
</div>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface MenuSidebarProps {
|
|
322
|
+
menu?: Menu;
|
|
323
|
+
basePath: string;
|
|
324
|
+
title?: string;
|
|
325
|
+
currentSlug: string;
|
|
326
|
+
LinkComponent: ComponentType<LinkProps>;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Wrapper for sticky sidebar
|
|
330
|
+
export function MenuSidebar({
|
|
331
|
+
menu,
|
|
332
|
+
basePath,
|
|
333
|
+
title,
|
|
334
|
+
currentSlug,
|
|
335
|
+
LinkComponent: Link,
|
|
336
|
+
}: MenuSidebarProps) {
|
|
337
|
+
if (!menu) return null;
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<div className="sticky top-0 overflow-y-auto pr-2 -mr-2 py-2">
|
|
341
|
+
{title && (
|
|
342
|
+
<div className="px-2 mb-6">
|
|
343
|
+
<Link
|
|
344
|
+
href={basePath}
|
|
345
|
+
className="text-lg font-bold text-foreground hover:text-brand-pink transition-colors"
|
|
346
|
+
>
|
|
347
|
+
{title}
|
|
348
|
+
</Link>
|
|
349
|
+
</div>
|
|
350
|
+
)}
|
|
351
|
+
<MenuNavigation
|
|
352
|
+
menu={menu}
|
|
353
|
+
basePath={basePath}
|
|
354
|
+
currentSlug={currentSlug}
|
|
355
|
+
LinkComponent={Link}
|
|
356
|
+
/>
|
|
357
|
+
</div>
|
|
358
|
+
);
|
|
359
|
+
}
|