mirror of
https://github.com/phranck/TUIkit.git
synced 2026-06-20 09:54:37 +00:00
147 lines
5.3 KiB
TypeScript
147 lines
5.3 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
SFSymbol,
|
|
SFAppleTerminalFill,
|
|
SFPaintbrushFill,
|
|
SFKeyboardFill,
|
|
SFSquareStack3dUpFill,
|
|
SFBoltFill,
|
|
SFDocumentFill,
|
|
SFEyeFill,
|
|
SFArrowLeftArrowRight,
|
|
SFSwift,
|
|
SFCheckmarkCircleFill,
|
|
SFBookFill,
|
|
SFChevronLeftForwardslashChevronRight,
|
|
SFChevronRight,
|
|
SFClockFill,
|
|
SFCalendar,
|
|
SFArrowClockwise,
|
|
SFChartBarFill,
|
|
SFNumberCircleFill,
|
|
SFStarFill,
|
|
SFArrowTriangleheadPull,
|
|
SFArrowTriangleheadMerge,
|
|
SFArrowTriangleheadBranch,
|
|
SFTagFill,
|
|
SFPerson2Fill,
|
|
SFShippingboxFill,
|
|
SFListBullet,
|
|
SFServerRack,
|
|
SFBubbleLeftAndExclamationmarkBubbleRightFill,
|
|
} from "sf-symbols-lib/hierarchical";
|
|
|
|
/** Maps of icon names to SF Symbol constants for type-safe usage. */
|
|
const sfIcons = {
|
|
terminal: SFAppleTerminalFill,
|
|
paintbrush: SFPaintbrushFill,
|
|
keyboard: SFKeyboardFill,
|
|
stack: SFSquareStack3dUpFill,
|
|
bolt: SFBoltFill,
|
|
document: SFDocumentFill,
|
|
eye: SFEyeFill,
|
|
arrows: SFArrowLeftArrowRight,
|
|
swift: SFSwift,
|
|
checkmark: SFCheckmarkCircleFill,
|
|
book: SFBookFill,
|
|
code: SFChevronLeftForwardslashChevronRight,
|
|
chevronRight: SFChevronRight,
|
|
clock: SFClockFill,
|
|
calendar: SFCalendar,
|
|
refresh: SFArrowClockwise,
|
|
chart: SFChartBarFill,
|
|
numberCircle: SFNumberCircleFill,
|
|
star: SFStarFill,
|
|
pullRequest: SFArrowTriangleheadPull,
|
|
merge: SFArrowTriangleheadMerge,
|
|
branch: SFArrowTriangleheadBranch,
|
|
tag: SFTagFill,
|
|
person2: SFPerson2Fill,
|
|
shippingbox: SFShippingboxFill,
|
|
listBullet: SFListBullet,
|
|
serverRack: SFServerRack,
|
|
issue: SFBubbleLeftAndExclamationmarkBubbleRightFill,
|
|
} as const;
|
|
|
|
/** Custom SVG icons not available in SF Symbols. */
|
|
const customIcons = {
|
|
/** Two overlapping rectangles — standard copy-to-clipboard metaphor. */
|
|
copy: (size: number) => (
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width={size}
|
|
height={size}
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
>
|
|
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
</svg>
|
|
),
|
|
/** Mastodon logo. */
|
|
mastodon: (size: number) => (
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width={size}
|
|
height={size}
|
|
viewBox="0 0 24 24"
|
|
fill="currentColor"
|
|
>
|
|
<path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.668 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z" />
|
|
</svg>
|
|
),
|
|
/** Twitter/X logo. */
|
|
twitter: (size: number) => (
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width={size}
|
|
height={size}
|
|
viewBox="0 0 24 24"
|
|
fill="currentColor"
|
|
>
|
|
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
|
|
</svg>
|
|
),
|
|
/** Bluesky logo. */
|
|
bluesky: (size: number) => (
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width={size}
|
|
height={size}
|
|
viewBox="0 0 24 24"
|
|
fill="currentColor"
|
|
>
|
|
<path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8z" />
|
|
</svg>
|
|
),
|
|
} as const;
|
|
|
|
export type IconName = keyof typeof sfIcons | keyof typeof customIcons;
|
|
|
|
interface IconProps {
|
|
name: IconName;
|
|
size?: number;
|
|
className?: string;
|
|
}
|
|
|
|
/** Decorative icon wrapper — hidden from screen readers since adjacent text conveys meaning. */
|
|
export default function Icon({ name, size = 20, className }: IconProps) {
|
|
if (name in customIcons) {
|
|
return (
|
|
<span aria-hidden="true" className={className}>
|
|
{customIcons[name as keyof typeof customIcons](size)}
|
|
</span>
|
|
);
|
|
}
|
|
return (
|
|
<span aria-hidden="true">
|
|
<SFSymbol name={sfIcons[name as keyof typeof sfIcons]} size={size} className={className} />
|
|
</span>
|
|
);
|
|
}
|