diff --git a/README.md b/README.md index ff1b06d..ac70000 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,16 @@ # react-driftkit -**Small, focused building blocks for floating UI in React.** -Tree-shakable, unstyled, one component per job. +**A tree-shakable React component library for floating UI: draggable widgets, an edge-pinned dock, a draggable bottom sheet, an N-pane resizable split pane, an image magnifier (zoom lens), and a peek-and-flick card stack.** + +Unstyled, TypeScript-first, zero dependencies, React 18 & React 19. [![npm version](https://img.shields.io/npm/v/react-driftkit)](https://www.npmjs.com/package/react-driftkit) [![npm downloads](https://img.shields.io/npm/dm/react-driftkit)](https://www.npmjs.com/package/react-driftkit) [![bundle size](https://img.shields.io/bundlephobia/minzip/react-driftkit)](https://bundlephobia.com/package/react-driftkit) [![license](https://img.shields.io/npm/l/react-driftkit)](./LICENSE) -[Live Demo](https://react-driftkit.saktichourasia.dev/) · [NPM](https://www.npmjs.com/package/react-driftkit) · [GitHub](https://github.com/shakcho/react-drift) +[Live Demo](https://react-driftkit.saktichourasia.dev/) · [NPM](https://www.npmjs.com/package/react-driftkit) · [GitHub](https://github.com/shakcho/react-driftkit) @@ -26,12 +27,12 @@ Building a chat widget, floating toolbar, debug panel, or side dock? You want th | Component | What it does | |-----------|--------------| -| [``](#movablelauncher) | A draggable floating wrapper that pins to any viewport corner or lives at custom `{x, y}` — drop-anywhere with optional snap-on-release. | -| [``](#snapdock) | An edge-pinned dock that slides along any side of the viewport and flips orientation automatically between horizontal and vertical. | -| [``](#draggablesheet) | A pull-up / pull-down sheet pinned to an edge with named snap points (`peek`, `half`, `full`) or arbitrary pixel / percentage stops. | -| [``](#resizablesplitter) | An N-pane resizable split layout with draggable handles, min/max constraints, and localStorage-persisted ratios. | -| [``](#zoomlens) | A draggable magnifier circle that zooms into whatever it hovers — free-drag over the whole page or scope it to one element (product-image-zoom style). Wheel to zoom, hotkey or Escape to dismiss. | -| [``](#flickdeck) | A stack of cards where each back card peeks from one edge. Click the peek to flick that card to the front, or optionally swipe the front card off to dismiss it. | +| [``](#movablelauncher) | **React draggable floating widget** — pin a chat bubble, AI assistant, debug panel, or toolbar to any viewport corner with snap-on-release. | +| [``](#snapdock) | **React floating dock / edge-pinned toolbar** — slides along any side of the viewport and flips orientation between horizontal and vertical. | +| [``](#draggablesheet) | **React bottom sheet & pull-up drawer** — `peek`, `half`, `full` snap points, velocity-aware release. Mobile-style filters, cart drawers, detail panels. | +| [``](#resizablesplitter) | **React resizable split pane** — N-pane IDE-style layout with draggable handles, min/max constraints, and localStorage-persisted ratios. | +| [``](#zoomlens) | **React image magnifier / zoom lens** — free-drag over the whole page or scope to one element (product-image zoom). Wheel to zoom, Escape to dismiss. | +| [``](#flickdeck) | **React card stack & swipeable cards** — back cards peek from one edge, click the peek to flick it forward; optional swipe-to-dismiss. | ## Installation @@ -286,6 +287,20 @@ import FlickDeck from 'react-driftkit/FlickDeck'; - **Tip / onboarding stacks** — a deck of coachmark cards the user can flick through and swipe off one by one - **Comparison decks** — toggle between product plans, chart variants, or before/after states with a single click on the peek +## Looking for… + +If you're searching for any of these, react-driftkit has a primitive for it: + +- **React draggable component** / **react draggable widget** → [`MovableLauncher`](#movablelauncher) +- **React chat widget** / **floating support button** → [`MovableLauncher`](#movablelauncher) + your UI +- **React floating dock** / **react sidebar** / **react vertical toolbar** → [`SnapDock`](#snapdock) +- **React bottom sheet** / **react pull-up drawer** / **mobile sheet** → [`DraggableSheet`](#draggablesheet) +- **React resizable split pane** / **react split view** / **alternative to allotment, react-split-pane, react-resizable-panels** → [`ResizableSplitPane`](#resizablesplitter) +- **React image magnifier** / **react product zoom** / **ecommerce hover zoom** → [`ZoomLens`](#zoomlens) +- **React card stack** / **swipeable cards** / **alternative to react-tinder-card** → [`FlickDeck`](#flickdeck) + +Each one is independently importable, ships unstyled, and works with React 18 and React 19. + ## How it works Under the hood all components use the [Pointer Events API](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events) for universal input handling. `MovableLauncher`, `SnapDock`, and `DraggableSheet` render as `position: fixed` elements at the top of the z-index stack (`2147483647`) and use a `ResizeObserver` to stay pinned when their content changes size. @@ -303,8 +318,8 @@ Under the hood all components use the [Pointer Events API](https://developer.moz Contributions are welcome. Open an issue or send a pull request. ```bash -git clone https://github.com/shakcho/react-drift.git -cd react-drift +git clone https://github.com/shakcho/react-driftkit.git +cd react-driftkit npm install npm run dev # Start the demo app npm test # Run the test suite diff --git a/demo/public/favicon.svg b/demo/public/favicon.svg new file mode 100644 index 0000000..6d9d9cf --- /dev/null +++ b/demo/public/favicon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/demo/src/data/dock.ts b/demo/src/data/dock.ts index 8e6d67f..8a592f1 100644 --- a/demo/src/data/dock.ts +++ b/demo/src/data/dock.ts @@ -77,7 +77,10 @@ export const dockMeta: ComponentMeta = { tagline: 'An edge-pinned dock that slides along any side of the viewport and flips orientation automatically between horizontal and vertical layouts.', metaDescription: - 'SnapDock — an edge-pinned React dock that snaps to left, right, top, or bottom. Horizontal/vertical layout flip, drag offset, unstyled primitive for React 18 and 19.', + 'SnapDock is a React floating dock / sidebar component — edge-pinned to left, right, top, or bottom of the viewport with automatic orientation flip between horizontal and vertical. Tree-shakable, unstyled, TypeScript-first, React 18 & React 19.', + seoDescriptor: 'React Floating Dock & Edge-Pinned Toolbar', + seoKeywords: + 'react dock component, react floating toolbar, react sidebar component, react edge dock, react snap to edge, react vertical toolbar, react app dock, react-driftkit SnapDock', apiRows: [ { prop: 'children', typeHtml: 'ReactNode', defaultHtml: '—', descriptionHtml: 'Content rendered inside the dock.' }, { prop: 'defaultEdge', typeHtml: "'left' | 'right' | 'top' | 'bottom'", defaultHtml: "'left'", descriptionHtml: 'Which edge the dock pins to initially.' }, diff --git a/demo/src/data/flickdeck.ts b/demo/src/data/flickdeck.ts index 6d07ae5..c98cf16 100644 --- a/demo/src/data/flickdeck.ts +++ b/demo/src/data/flickdeck.ts @@ -113,7 +113,10 @@ export const flickDeckMeta: ComponentMeta = { tagline: 'A stack of cards where each back card peeks from one edge — receding into depth for top/bottom peek, fanning out at an angle for left/right. Click the peek to flick that card to the front, or optionally swipe the front card off to dismiss it. Useful for toggles between views, tip stacks, and side-by-side comparisons.', metaDescription: - 'FlickDeck — a stacked card component for React. Back cards peek from a configurable edge, click the peek to flick it forward with a smooth transition, optional swipe-to-dismiss. Controlled or uncontrolled, unstyled, zero runtime deps.', + 'FlickDeck is a React card stack & swipeable cards component — back cards peek from a configurable edge, click the peek to flick it forward, optional swipe-to-dismiss. Useful for tip stacks, view toggles, side-by-side comparisons, and onboarding flows. React 18 & React 19.', + seoDescriptor: 'React Card Stack & Swipeable Cards Component', + seoKeywords: + 'react card stack, react swipeable cards, react card deck, react tinder cards alternative, react onboarding cards, react flick deck, react peek cards, react-driftkit FlickDeck', apiRows: [ { prop: 'frontId', typeHtml: 'string', defaultHtml: '—', descriptionHtml: "Controlled id of the front card — matches a child's React key. Omit for uncontrolled." }, { prop: 'defaultFrontId', typeHtml: 'string', defaultHtml: '—', descriptionHtml: "Uncontrolled initial front card id. Falls back to the first child's key if unset." }, diff --git a/demo/src/data/launcher.ts b/demo/src/data/launcher.ts index fdc289a..ba49bad 100644 --- a/demo/src/data/launcher.ts +++ b/demo/src/data/launcher.ts @@ -87,7 +87,10 @@ export const launcherMeta: ComponentMeta = { tagline: 'A draggable floating wrapper that lets users pick up any widget and drop it anywhere on the viewport — or snap it to the nearest corner on release.', metaDescription: - 'MovableLauncher — a tree-shakable draggable floating wrapper for React. Corner snapping, free positioning, pointer-event based, unstyled, TypeScript-first, React 18 and 19.', + 'MovableLauncher is a React draggable floating widget component — pin a chat bubble, AI assistant, debug panel, or toolbar to any viewport corner with snap-on-release. Tree-shakable, unstyled, TypeScript-first, React 18 & React 19.', + seoDescriptor: 'React Draggable Floating Widget Component', + seoKeywords: + 'react draggable component, react draggable widget, react floating widget, react chat widget, react draggable corner, react snap to corner, react draggable launcher, react-driftkit MovableLauncher', apiRows: [ { prop: 'children', typeHtml: 'ReactNode', defaultHtml: '—', descriptionHtml: 'Content rendered inside the draggable container.' }, { diff --git a/demo/src/data/sheet.ts b/demo/src/data/sheet.ts index 950d761..f20ee60 100644 --- a/demo/src/data/sheet.ts +++ b/demo/src/data/sheet.ts @@ -96,7 +96,10 @@ export const sheetMeta: ComponentMeta = { tagline: 'A pull-up / pull-down sheet pinned to an edge with snap points like peek, half, and full. Great for mobile-style details, filters, or cart drawers.', metaDescription: - 'DraggableSheet — an edge-pinned React sheet with named and custom snap points, velocity-aware release, and a drag-handle selector. Unstyled, TypeScript-first, React 18 and 19.', + 'DraggableSheet is a React bottom sheet & pull-up drawer component — peek, half, and full snap points, velocity-aware release, drag-handle selector. Mobile-style filters, cart drawers, and detail panels. Unstyled, TypeScript-first, React 18 & React 19.', + seoDescriptor: 'React Bottom Sheet & Pull-Up Drawer Component', + seoKeywords: + 'react bottom sheet, react pull-up drawer, react draggable sheet, react snap points sheet, react cart drawer, react filter drawer, react mobile sheet, react-driftkit DraggableSheet', apiRows: [ { prop: 'children', typeHtml: 'ReactNode', defaultHtml: '—', descriptionHtml: 'Content rendered inside the sheet.' }, { prop: 'edge', typeHtml: "'bottom' | 'top' | 'left' | 'right'", defaultHtml: "'bottom'", descriptionHtml: 'Edge the sheet is pinned to.' }, diff --git a/demo/src/data/splitter.ts b/demo/src/data/splitter.ts index 3a47d7e..436edae 100644 --- a/demo/src/data/splitter.ts +++ b/demo/src/data/splitter.ts @@ -122,7 +122,10 @@ export const splitterMeta: ComponentMeta = { tagline: 'An N-pane split view with draggable handles and a single render prop for custom handle UI. Supports min/max constraints, persisted sizes, and both horizontal and vertical layouts.', metaDescription: - 'ResizableSplitPane — an N-pane resizable split layout for React with draggable handles, min/max constraints, localStorage persistence, and controlled/uncontrolled modes.', + 'ResizableSplitPane is a React resizable split pane component for IDE-style layouts — N panes, draggable handles, min/max constraints, localStorage-persisted ratios, horizontal & vertical orientations. Tree-shakable, unstyled, TypeScript-first, React 18 & React 19.', + seoDescriptor: 'React Resizable Split Pane (N-Pane Layout) Component', + seoKeywords: + 'react resizable split pane, react split view, react split pane component, react resizable panes, react ide layout, react allotment alternative, react split.js alternative, react-driftkit ResizableSplitPane', apiRows: [ { prop: 'children', typeHtml: 'ReactNode[]', defaultHtml: '—', descriptionHtml: 'Two or more child elements to render in the split panes.' }, { prop: 'orientation', typeHtml: "'horizontal' | 'vertical'", defaultHtml: "'horizontal'", descriptionHtml: "Split direction. 'horizontal' puts panes side-by-side; 'vertical' stacks them." }, diff --git a/demo/src/data/types.ts b/demo/src/data/types.ts index ec89fc1..7209b2a 100644 --- a/demo/src/data/types.ts +++ b/demo/src/data/types.ts @@ -22,6 +22,10 @@ export type ComponentMeta = { title: string; tagline: string; metaDescription: string; + /** Short, keyword-rich descriptor appended to for SEO (e.g. "React Draggable Floating Widget"). */ + seoDescriptor?: string; + /** Comma-separated keywords for <meta name="keywords"> hints. */ + seoKeywords?: string; apiRows: ApiRow[]; apiFootnoteHtml?: string; codeExamples: CodeExample[]; diff --git a/demo/src/data/zoomlens.ts b/demo/src/data/zoomlens.ts index 64bdfce..3c3c2e0 100644 --- a/demo/src/data/zoomlens.ts +++ b/demo/src/data/zoomlens.ts @@ -131,7 +131,10 @@ export const zoomLensMeta: ComponentMeta = { tagline: 'A draggable magnifier circle that zooms into whatever it hovers. Great for design review, image inspection, or dense data tables. Drag to move, scroll to zoom, Escape or a hotkey to exit.', metaDescription: - 'ZoomLens — a draggable magnifier circle for React. Live-clones the page into a circular lens, drag to reposition, scroll to change zoom, Escape or hotkey to dismiss. Tree-shakable, unstyled, zero runtime deps.', + 'ZoomLens is a React image magnifier & zoom-lens component — draggable magnifier circle for product images, design review, dense data tables, or map inspection. Free-drag or scope to a single element (product zoom). Wheel to zoom, Escape to dismiss. React 18 & React 19.', + seoDescriptor: 'React Image Magnifier & Zoom Lens Component', + seoKeywords: + 'react image magnifier, react zoom lens, react product image zoom, react magnifier component, react ecommerce zoom, react design review tool, react draggable magnifier, react-driftkit ZoomLens', apiRows: [ { prop: 'active', typeHtml: '<code>boolean</code>', defaultHtml: '—', descriptionHtml: 'Controlled active state. Omit for uncontrolled.' }, { prop: 'defaultActive', typeHtml: '<code>boolean</code>', defaultHtml: '<code>false</code>', descriptionHtml: 'Uncontrolled initial active state.' }, diff --git a/demo/src/layouts/Layout.astro b/demo/src/layouts/Layout.astro index ecc1be0..7882e5a 100644 --- a/demo/src/layouts/Layout.astro +++ b/demo/src/layouts/Layout.astro @@ -12,6 +12,8 @@ export interface Props { activeComponent?: 'launcher' | 'dock' | 'sheet' | 'splitter' | 'zoomlens' | 'flickdeck' | null; /** Per-page OG image slug under /og/ (e.g. "movable-launcher"). Defaults to "home". */ ogSlug?: string; + /** Optional comma-separated keywords for the page (used by some search engines as hints). */ + keywords?: string; } const { @@ -21,6 +23,7 @@ const { jsonLd, activeComponent = null, ogSlug = 'home', + keywords, } = Astro.props; const siteUrl = Astro.site?.toString().replace(/\/$/, '') ?? 'https://react-driftkit.saktichourasia.dev'; @@ -37,19 +40,32 @@ const jsonLdArray = jsonLd ? (Array.isArray(jsonLd) ? jsonLd : [jsonLd]) : []; <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>{title} + {keywords && } + + + + + + + + + + + + {jsonLdArray.map((ld) => (