From 7c60e4362fd477904ed3284d53e37b6fd45ccf18 Mon Sep 17 00:00:00 2001 From: krutoo Date: Sun, 28 Jun 2026 00:38:31 +0500 Subject: [PATCH] docs: Homepage added --- docs/docs/main.mdx | 14 ++ docs/package-lock.json | 47 ++++--- docs/package.json | 3 +- docs/src/components/homepage/demo-dots.ts | 72 +++++++++++ docs/src/components/homepage/homepage.m.css | 115 +++++++++++++++++ docs/src/components/homepage/homepage.tsx | 135 ++++++++++++++++++++ docs/src/components/homepage/types.ts | 20 +++ docs/src/components/homepage/utils.ts | 3 + docs/src/showcase.tsx | 3 + package-lock.json | 6 +- 10 files changed, 396 insertions(+), 22 deletions(-) create mode 100644 docs/docs/main.mdx create mode 100644 docs/src/components/homepage/demo-dots.ts create mode 100644 docs/src/components/homepage/homepage.m.css create mode 100644 docs/src/components/homepage/homepage.tsx create mode 100644 docs/src/components/homepage/types.ts create mode 100644 docs/src/components/homepage/utils.ts diff --git a/docs/docs/main.mdx b/docs/docs/main.mdx new file mode 100644 index 0000000..a379a20 --- /dev/null +++ b/docs/docs/main.mdx @@ -0,0 +1,14 @@ +import { Homepage } from '#components/homepage/homepage.tsx'; + +export const meta = { + title: 'Main', + category: '', + menuPriority: 10000, + menuHidden: true, + parameters: { + layout: 'fullscreen', + asideEnabled: false, + }, +}; + + diff --git a/docs/package-lock.json b/docs/package-lock.json index a465882..08eae81 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -8,9 +8,10 @@ "name": "@krutoo/utils-docs", "version": "0.0.0", "dependencies": { - "@krutoo/showcase": "^0.6.1", + "@krutoo/showcase": "^0.7.1", "@mdx-js/react": "^3.1.0", "classnames": "^2.5.1", + "lucide-react": "^1.17.0", "react": "^19.1.1", "react-dom": "^19.1.1", "shiki": "^3.9.2" @@ -60,12 +61,11 @@ } }, "node_modules/@krutoo/showcase": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@krutoo/showcase/-/showcase-0.6.1.tgz", - "integrity": "sha512-f9Qe4bWHT7G1AU2FEvlHXFAcmVUTFusErWx+aooDK5YC0k5xztm76BidH0k8+Pv4hJwZE42p5egZ456fnUv4JA==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@krutoo/showcase/-/showcase-0.7.1.tgz", + "integrity": "sha512-TxS8oSfrorJBrAGjysYIMgXIIZgFa9BujJqNFEpz3BMhwmqA72GAUpfrCXvtBMpfI0mI0ta6ubnN+dbfWL239w==", "license": "Apache-2.0", "dependencies": { - "@krutoo/utils": "^0.0.29", "chokidar": "^4.0.3", "classnames": "^2.5.1", "fast-glob": "^3.3.3", @@ -73,23 +73,25 @@ "zod": "^4.1.9" }, "engines": { - "node": ">=16" + "node": ">=18" }, "peerDependencies": { + "@krutoo/utils": "^0.0.37", "react": "^18 || ^19", "react-dom": "^18 || ^19" } }, - "node_modules/@krutoo/showcase/node_modules/@krutoo/utils": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@krutoo/utils/-/utils-0.0.29.tgz", - "integrity": "sha512-ckG9LlTUfuMaPq3/vEiuqlFyUWDgcNExhxIrgEjTzq+Bu4Tlb+oUZrSCRSnKFpNxx8fDkPoCroan0e6IKiWyzA==", - "license": "Apache-2.0", + "node_modules/@krutoo/utils": { + "version": "0.0.37", + "resolved": "https://registry.npmjs.org/@krutoo/utils/-/utils-0.0.37.tgz", + "integrity": "sha512-bNrlwdvyc4i/R+xIhZaEU4otNINneHez/Cqgaj2qp0bjzkeyUdW2wys9gSDvswTrbqlavFgmrsRn3qrP8ri/gA==", + "license": "MIT", + "peer": true, "engines": { "node": ">=16" }, "peerDependencies": { - "@rspack/core": "^1.1.8", + "@rspack/core": "^1.1.8 || ^2", "@svgr/webpack": "^8.1.0", "css-loader": "^7.1.2", "react": "^17 || ^18 || ^19", @@ -1072,9 +1074,9 @@ } }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -1336,6 +1338,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/lucide-react": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz", + "integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/markdown-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", @@ -2967,9 +2978,9 @@ } }, "node_modules/zod": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", - "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/docs/package.json b/docs/package.json index 22a8785..11db24e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -22,9 +22,10 @@ "typescript": "^5.9.2" }, "dependencies": { - "@krutoo/showcase": "^0.6.1", + "@krutoo/showcase": "^0.7.1", "@mdx-js/react": "^3.1.0", "classnames": "^2.5.1", + "lucide-react": "^1.17.0", "react": "^19.1.1", "react-dom": "^19.1.1", "shiki": "^3.9.2" diff --git a/docs/src/components/homepage/demo-dots.ts b/docs/src/components/homepage/demo-dots.ts new file mode 100644 index 0000000..9afcfc4 --- /dev/null +++ b/docs/src/components/homepage/demo-dots.ts @@ -0,0 +1,72 @@ +import { Point2d, Vector2, clamp, lerp } from '@krutoo/utils'; +import { Demo, DemoContext, DemoInitContext, DemoRenderContext } from './types.ts'; +import { easeInQuad } from './utils.ts'; + +interface Thing extends Point2d { + readonly radius: number; +} + +export class DemoDots implements Demo { + dots: Vector2[]; + thing: Thing; + + constructor({ canvas }: DemoInitContext) { + this.dots = []; + this.thing = { + x: canvas.width / 2, + y: canvas.height / 2, + get radius() { + return Math.min(canvas.width, canvas.height) / 2; + }, + }; + + const radius = clamp(Math.min(canvas.width, canvas.height) / 48, 12, 64); + const hexWidth = Math.sqrt(3) * radius; + const hexHeight = 2 * radius; + const xSpacing = hexWidth; + const ySpacing = (3 / 4) * hexHeight; + const columns = canvas.width / xSpacing; + const rows = canvas.height / ySpacing + 1; + + for (let r = 0; r < rows; r++) { + const xOffset = r % 2 === 0 ? 0 : xSpacing / 2; + + for (let c = 0; c < columns; c++) { + const x = c * xSpacing + xOffset; + const y = r * ySpacing; + + this.dots.push(Vector2.of(x, y)); + } + } + } + + update({ mouse }: DemoContext) { + const { thing } = this; + + thing.x = lerp(thing.x, mouse.x, 0.08); + thing.y = lerp(thing.y, mouse.y, 0.08); + } + + render({ context, colorScheme }: DemoRenderContext) { + const { dots, thing } = this; + + context.fillStyle = colorScheme === 'dark' ? '#ffffffaa' : '#3c3c43aa'; + context.beginPath(); + + for (let i = 0; i < dots.length; i++) { + const dot = dots[i]!; + const distance = dot.getDistance(thing); + + if (distance > thing.radius) { + continue; + } + + const dotRadius = lerp(0, 3, easeInQuad(1 - distance / thing.radius)); + + context.moveTo(dot.x + dotRadius, dot.y); + context.arc(dot.x, dot.y, dotRadius, 0, 2 * Math.PI); + } + + context.fill(); + } +} diff --git a/docs/src/components/homepage/homepage.m.css b/docs/src/components/homepage/homepage.m.css new file mode 100644 index 0000000..9ee4b27 --- /dev/null +++ b/docs/src/components/homepage/homepage.m.css @@ -0,0 +1,115 @@ +.root { + height: calc(100vh - var(--showcase-ui-header-height, 64px)); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + position: relative; + padding: 24px; +} + +.main { + display: flex; + flex-direction: column; + gap: 12px; + width: 400px; + max-width: 100%; + z-index: 1; +} + +.canvas { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; + user-select: none; + z-index: 0; +} + +.field { + overflow: hidden; + background: var(--showcase-ui-background); + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 0 12px; + font-size: 20px; + height: 48px; + border-radius: 99999px; + color: var(--showcase-ui-main-color); + box-shadow: inset 0 0 0 2px var(--showcase-ui-main-color); +} + +.field ::selection { + color: var(--showcase-ui-background); + background: var(--showcase-ui-color); +} + +.fieldContent { + white-space: nowrap; + padding-left: 32px; + text-align: center; + flex-grow: 1; + line-height: 1; + font-family: + Menlo, + Consolas, + Monaco, + Adwaita Mono, + Liberation Mono, + Lucida Console, + monospace; +} + +.fieldButton { + all: unset; + display: block; + background: transparent; + user-select: none; + color: var(--showcase-ui-link-color); +} + +.fieldButton:hover { + background: transparent; + color: var(--showcase-ui-link-hover-color); +} + +.fieldButton:disabled { + background: transparent; + color: var(--showcase-ui-link-color); +} + +.fieldButton > svg { + display: block; +} + +.links { + display: flex; + gap: 8px; +} + +a.link { + display: flex; + align-items: center; + justify-content: center; + text-align: center; + font-size: 20px; + padding: 0 20px; + width: 0; + flex-grow: 1; + height: 56px; + border-radius: 16px; + font-weight: 700; + background: var(--showcase-ui-main-color); + color: var(--showcase-ui-background) !important; + text-decoration: none !important; +} + +a.link.secondary { + color: var(--showcase-ui-main-color) !important; + background: var(--showcase-ui-background); + box-shadow: inset 0 0 0 2px var(--showcase-ui-main-color); +} diff --git a/docs/src/components/homepage/homepage.tsx b/docs/src/components/homepage/homepage.tsx new file mode 100644 index 0000000..170c37e --- /dev/null +++ b/docs/src/components/homepage/homepage.tsx @@ -0,0 +1,135 @@ +import { MouseEvent, useContext, useEffect, useRef, useState } from 'react'; +import { ColorSchemesContext } from '@krutoo/showcase/runtime-showcase'; +import { wait } from '@krutoo/utils'; +import { useNavigate, useResize } from '@krutoo/utils/react'; +import classNames from 'classnames'; +import { Check, Copy } from 'lucide-react'; +import { DemoDots } from './demo-dots.ts'; +import styles from './homepage.m.css'; + +export function Homepage() { + const navigate = useNavigate(); + const rootRef = useRef(null); + const canvasRef = useRef(null); + const [copied, setCopied] = useState(false); + const [size, setSize] = useState(() => ({ width: 0, height: 0 })); + const content = 'npm add @krutoo/utils'; + const { colorScheme } = useContext(ColorSchemesContext); + + const handleCopy = () => { + if (!copied) { + navigator.clipboard + .writeText(content) + .then(() => setCopied(true)) + .catch(() => {}); + } + }; + + const handleLinkClick = (event: MouseEvent) => { + if (event.currentTarget.target !== '_blank') { + event.preventDefault(); + navigate(event.currentTarget.href); + } + }; + + useResize(rootRef, ({ target }) => { + const { width, height } = target.getBoundingClientRect(); + + setSize({ width, height }); + }); + + useEffect(() => { + if (copied) { + wait(2000).then(() => setCopied(false)); + } + }, [copied]); + + useEffect(() => { + const canvas = canvasRef.current; + const context = canvas?.getContext?.('2d'); + + if (!canvas || !context) { + return; + } + + const mouse = { + x: canvas.width / 2, + y: canvas.height / 2, + }; + + window.addEventListener('mousemove', event => { + mouse.x = event.clientX; + mouse.y = event.clientY; + }); + + const demoCtx = { + canvas, + context, + mouse, + colorScheme, + }; + const demo = new DemoDots(demoCtx); + + let last = Date.now(); + let enabled = true; + + const loop = () => { + const now = Date.now(); + const delta = now - last; + + if (delta > 1000 / 60) { + last = now; + demo.update(demoCtx); + context.clearRect(0, 0, canvas.width, canvas.height); + demo.render(demoCtx); + } + + if (enabled) { + requestAnimationFrame(loop); + } + }; + + loop(); + + return () => { + enabled = false; + }; + }, [size, colorScheme]); + + return ( +
+ +
+
+ {content} + +
+ +
+
+ ); +} diff --git a/docs/src/components/homepage/types.ts b/docs/src/components/homepage/types.ts new file mode 100644 index 0000000..2702835 --- /dev/null +++ b/docs/src/components/homepage/types.ts @@ -0,0 +1,20 @@ +import { ColorSchemesContextValue } from '@krutoo/showcase/runtime-showcase'; +import { Point2d } from '@krutoo/utils'; + +export interface DemoInitContext { + colorScheme: ColorSchemesContextValue['colorScheme']; + canvas: HTMLCanvasElement; +} + +export interface DemoContext extends DemoInitContext { + mouse: Point2d; +} + +export interface DemoRenderContext extends DemoContext { + context: CanvasRenderingContext2D; +} + +export interface Demo { + update(ctx: DemoContext): void; + render(ctx: DemoRenderContext): void; +} diff --git a/docs/src/components/homepage/utils.ts b/docs/src/components/homepage/utils.ts new file mode 100644 index 0000000..2bd0ece --- /dev/null +++ b/docs/src/components/homepage/utils.ts @@ -0,0 +1,3 @@ +export function easeInQuad(x: number): number { + return x * x; +} diff --git a/docs/src/showcase.tsx b/docs/src/showcase.tsx index 80fddae..571077f 100644 --- a/docs/src/showcase.tsx +++ b/docs/src/showcase.tsx @@ -25,6 +25,9 @@ createRoot(document.getElementById('root')!).render( light: './public/logo.svg', dark: './public/logo.dark.svg', }} + defaultStory={{ + pathname: '/main', + }} colorSchemes={{ enabled: true, attributeTarget: 'documentElement', diff --git a/package-lock.json b/package-lock.json index d6a11f4..9e52b8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6900,9 +6900,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, "license": "MIT", "engines": {