Skip to content

Commit de3e7cc

Browse files
committed
refactor(types): improve typing for capability grid, capability card, and terminal utilities
1 parent 70e27e4 commit de3e7cc

File tree

3 files changed

+73
-33
lines changed

3 files changed

+73
-33
lines changed

components/sections/capabilities/capability-grid.tsx

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,8 @@ export const CapabilityGrid = React.memo(function CapabilityGrid({
214214
const base = toCssSize(cardHeight);
215215
const mobile = toCssSize(cardHeightMobile);
216216
const style: React.CSSProperties = {};
217-
if (mobile) (style as any)["--cap-h-mobile"] = mobile;
218-
if (base) (style as any)["--cap-h-base"] = base;
217+
if (mobile) (style as Record<string, string>)["--cap-h-mobile"] = mobile;
218+
if (base) (style as Record<string, string>)["--cap-h-base"] = base;
219219
return style;
220220
}, [hasResponsiveHeight, cardHeight, cardHeightMobile, toCssSize]);
221221

@@ -227,25 +227,35 @@ export const CapabilityGrid = React.memo(function CapabilityGrid({
227227
[hasResponsiveHeight, cardHeight, cardMinHeight]
228228
);
229229

230-
if (alignLastRowCenter) {
230+
// Precompute layout variants and style (avoid conditional hook usage)
231+
const flexCenterParams = React.useMemo(() => {
232+
if (!alignLastRowCenter) return null;
231233
const gapValue = resolveGapValue(gap);
232234
const colsMobile = mobileColumns || 1;
233235
const colsTablet = tabletColumns || colsMobile;
234236
const colsDesktop = desktopColumns || colsTablet;
235-
236237
const makeBasis = (cols: number) =>
237238
`calc((100% - ( ${cols} - 1 ) * var(--gap)) / ${cols})`;
238-
const style: React.CSSProperties = React.useMemo(
239-
() => ({
240-
["--gap" as any]: gapValue,
241-
["--basis-mobile" as any]: makeBasis(colsMobile),
242-
["--basis-tablet" as any]: makeBasis(colsTablet),
243-
["--basis-desktop" as any]: makeBasis(colsDesktop),
244-
...(containerHeightVars || {}),
245-
}),
246-
[gapValue, colsMobile, colsTablet, colsDesktop, containerHeightVars]
247-
);
239+
const style: React.CSSProperties = {
240+
...(containerHeightVars || {}),
241+
};
242+
const varStyle = style as Record<string, string>;
243+
varStyle["--gap"] = gapValue;
244+
varStyle["--basis-mobile"] = makeBasis(colsMobile);
245+
varStyle["--basis-tablet"] = makeBasis(colsTablet);
246+
varStyle["--basis-desktop"] = makeBasis(colsDesktop);
247+
return { style, colsMobile, colsTablet, colsDesktop };
248+
}, [
249+
alignLastRowCenter,
250+
gap,
251+
mobileColumns,
252+
tabletColumns,
253+
desktopColumns,
254+
containerHeightVars,
255+
]);
248256

257+
if (flexCenterParams) {
258+
const { style, colsMobile, colsTablet, colsDesktop } = flexCenterParams;
249259
return (
250260
<ul
251261
className={cn(

components/ui/capability-card.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export interface CapabilityCardProps
2020
size?: "compact" | "default";
2121
interactive?: boolean;
2222
iconClassName?: string;
23-
as?: React.ElementType;
23+
as?: keyof React.JSX.IntrinsicElements | React.ComponentType<unknown>; // limited escape hatch; if custom component supply required props
2424
/** Enables subtle radial glow */
2525
glow?: boolean;
2626
}
@@ -58,7 +58,7 @@ const CapabilityCardBase = React.forwardRef<
5858
},
5959
ref
6060
) {
61-
const Component: any = as;
61+
const Component = as as React.ElementType;
6262
const content = (
6363
<Component
6464
ref={ref}

components/ui/shadcn-io/terminal/index.tsx

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,48 @@ const BASE_TEXT_CLASS = "text-sm font-normal tracking-tight font-mono";
2121
*/
2222
const isSpecificComponent = (
2323
element: React.ReactElement,
24-
target: React.ComponentType<any>,
24+
target: React.ElementType,
2525
displayName: string
2626
): boolean => {
27-
const t: any = element.type;
27+
const LAZY = Symbol.for("react.lazy");
28+
29+
const getTypeName = (val: unknown): string | undefined => {
30+
if (typeof val === "function") {
31+
const fn = val as { displayName?: string; name?: string };
32+
return fn.displayName || fn.name;
33+
}
34+
if (val && typeof val === "object" && "displayName" in (val as object)) {
35+
const dn = (val as { displayName?: unknown }).displayName;
36+
return typeof dn === "string" ? dn : undefined;
37+
}
38+
return undefined;
39+
};
40+
41+
const isLazy = (
42+
val: unknown
43+
): val is { $$typeof: symbol; _payload?: { value?: unknown } } => {
44+
return (
45+
typeof val === "object" &&
46+
val !== null &&
47+
"$$typeof" in (val as object) &&
48+
(val as { $$typeof?: unknown }).$$typeof === LAZY
49+
);
50+
};
51+
52+
const t = element.type as unknown;
2853
if (!t) return false;
2954

3055
// Direct reference or displayName/name match
3156
if (t === target) return true;
32-
const tName =
33-
(typeof t === "function" && (t.displayName || t.name)) || t.displayName;
57+
const tName = getTypeName(t);
3458
if (tName === displayName) return true;
3559

3660
// Minimal lazy component support (mirrors previous behavior)
37-
if (t && t.$$typeof === Symbol.for("react.lazy")) {
61+
if (isLazy(t)) {
3862
const payload = t._payload;
3963
const value = payload?.value;
4064
if (typeof value === "function") {
41-
const vName = value.displayName || value.name;
65+
const vName = getTypeName(value);
4266
if (value === target || vName === displayName) return true;
4367
}
4468
// Turbopack/SSR array metadata case
@@ -73,7 +97,10 @@ const extractPlainText = (node: React.ReactNode): string => {
7397
return (node as React.ReactNode[]).map(extractPlainText).join("");
7498
default:
7599
return React.isValidElement(node)
76-
? extractPlainText((node as React.ReactElement<any>).props?.children)
100+
? extractPlainText(
101+
(node as React.ReactElement<{ children?: React.ReactNode }>).props
102+
?.children
103+
)
77104
: "";
78105
}
79106
};
@@ -116,13 +143,13 @@ const parseTypingChildren = (
116143
if (!React.isValidElement(child)) continue;
117144
if (!leadingCharComponent && isLeadingCharComponent(child)) {
118145
leadingCharComponent = child;
119-
const c = child as React.ReactElement<any>;
146+
const c = child as React.ReactElement<{ children?: React.ReactNode }>;
120147
leadingCharText = extractPlainText(c.props?.children);
121148
continue;
122149
}
123150
if (!animatedContentComponent && isAnimatedContentComponent(child)) {
124151
animatedContentComponent = child;
125-
const c = child as React.ReactElement<any>;
152+
const c = child as React.ReactElement<{ children?: React.ReactNode }>;
126153
const raw = extractPlainText(c.props?.children);
127154
contentText = raw && !raw.startsWith(" ") ? " " + raw : raw;
128155
}
@@ -255,6 +282,7 @@ export const TypingAnimation = ({
255282
const animatedContentComponent = parsed.animatedContentComponent;
256283
const leadingCharText = parsed.leadingCharText;
257284
const fullTextToAnimate = parsed.fullText;
285+
const hasLeadingChar = Boolean(leadingCharComponent);
258286

259287
useEffect(() => {
260288
const startTimeout = setTimeout(() => {
@@ -271,7 +299,7 @@ export const TypingAnimation = ({
271299
if (i < fullTextToAnimate.length) {
272300
const currentText = fullTextToAnimate.substring(0, i + 1);
273301

274-
if (leadingCharComponent && i < leadingCharText.length) {
302+
if (hasLeadingChar && i < leadingCharText.length) {
275303
setDisplayedLeadingChar(currentText);
276304
setDisplayedText("");
277305
} else {
@@ -288,7 +316,7 @@ export const TypingAnimation = ({
288316
return () => {
289317
clearInterval(typingEffect);
290318
};
291-
}, [fullTextToAnimate, leadingCharText, duration, started]);
319+
}, [fullTextToAnimate, leadingCharText, duration, started, hasLeadingChar]);
292320

293321
// Nothing to animate
294322
if (!fullTextToAnimate) {
@@ -302,14 +330,16 @@ export const TypingAnimation = ({
302330
{...props}
303331
>
304332
{leadingCharComponent
305-
? React.cloneElement(leadingCharComponent as any, {
306-
children: displayedLeadingChar,
307-
})
333+
? React.cloneElement<LeadingCharProps>(
334+
leadingCharComponent as React.ReactElement<LeadingCharProps>,
335+
{ children: displayedLeadingChar }
336+
)
308337
: null}
309338
{animatedContentComponent
310-
? React.cloneElement(animatedContentComponent as any, {
311-
children: displayedText,
312-
})
339+
? React.cloneElement<AnimatedContentProps>(
340+
animatedContentComponent as React.ReactElement<AnimatedContentProps>,
341+
{ children: displayedText }
342+
)
313343
: typeof children === "string"
314344
? fullTextToAnimate.substring(
315345
0,

0 commit comments

Comments
 (0)