diff --git a/.github/workflows/explorer.yml b/.github/workflows/explorer.yml new file mode 100644 index 0000000..676a31b --- /dev/null +++ b/.github/workflows/explorer.yml @@ -0,0 +1,50 @@ +name: UI library and explorer +on: + push: + branches: [main] + pull_request: + workflow_dispatch: +permissions: + contents: read +concurrency: + group: explorer-${{ github.ref }} + cancel-in-progress: true +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm check + - run: pnpm tokens:build + - run: git diff --exit-code -- packages/tokens swift/Sources/SaaSMakerUI/Tokens.generated.swift + - run: pnpm storybook:build + - run: pnpm exec shadcn build --output explorer/storybook-static/r + - run: pnpm check:explorer --built + - uses: actions/upload-artifact@v4 + with: + name: explorer-preview + path: explorer/storybook-static + - uses: actions/upload-pages-artifact@v4 + if: github.ref == 'refs/heads/main' + with: + path: explorer/storybook-static + deploy: + if: github.ref == 'refs/heads/main' + needs: build + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/configure-pages@v5 + - uses: actions/deploy-pages@v4 + id: deployment diff --git a/.gitignore b/.gitignore index 7db9672..d378918 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist/ .build/ .swiftpm/ .claude/ +storybook-static/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bbb97a5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2026 SaaS Maker contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Third-party material retains the licenses and notices in THIRD_PARTY_NOTICES.md +and the font license files shipped alongside the fonts. diff --git a/README.md b/README.md index 93bbd98..e223a28 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,37 @@ one theme. | `SaaSMakerUI` (Swift) | palette, type, components and motion for Mac/iOS | +## Explore the library + +The deployed [Fleet UI explorer](https://sass-maker.github.io/ui-library/) runs +Storybook with live source, composition examples, theme and light/dark controls, +responsive viewports, reduced motion, and accessibility inspection. + +```sh +pnpm storybook # http://localhost:6006 +pnpm storybook:check # explorer and component types +pnpm storybook:build # static explorer +pnpm check:explorer --built +``` + +Import interaction patterns from `@saas-maker/ui/recipes/*`, visual treatments +from `@saas-maker/ui/effects/*`, and primitives from `@saas-maker/ui/components/*`. +Import `@saas-maker/ui/styles.css` once; theme.css includes the effect keyframes. +Each module has a working example and import path in the explorer. Uploads and +async forms require application-owned callbacks; examples do not send files or +create accounts. Motion is optional and honors reduced-motion preferences. + +Selective MIT adaptations come from shadcn/ui, Xevrion UI Lab, Motion Primitives +and Magic UI. Pinned source provenance is in `packages/ui/upstream.json` and +copyright/permission notices in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). +Transitions.dev is a design reference only; its restricted collection is not +redistributed. The retained iOS factory keeps its independent dependencies. + +The `UI library and explorer` workflow checks pull requests and deploys the +static explorer to GitHub Pages on main. To restore a verified revision, rerun +its successful main workflow. This first publication can be withdrawn by +disabling Pages; product consumers keep their pinned Git tags. + ## Retained native-app landing factory The complete former iOS Landings repository lives in @@ -46,7 +77,7 @@ Install from the GitHub repo (public), not npm. Write the specs into `package.js and run `pnpm install`; templates needs ui and motion beside it. ```sh -G="github:sass-maker/ui-library#v0.1.20" +G="github:sass-maker/ui-library#v0.1.21" pnpm pkg set "dependencies.@saas-maker/ui=$G&path:/packages/ui" \ "dependencies.@saas-maker/motion=$G&path:/packages/motion" \ "dependencies.@saas-maker/templates=$G&path:/packages/templates" @@ -56,9 +87,9 @@ pnpm install `package.json` should then read: ```json -"@saas-maker/motion": "github:sass-maker/ui-library#v0.1.20&path:/packages/motion", -"@saas-maker/templates": "github:sass-maker/ui-library#v0.1.20&path:/packages/templates", -"@saas-maker/ui": "github:sass-maker/ui-library#v0.1.20&path:/packages/ui" +"@saas-maker/motion": "github:sass-maker/ui-library#v0.1.21&path:/packages/motion", +"@saas-maker/templates": "github:sass-maker/ui-library#v0.1.21&path:/packages/templates", +"@saas-maker/ui": "github:sass-maker/ui-library#v0.1.21&path:/packages/ui" ``` Do not use `pnpm add` for these: pnpm 10.33 saves the spec as @@ -595,7 +626,7 @@ swift build && swift test Install the pinned UI package with the same GitHub spec style: ```sh -pnpm pkg set 'dependencies.@saas-maker/ui=github:sass-maker/ui-library#v0.1.20&path:/packages/ui' +pnpm pkg set 'dependencies.@saas-maker/ui=github:sass-maker/ui-library#v0.1.21&path:/packages/ui' pnpm install cp node_modules/@saas-maker/ui/footer.* public/ ``` diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..9b05cb1 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,116 @@ +# Third-party notices + +Selected components are adapted from the projects below. Original authorship and MIT permissions are retained. File headers and `packages/ui/upstream.json` identify exact sources and revisions. Third-party images, brands, and services are not included under these permissions. + +## shadcn-ui/ui + +Source: https://github.com/shadcn-ui/ui/blob/3d98900e9b73c45f035f8109e9d41ba6a51685e7/LICENSE.md + +```text +MIT License + +Copyright (c) 2023 shadcn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## xevrion/ui-lab + +Source: https://github.com/xevrion/ui-lab/blob/846633609287fada19a1585dad1d4d4673eb76fe/LICENSE + +```text +MIT License + +Copyright (c) 2026 Yash Bavadiya + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## ibelick/motion-primitives + +Source: https://github.com/ibelick/motion-primitives/blob/120f64f6ca60348e251f929e9c81f11ccbe45eda/LICENCE.md + +```text +MIT License + +Copyright (c) 2024 ibelick + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +app/page.tsx +``` + +## magicuidesign/magicui + +Source: https://github.com/magicuidesign/magicui/blob/efe76fc5755eca1142dd4f74c1d6f0ace2887e3c/LICENSE.md + +```text +MIT License + +Copyright (c) Magic UI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` diff --git a/explorer/.storybook/main.ts b/explorer/.storybook/main.ts new file mode 100644 index 0000000..89554ed --- /dev/null +++ b/explorer/.storybook/main.ts @@ -0,0 +1,18 @@ +import type { StorybookConfig } from "@storybook/react-vite"; +import tailwindcss from "@tailwindcss/vite"; + +const config: StorybookConfig = { + framework: "@storybook/react-vite", + stories: ["../stories/**/*.stories.@(ts|tsx)"], + staticDirs: ["../../site/public"], + addons: ["@storybook/addon-docs", "@storybook/addon-a11y"], + core: { disableTelemetry: true }, + docs: { defaultName: "Guide" }, + async viteFinal(config) { + config.base = "./"; + config.plugins = [...(config.plugins ?? []), tailwindcss()]; + return config; + }, +}; + +export default config; diff --git a/explorer/.storybook/manager.ts b/explorer/.storybook/manager.ts new file mode 100644 index 0000000..7c4c78f --- /dev/null +++ b/explorer/.storybook/manager.ts @@ -0,0 +1,23 @@ +import { addons } from "storybook/manager-api"; +import { create } from "storybook/theming"; + +addons.setConfig({ + layoutCustomisations: { + showPanel: (state, defaultValue) => state.storyId === "start-here-overview--explore" ? false : defaultValue, + }, + sidebar: { collapsedRoots: ["inputs", "navigation", "overlays", "feedback", "data", "interactions", "motion", "effects", "blocks", "templates", "native"] }, + theme: create({ + base: "dark", + brandTitle: "Fleet UI · component explorer", + brandUrl: "https://github.com/sass-maker/ui-library", + brandTarget: "_blank", + colorPrimary: "#91a7ff", + colorSecondary: "#91a7ff", + appBg: "#111113", + appContentBg: "#19191c", + appPreviewBg: "#111113", + appBorderColor: "#303036", + textColor: "#eeeeef", + textMutedColor: "#a1a1aa", + }), +}); diff --git a/explorer/.storybook/preview.css b/explorer/.storybook/preview.css new file mode 100644 index 0000000..b47a858 --- /dev/null +++ b/explorer/.storybook/preview.css @@ -0,0 +1,35 @@ +@import "@saas-maker/ui/theme.css"; +@import "tailwindcss/utilities"; +@source "../stories"; +@source "../../packages/ui/src"; +@source "../../packages/templates/src"; + +html, body, #storybook-root { min-height: 100%; } +body { background: var(--background); color: var(--foreground); } +.fleet-story-frame { + min-height: 100%; + color: var(--foreground); + background: var(--background); + font-family: var(--font-sans); + line-height: 1.5; +} +.sbdocs-wrapper { background: var(--background) !important; color: var(--foreground); } +.sbdocs-preview { background: var(--background) !important; border-color: var(--border) !important; } +.sbdocs-content { max-width: 1120px !important; } +.sbdocs-title, .sbdocs-h1, .sbdocs-h2, .sbdocs-h3, .sbdocs-p, .sbdocs-li { color: var(--foreground) !important; } +.sbdocs-a { color: var(--brand-ink) !important; } +.story-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); gap: 24px; } +.story-stack { display: flex; flex-direction: column; gap: 20px; width: 100%; max-width: 760px; } +.story-row { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; } +.story-panel { padding: clamp(20px, 4vw, 40px); border: 1px solid var(--border); border-radius: var(--radius); background: var(--card); } +.story-caption { color: var(--muted-foreground); font-size: 13px; line-height: 1.6; } +.story-label { display: block; margin-bottom: 8px; font-size: 13px; font-weight: 600; } +[data-motion="reduced"] *, [data-motion="reduced"] *::before, [data-motion="reduced"] *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; +} +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { scroll-behavior: auto !important; } +} diff --git a/explorer/.storybook/preview.tsx b/explorer/.storybook/preview.tsx new file mode 100644 index 0000000..1fc415e --- /dev/null +++ b/explorer/.storybook/preview.tsx @@ -0,0 +1,54 @@ +import type { Preview } from "@storybook/react-vite"; +import { MotionConfig } from "motion/react"; +import { useEffect } from "react"; +import { TooltipProvider } from "@saas-maker/ui/components/tooltip"; +import { ReducedMotionProvider } from "@saas-maker/ui/lib/use-reduced-motion"; +import "./preview.css"; + +function ThemeFrame({ children, theme, mode, reduced }: { + children: React.ReactNode; theme: string; mode: string; reduced: boolean; +}) { + useEffect(() => { + const root = document.documentElement; + root.dataset.theme = theme; + root.dataset.mode = mode; + root.dataset.motion = reduced ? "reduced" : "user"; + root.style.colorScheme = mode; + }, [theme, mode, reduced]); + return ( + + + +
{children}
+
+
+
+ ); +} + +const preview: Preview = { + tags: ["autodocs"], + initialGlobals: { theme: "base", mode: "dark", motion: "user" }, + globalTypes: { + theme: { description: "Fleet theme", toolbar: { icon: "paintbrush", dynamicTitle: true, items: ["base", "paper", "hearth", "signal", "gallery", "ink"].map(value => ({ value, title: value })) } }, + mode: { description: "Color mode", toolbar: { icon: "circlehollow", dynamicTitle: true, items: [{ value: "light", title: "Light" }, { value: "dark", title: "Dark" }] } }, + motion: { description: "Motion preference", toolbar: { icon: "play", dynamicTitle: true, items: [{ value: "user", title: "System motion" }, { value: "reduced", title: "Reduced motion" }] } }, + }, + decorators: [(Story, context) => ], + parameters: { + layout: "padded", + controls: { expanded: true, matchers: { color: /(background|color)$/i, date: /Date$/i } }, + docs: { codePanel: true, toc: true, source: { type: "dynamic" } }, + options: { storySort: { order: ["Start here", "Foundations", "Inputs", "Navigation", "Overlays", "Feedback", "Data", "Interactions", "Motion", "Effects", "Blocks", "Templates", "Native"] } }, + viewport: { + options: { + phone: { name: "Phone · 390", styles: { width: "390px", height: "844px" } }, + tablet: { name: "Tablet · 768", styles: { width: "768px", height: "1024px" } }, + desktop: { name: "Desktop · 1440", styles: { width: "1440px", height: "900px" } }, + }, + }, + a11y: { test: "error" }, + }, +}; + +export default preview; diff --git a/explorer/package.json b/explorer/package.json new file mode 100644 index 0000000..8e027f3 --- /dev/null +++ b/explorer/package.json @@ -0,0 +1,35 @@ +{ + "name": "@saas-maker/ui-explorer", + "version": "0.1.21", + "private": true, + "type": "module", + "scripts": { + "dev": "storybook dev -p 6006 --no-open", + "build": "storybook build --disable-telemetry", + "check": "tsc --noEmit -p tsconfig.json" + }, + "dependencies": { + "@saas-maker/ui": "workspace:*", + "@saas-maker/motion": "workspace:*", + "@saas-maker/templates": "workspace:*", + "react": "19.3.0", + "react-dom": "19.3.0", + "motion": "13.4.3", + "lucide-react": "1.48.0", + "radix-ui": "1.6.7", + "sonner": "2.0.8" + }, + "devDependencies": { + "storybook": "10.6.3", + "@storybook/react-vite": "10.6.3", + "@storybook/addon-docs": "10.6.3", + "@storybook/addon-a11y": "10.6.3", + "@tailwindcss/vite": "4.3.3", + "tailwindcss": "4.3.3", + "vite": "8.3.5", + "typescript": "5.9.3", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "@types/node": "22.19.15" + } +} diff --git a/explorer/scripts/check-explorer.mjs b/explorer/scripts/check-explorer.mjs new file mode 100644 index 0000000..bbd0566 --- /dev/null +++ b/explorer/scripts/check-explorer.mjs @@ -0,0 +1,28 @@ +import { readFile, readdir } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; +const root=fileURLToPath(new URL('../../',import.meta.url)); +async function files(dir){return (await Promise.all((await readdir(dir,{withFileTypes:true})).map(entry=>entry.isDirectory()?files(path.join(dir,entry.name)):[path.join(dir,entry.name)]))).flat();} +const storyFiles=(await files(path.join(root,'explorer/stories'))).filter(file=>file.endsWith('.stories.tsx')); +const stories=(await Promise.all(storyFiles.map(file=>readFile(file,'utf8')))).join('\n'); +const missing=[];let modules=0; +for(const category of ['components','recipes','effects','blocks']){ + for(const file of await readdir(path.join(root,'packages/ui/src',category))){ + if(!file.endsWith('.tsx')||file==='upload-types.tsx')continue; + modules++; + const module=`@saas-maker/ui/${category}/${file.slice(0,-4)}`; + if(!stories.includes(module))missing.push(module); + } +} +if(missing.length)throw new Error(`Missing explorer examples:\n${missing.join('\n')}`); +const upstream=JSON.parse(await readFile(path.join(root,'packages/ui/upstream.json'),'utf8')); +const entries=Array.isArray(upstream)?upstream:upstream.components; +if(entries)for(const entry of entries)if(entry.file)await readFile(path.join(root,entry.file),'utf8'); +console.log(`${modules} UI modules covered by ${storyFiles.length} story files.`); +if(process.argv.includes('--built')){ + const index=JSON.parse(await readFile(path.join(root,'explorer/storybook-static/index.json'),'utf8')); + const entries=Object.values(index.entries); + const count=entries.filter(entry=>entry.type==='story').length; + if(countentry.type==='docs').length} guides.`); +} diff --git a/explorer/scripts/verify.mjs b/explorer/scripts/verify.mjs new file mode 100644 index 0000000..e655d44 --- /dev/null +++ b/explorer/scripts/verify.mjs @@ -0,0 +1,12 @@ +import { spawnSync } from 'node:child_process'; +const commands = process.argv.includes('--build-only') ? [['storybook:check']] : [['check']]; +commands.push(['tokens:build'],['storybook:build'],['check:explorer','--built'],['exec','shadcn','build','--output','explorer/storybook-static/r']); +for (const args of commands) { + const result=spawnSync('pnpm',args,{stdio:'inherit',shell:false}); + if(result.status!==0)process.exit(result.status??1); +} +// Run locally on macOS; the web workflow runs the platform-independent steps above. +if(process.platform==='darwin' && process.argv.includes('--native')){ + const result=spawnSync('swift',['build'],{stdio:'inherit',shell:false}); + if(result.status!==0)process.exit(result.status??1); +} diff --git a/explorer/stories/accordion.stories.tsx b/explorer/stories/accordion.stories.tsx new file mode 100644 index 0000000..30411a5 --- /dev/null +++ b/explorer/stories/accordion.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/accordion"; + +const meta = { title: "Navigation/Accordion", component: C.Accordion, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/accordion`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return Can I adapt the source?Yes. Keep the MIT attribution for adapted components and use the shared tokens to fit the product.What happens with reduced motion?State changes remain understandable while movement is reduced. Use the toolbar to inspect this behavior.; } }; diff --git a/explorer/stories/alert-dialog.stories.tsx b/explorer/stories/alert-dialog.stories.tsx new file mode 100644 index 0000000..7a4148b --- /dev/null +++ b/explorer/stories/alert-dialog.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/alert-dialog"; + +const meta = { title: "Overlays/Alert dialog", component: C.AlertDialog, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/alert-dialog`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return Remove this example?This preview does not delete product data. Cancel is the safe default.CancelRemove example; } }; diff --git a/explorer/stories/alert.stories.tsx b/explorer/stories/alert.stories.tsx new file mode 100644 index 0000000..cecb16a --- /dev/null +++ b/explorer/stories/alert.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/alert"; + +const meta = { title: "Feedback/Alert", component: C.Alert, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/alert`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
Source is ready to exploreInspect the component and its states before using it in a product.Could not save changesYour draft is retained. Try again when the connection is available.
; } }; diff --git a/explorer/stories/avatar.stories.tsx b/explorer/stories/avatar.stories.tsx new file mode 100644 index 0000000..24c77fe --- /dev/null +++ b/explorer/stories/avatar.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/avatar"; + +const meta = { title: "Foundations/Avatar", component: C.Avatar, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/avatar`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
UIAHInitials remain usable when an image is unavailable.
; } }; diff --git a/explorer/stories/badge.stories.tsx b/explorer/stories/badge.stories.tsx new file mode 100644 index 0000000..c8df438 --- /dev/null +++ b/explorer/stories/badge.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/badge"; + +const meta = { title: "Foundations/Badge", component: C.Badge, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/badge`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
{(["default","secondary","outline","destructive"] as const).map(variant=>{variant})}
; } }; diff --git a/explorer/stories/blocks/app.stories.tsx b/explorer/stories/blocks/app.stories.tsx new file mode 100644 index 0000000..2bcca60 --- /dev/null +++ b/explorer/stories/blocks/app.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/app'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/App",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/app`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return
} title="No components selected" body="Select a component to inspect its source."/>
;}}; diff --git a/explorer/stories/blocks/assistant-logos.stories.tsx b/explorer/stories/blocks/assistant-logos.stories.tsx new file mode 100644 index 0000000..0364639 --- /dev/null +++ b/explorer/stories/blocks/assistant-logos.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/assistant-logos'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Assistant logos",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/assistant-logos`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return
{["Claude","ChatGPT","Perplexity","Grok"].map(name=>{name})}
;}}; diff --git a/explorer/stories/blocks/closing.stories.tsx b/explorer/stories/blocks/closing.stories.tsx new file mode 100644 index 0000000..1c3cd7b --- /dev/null +++ b/explorer/stories/blocks/closing.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/closing'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Closing",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/closing`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return
Open repository}/>
;}}; diff --git a/explorer/stories/blocks/export-menu.stories.tsx b/explorer/stories/blocks/export-menu.stories.tsx new file mode 100644 index 0000000..e344ffb --- /dev/null +++ b/explorer/stories/blocks/export-menu.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/export-menu'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Export menu",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/export-menu`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return
row.name},{id:"kind",label:"Kind",value:row=>row.kind}]} filename="fleet-ui-example"/>

Downloads the two example rows shown in this story.

;}}; diff --git a/explorer/stories/blocks/features.stories.tsx b/explorer/stories/blocks/features.stories.tsx new file mode 100644 index 0000000..ec3779c --- /dev/null +++ b/explorer/stories/blocks/features.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/features'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Features",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/features`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return },{title:"Accessible interactions",body:"Start with maintained upstream primitives.",icon:},{title:"Purposeful motion",body:"Preserve meaning when reduced motion is enabled.",icon:}]}/>;}}; diff --git a/explorer/stories/blocks/filter-bar.stories.tsx b/explorer/stories/blocks/filter-bar.stories.tsx new file mode 100644 index 0000000..1852783 --- /dev/null +++ b/explorer/stories/blocks/filter-bar.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/filter-bar'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Filter bar",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/filter-bar`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [query,setQuery]=useState("");const [selected,setSelected]=useState([]);return
    {[{name:"Button",kind:"Component"},{name:"Dialog",kind:"Component"},{name:"Kanban",kind:"Pattern"}].filter(item=>item.name.toLowerCase().includes(query.toLowerCase())&&(!selected.length||selected.includes(item.kind))).map(item=>
  • {item.name}{item.kind}
  • )}
;}}; diff --git a/explorer/stories/blocks/footer.stories.tsx b/explorer/stories/blocks/footer.stories.tsx new file mode 100644 index 0000000..0cc262b --- /dev/null +++ b/explorer/stories/blocks/footer.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/footer'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Footer",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/footer`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return ;}}; diff --git a/explorer/stories/blocks/frames.stories.tsx b/explorer/stories/blocks/frames.stories.tsx new file mode 100644 index 0000000..aff008d --- /dev/null +++ b/explorer/stories/blocks/frames.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/frames'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Frames",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/frames`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return
;}}; diff --git a/explorer/stories/blocks/gallery.stories.tsx b/explorer/stories/blocks/gallery.stories.tsx new file mode 100644 index 0000000..325ed4d --- /dev/null +++ b/explorer/stories/blocks/gallery.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/gallery'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Gallery",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/gallery`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return ;}}; diff --git a/explorer/stories/blocks/hero.stories.tsx b/explorer/stories/blocks/hero.stories.tsx new file mode 100644 index 0000000..bf6775a --- /dev/null +++ b/explorer/stories/blocks/hero.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/hero'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Hero",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/hero`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return Build on a shared foundation.} lede="A reusable system of components, motion and page blocks for Fleet products." actions={} media={UI theme preview}/>;}}; diff --git a/explorer/stories/blocks/layout.stories.tsx b/explorer/stories/blocks/layout.stories.tsx new file mode 100644 index 0000000..43fa85b --- /dev/null +++ b/explorer/stories/blocks/layout.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/layout'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Layout",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/layout`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return ;}}; diff --git a/explorer/stories/blocks/mobile-nav.stories.tsx b/explorer/stories/blocks/mobile-nav.stories.tsx new file mode 100644 index 0000000..70bc7cd --- /dev/null +++ b/explorer/stories/blocks/mobile-nav.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/mobile-nav'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Mobile nav",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/mobile-nav`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return

Resize to phone width to inspect the navigation disclosure.

;}}; diff --git a/explorer/stories/blocks/proof.stories.tsx b/explorer/stories/blocks/proof.stories.tsx new file mode 100644 index 0000000..7c51a07 --- /dev/null +++ b/explorer/stories/blocks/proof.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/proof'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Proof",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/proof`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return
Example evidenceSave"},{kind:"add",text:""}]}/>
;}}; diff --git a/explorer/stories/blocks/quote-list.stories.tsx b/explorer/stories/blocks/quote-list.stories.tsx new file mode 100644 index 0000000..fe72b65 --- /dev/null +++ b/explorer/stories/blocks/quote-list.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/quote-list'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Quote list",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/quote-list`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return ;}}; diff --git a/explorer/stories/blocks/record-detail.stories.tsx b/explorer/stories/blocks/record-detail.stories.tsx new file mode 100644 index 0000000..b806f93 --- /dev/null +++ b/explorer/stories/blocks/record-detail.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/record-detail'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Record detail",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/record-detail`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [open,setOpen]=useState(false);return ;}}; diff --git a/explorer/stories/blocks/resource-boundary.stories.tsx b/explorer/stories/blocks/resource-boundary.stories.tsx new file mode 100644 index 0000000..7e31a58 --- /dev/null +++ b/explorer/stories/blocks/resource-boundary.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/resource-boundary'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Resource boundary",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/resource-boundary`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [status,setStatus]=useState<"loading"|"success"|"empty"|"error">("success");return
{(["loading","success","empty","error"] as const).map(value=>)}
setStatus("success")}} skeleton={
Loading component list…
}>{items=>
    {items.map(item=>
  • {item}
  • )}
}
;}}; diff --git a/explorer/stories/blocks/search-palette.stories.tsx b/explorer/stories/blocks/search-palette.stories.tsx new file mode 100644 index 0000000..c1ff2bd --- /dev/null +++ b/explorer/stories/blocks/search-palette.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/search-palette'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Search palette",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/search-palette`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [selection,setSelection]=useState("");return
["Button","Dialog","Combobox","Calendar"].filter(title=>title.toLowerCase().includes(query.toLowerCase())).map(title=>({id:title,title,subtitle:"@saas-maker/ui"}))}]} onSelect={item=>setSelection(item.title)}/>{selection?`Selected: ${selection}`:"Search local examples; no network request."}
;}}; diff --git a/explorer/stories/blocks/site-header.stories.tsx b/explorer/stories/blocks/site-header.stories.tsx new file mode 100644 index 0000000..24e6686 --- /dev/null +++ b/explorer/stories/blocks/site-header.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/site-header'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Site header",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/site-header`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return GitHub}/>;}}; diff --git a/explorer/stories/blocks/skeletons.stories.tsx b/explorer/stories/blocks/skeletons.stories.tsx new file mode 100644 index 0000000..da662be --- /dev/null +++ b/explorer/stories/blocks/skeletons.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/blocks/skeletons'; +import {comparisonAfter,comparisonBefore} from '../fixtures'; +const meta={title:"Blocks/Skeletons",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/blocks/skeletons`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return
;}}; diff --git a/explorer/stories/breadcrumb.stories.tsx b/explorer/stories/breadcrumb.stories.tsx new file mode 100644 index 0000000..20ee757 --- /dev/null +++ b/explorer/stories/breadcrumb.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/breadcrumb"; + +const meta = { title: "Navigation/Breadcrumb", component: C.Breadcrumb, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/breadcrumb`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return UI LibraryInputsCombobox; } }; diff --git a/explorer/stories/button.stories.tsx b/explorer/stories/button.stories.tsx new file mode 100644 index 0000000..511366a --- /dev/null +++ b/explorer/stories/button.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/button"; + +const meta = { title: "Foundations/Button", component: C.Button, parameters: {"docs": {"description": {"component": "Shared action geometry, quiet press feedback, and visible focus.\n\nImport from `@saas-maker/ui/components/button`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
{(["default","brand","secondary","outline","ghost","destructive","link"] as const).map(variant => {variant === "brand" ? "create project" : variant})}
compactregularlargeunavailable
; } }; + +export const Controls: Story = { args: { children: "create project", variant: "brand", size: "default", disabled: false }, argTypes: { variant: { control: "select", options: ["default", "brand", "outline", "secondary", "ghost", "link", "destructive"] }, size: { control: "select", options: ["xs", "sm", "default", "lg", "xl", "icon"] }, disabled: { control: "boolean" }, children: { control: "text" } }, render: args => }; +export const LongLabel: Story = { render: () => create a project with a deliberately longer, localized action label }; diff --git a/explorer/stories/calendar.stories.tsx b/explorer/stories/calendar.stories.tsx new file mode 100644 index 0000000..bfb1ae8 --- /dev/null +++ b/explorer/stories/calendar.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/calendar"; + +const meta = { title: "Inputs/Calendar", component: C.Calendar, parameters: {"docs": {"description": {"component": "Single-date keyboard selection; the same component supports ranges and multiple dates.\n\nImport from `@saas-maker/ui/components/calendar`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { const [date,setDate]=useState(new Date(2026,9,23));return

{date ? date.toLocaleDateString() : "Choose a date"}

; } }; diff --git a/explorer/stories/card.stories.tsx b/explorer/stories/card.stories.tsx new file mode 100644 index 0000000..6d78926 --- /dev/null +++ b/explorer/stories/card.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/card"; + +const meta = { title: "Foundations/Card", component: C.Card, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/card`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return UI LibraryShared components, tokens, and motion for Fleet products.

Explore the actual components in every theme and inspect their states before using them in a product.

; } }; diff --git a/explorer/stories/checkbox.stories.tsx b/explorer/stories/checkbox.stories.tsx new file mode 100644 index 0000000..e01ed9d --- /dev/null +++ b/explorer/stories/checkbox.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/checkbox"; + +const meta = { title: "Inputs/Checkbox", component: C.Checkbox, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/checkbox`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { const [checked,setChecked]=useState("indeterminate"); return
; } }; diff --git a/explorer/stories/collapsible.stories.tsx b/explorer/stories/collapsible.stories.tsx new file mode 100644 index 0000000..ffbc05c --- /dev/null +++ b/explorer/stories/collapsible.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/collapsible"; + +const meta = { title: "Navigation/Collapsible", component: C.Collapsible, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/collapsible`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return Products install tagged packages from the UI Library repository. Keep shared package versions aligned.; } }; diff --git a/explorer/stories/combobox.stories.tsx b/explorer/stories/combobox.stories.tsx new file mode 100644 index 0000000..548cfc1 --- /dev/null +++ b/explorer/stories/combobox.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/combobox"; + +const meta = { title: "Inputs/Combobox", component: C.Combobox, parameters: {"docs": {"description": {"component": "Search, keyboard selection, and empty results using the upstream accessible primitive.\n\nImport from `@saas-maker/ui/components/combobox`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
No matching projects.{(item: string) => {item}}
; } }; diff --git a/explorer/stories/command.stories.tsx b/explorer/stories/command.stories.tsx new file mode 100644 index 0000000..e7ef2f4 --- /dev/null +++ b/explorer/stories/command.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/command"; + +const meta = { title: "Navigation/Command", component: C.Command, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/command`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return No matching components.ComboboxSettings panelNotification inbox; } }; diff --git a/explorer/stories/context-menu.stories.tsx b/explorer/stories/context-menu.stories.tsx new file mode 100644 index 0000000..ba9cc6a --- /dev/null +++ b/explorer/stories/context-menu.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/context-menu"; + +const meta = { title: "Overlays/Context menu", component: C.ContextMenu, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/context-menu`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return Right-click, long press, or focus and press Shift F10.Open componentCopy import pathUnavailable action; } }; diff --git a/explorer/stories/data-table.stories.tsx b/explorer/stories/data-table.stories.tsx new file mode 100644 index 0000000..f83ed62 --- /dev/null +++ b/explorer/stories/data-table.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/data-table"; + +const meta = { title: "Data/Data table", component: C.DataTable, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/data-table`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { const data=[{id:"ui",name:"UI Library",kind:"Shared system"},{id:"health",name:"App Health",kind:"Operations"},{id:"vetter",name:"CodeVetter",kind:"Developer tool"},{id:"kith",name:"Kith",kind:"Personal relationships"}];const [selected,setSelected]=useState([]);return
row.name},{id:"kind",header:"Purpose",value:(row:typeof data[number])=>row.kind}]} getRowId={row=>row.id} label="Fleet product examples" selection={{selected,onChange:setSelected,rowLabel:row=>row.name}} pageSize={3}/>

Example rows · {selected.length} selected. Try sorting, column visibility, selection, and pagination.

; } }; diff --git a/explorer/stories/dialog.stories.tsx b/explorer/stories/dialog.stories.tsx new file mode 100644 index 0000000..290170d --- /dev/null +++ b/explorer/stories/dialog.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/dialog"; + +const meta = { title: "Overlays/Dialog", component: C.Dialog, parameters: {"docs": {"description": {"component": "Modal focus, Escape dismissal, accessible title, and focus return use Radix.\n\nImport from `@saas-maker/ui/components/dialog`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return Edit projectUpdate the display name for this local example.; } }; diff --git a/explorer/stories/drawer.stories.tsx b/explorer/stories/drawer.stories.tsx new file mode 100644 index 0000000..529b450 --- /dev/null +++ b/explorer/stories/drawer.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/drawer"; + +const meta = { title: "Overlays/Drawer", component: C.Drawer, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/drawer`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
Choose a previewDrag to dismiss on touch or use the close action.
; } }; diff --git a/explorer/stories/dropdown-menu.stories.tsx b/explorer/stories/dropdown-menu.stories.tsx new file mode 100644 index 0000000..56e3f7e --- /dev/null +++ b/explorer/stories/dropdown-menu.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/dropdown-menu"; + +const meta = { title: "Overlays/Dropdown menu", component: C.DropdownMenu, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/dropdown-menu`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return UI LibraryOpen sourceCopy linkMove toActiveReviewUnavailable action; } }; diff --git a/explorer/stories/effects/animated-beam.stories.tsx b/explorer/stories/effects/animated-beam.stories.tsx new file mode 100644 index 0000000..068e110 --- /dev/null +++ b/explorer/stories/effects/animated-beam.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/animated-beam'; + +const meta={title:"Effects/Animated beam",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/animated-beam`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const container=useRef(null),from=useRef(null),to=useRef(null);return
;}}; diff --git a/explorer/stories/effects/bento-grid.stories.tsx b/explorer/stories/effects/bento-grid.stories.tsx new file mode 100644 index 0000000..56a7c6a --- /dev/null +++ b/explorer/stories/effects/bento-grid.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/bento-grid'; + +const meta={title:"Effects/Bento grid",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/bento-grid`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return }/>}/>;}}; diff --git a/explorer/stories/effects/blur-fade.stories.tsx b/explorer/stories/effects/blur-fade.stories.tsx new file mode 100644 index 0000000..fcc7e87 --- /dev/null +++ b/explorer/stories/effects/blur-fade.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/blur-fade'; + +const meta={title:"Effects/Blur fade",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/blur-fade`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [key,setKey]=useState(0);return

Clarity, then detail.

A short entrance for supporting content.

;}}; diff --git a/explorer/stories/effects/border-beam.stories.tsx b/explorer/stories/effects/border-beam.stories.tsx new file mode 100644 index 0000000..7b2bb03 --- /dev/null +++ b/explorer/stories/effects/border-beam.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/border-beam'; + +const meta={title:"Effects/Border beam",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/border-beam`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return

A focused edge

Use sparingly on a focal surface.

;}}; diff --git a/explorer/stories/effects/code-comparison.stories.tsx b/explorer/stories/effects/code-comparison.stories.tsx new file mode 100644 index 0000000..46c8401 --- /dev/null +++ b/explorer/stories/effects/code-comparison.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/code-comparison'; + +const meta={title:"Effects/Code comparison",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/code-comparison`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return Save'} afterCode={''}/>;}}; diff --git a/explorer/stories/effects/dot-pattern.stories.tsx b/explorer/stories/effects/dot-pattern.stories.tsx new file mode 100644 index 0000000..a629812 --- /dev/null +++ b/explorer/stories/effects/dot-pattern.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/dot-pattern'; + +const meta={title:"Effects/Dot pattern",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/dot-pattern`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return

A quiet backdrop

;}}; diff --git a/explorer/stories/effects/file-tree.stories.tsx b/explorer/stories/effects/file-tree.stories.tsx new file mode 100644 index 0000000..c9e200c --- /dev/null +++ b/explorer/stories/effects/file-tree.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/file-tree'; + +const meta={title:"Effects/File tree",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/file-tree`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return
;}}; diff --git a/explorer/stories/effects/globe.stories.tsx b/explorer/stories/effects/globe.stories.tsx new file mode 100644 index 0000000..871c031 --- /dev/null +++ b/explorer/stories/effects/globe.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/globe'; + +const meta={title:"Effects/Globe",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/globe`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return ;}}; diff --git a/explorer/stories/effects/hero-video-dialog.stories.tsx b/explorer/stories/effects/hero-video-dialog.stories.tsx new file mode 100644 index 0000000..f253ebf --- /dev/null +++ b/explorer/stories/effects/hero-video-dialog.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/hero-video-dialog'; +import {comparisonAfter} from '../fixtures'; +const meta={title:"Effects/Hero video dialog",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/hero-video-dialog`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return

Shell preview with a blank embed. Supply your own video URL; no external video is loaded here.

;}}; diff --git a/explorer/stories/effects/interactive-hover-button.stories.tsx b/explorer/stories/effects/interactive-hover-button.stories.tsx new file mode 100644 index 0000000..a18eb0c --- /dev/null +++ b/explorer/stories/effects/interactive-hover-button.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/interactive-hover-button'; + +const meta={title:"Effects/Interactive hover button",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/interactive-hover-button`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [clicked,setClicked]=useState(false);return
setClicked(true)}>Explore components{clicked?"Example action selected":""}
;}}; diff --git a/explorer/stories/effects/marquee.stories.tsx b/explorer/stories/effects/marquee.stories.tsx new file mode 100644 index 0000000..6a9afa0 --- /dev/null +++ b/explorer/stories/effects/marquee.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/marquee'; + +const meta={title:"Effects/Marquee",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/marquee`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return
{["Components","Tokens","Motion","Blocks","Templates"].map(label=>
{label}
)}

Pauses on hover and keyboard focus. Reduced motion displays a static row.

;}}; diff --git a/explorer/stories/effects/number-ticker.stories.tsx b/explorer/stories/effects/number-ticker.stories.tsx new file mode 100644 index 0000000..d693a02 --- /dev/null +++ b/explorer/stories/effects/number-ticker.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/number-ticker'; + +const meta={title:"Effects/Number ticker",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/number-ticker`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [value,setValue]=useState(128);return
;}}; diff --git a/explorer/stories/effects/orbiting-circles.stories.tsx b/explorer/stories/effects/orbiting-circles.stories.tsx new file mode 100644 index 0000000..913e846 --- /dev/null +++ b/explorer/stories/effects/orbiting-circles.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/orbiting-circles'; + +const meta={title:"Effects/Orbiting circles",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/orbiting-circles`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return
;}}; diff --git a/explorer/stories/effects/shimmer-button.stories.tsx b/explorer/stories/effects/shimmer-button.stories.tsx new file mode 100644 index 0000000..feb5cff --- /dev/null +++ b/explorer/stories/effects/shimmer-button.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/shimmer-button'; + +const meta={title:"Effects/Shimmer button",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/shimmer-button`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [clicked,setClicked]=useState(false);return
setClicked(true)}>Explore components{clicked?"Example action selected":""}
;}}; diff --git a/explorer/stories/effects/text-animate.stories.tsx b/explorer/stories/effects/text-animate.stories.tsx new file mode 100644 index 0000000..4caa41e --- /dev/null +++ b/explorer/stories/effects/text-animate.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/effects/text-animate'; + +const meta={title:"Effects/Text animate",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/effects/text-animate`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [key,setKey]=useState(0);return
One visual language across the Fleet.
;}}; diff --git a/explorer/stories/empty-state.stories.tsx b/explorer/stories/empty-state.stories.tsx new file mode 100644 index 0000000..1a91290 --- /dev/null +++ b/explorer/stories/empty-state.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/empty-state"; + +const meta = { title: "Feedback/Empty state", component: C.EmptyState, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/empty-state`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
} title="No saved views" description="Save a filtered view to return to it later." primaryAction={{label:"Create a view",onClick:()=>{}}}/>
; } }; diff --git a/explorer/stories/error-state.stories.tsx b/explorer/stories/error-state.stories.tsx new file mode 100644 index 0000000..a243319 --- /dev/null +++ b/explorer/stories/error-state.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/error-state"; + +const meta = { title: "Feedback/Error state", component: C.ErrorState, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/error-state`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { const [retried,setRetried]=useState(false);return
setRetried(true)} detail="Example: the data service is temporarily unavailable."/>
; } }; diff --git a/explorer/stories/fixtures.ts b/explorer/stories/fixtures.ts new file mode 100644 index 0000000..5d44c33 --- /dev/null +++ b/explorer/stories/fixtures.ts @@ -0,0 +1,3 @@ +function screen(bg:string,fg:string){return 'data:image/svg+xml,'+encodeURIComponent(`FLEET UI / THEME PREVIEWOne shared system.Components · Motion · TokensExplore the library`)} +export const comparisonBefore=screen('#f7f1e8','#1d1611'); +export const comparisonAfter=screen('#191919','#f5f5f5'); diff --git a/explorer/stories/hover-card.stories.tsx b/explorer/stories/hover-card.stories.tsx new file mode 100644 index 0000000..187687c --- /dev/null +++ b/explorer/stories/hover-card.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/hover-card"; + +const meta = { title: "Overlays/Hover card", component: C.HoverCard, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/hover-card`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return UI Library source

One design system

Shared web components, page templates, tokens, and SwiftUI components.

; } }; diff --git a/explorer/stories/input-group.stories.tsx b/explorer/stories/input-group.stories.tsx new file mode 100644 index 0000000..e19600b --- /dev/null +++ b/explorer/stories/input-group.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/input-group"; + +const meta = { title: "Inputs/Input group", component: C.InputGroup, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/input-group`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
⌘ K.com
; } }; diff --git a/explorer/stories/input-otp.stories.tsx b/explorer/stories/input-otp.stories.tsx new file mode 100644 index 0000000..6b6f0fc --- /dev/null +++ b/explorer/stories/input-otp.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/input-otp"; + +const meta = { title: "Inputs/One-time code", component: C.InputOTP, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/input-otp`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { const [code,setCode]=useState("");return
{[0,1,2].map(index => )}{[3,4,5].map(index => )}

Example only. Paste a six-digit code; nothing is sent.

; } }; diff --git a/explorer/stories/input.stories.tsx b/explorer/stories/input.stories.tsx new file mode 100644 index 0000000..599a45d --- /dev/null +++ b/explorer/stories/input.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/input"; + +const meta = { title: "Inputs/Input", component: C.Input, parameters: {"docs": {"description": {"component": "Default, validation, read-only, and disabled input states.\n\nImport from `@saas-maker/ui/components/input`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
; } }; diff --git a/explorer/stories/kbd.stories.tsx b/explorer/stories/kbd.stories.tsx new file mode 100644 index 0000000..ea93058 --- /dev/null +++ b/explorer/stories/kbd.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/kbd"; + +const meta = { title: "Foundations/Keyboard hints", component: C.Kbd, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/kbd`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
Search components⌘KUse platform-appropriate shortcut labels in consuming applications.
; } }; diff --git a/explorer/stories/label.stories.tsx b/explorer/stories/label.stories.tsx new file mode 100644 index 0000000..bdc1482 --- /dev/null +++ b/explorer/stories/label.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/label"; + +const meta = { title: "Inputs/Label", component: C.Label, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/label`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
Project name

A visible label stays associated with the field even when it has a value.

; } }; diff --git a/explorer/stories/menubar.stories.tsx b/explorer/stories/menubar.stories.tsx new file mode 100644 index 0000000..6dd99e1 --- /dev/null +++ b/explorer/stories/menubar.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/menubar"; + +const meta = { title: "Navigation/Menubar", component: C.Menubar, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/menubar`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return LibraryExplore componentsView tokensView sourceViewLight themeDark theme; } }; diff --git a/explorer/stories/native.stories.tsx b/explorer/stories/native.stories.tsx new file mode 100644 index 0000000..6fcb832 --- /dev/null +++ b/explorer/stories/native.stories.tsx @@ -0,0 +1,11 @@ +import type {Meta,StoryObj} from '@storybook/react-vite'; +const meta={title:'Native/SwiftUI',parameters:{docs:{description:{component:'Native components run in SwiftUI, not the browser. This page documents the existing native package; it does not simulate native rendering.'}}}} satisfies Meta;export default meta; +export const Package:StoryObj={render:()=>

SaaSMakerUI for Apple platforms

Add the public repository as a Swift package. Shared generated tokens power the native palette and type roles.

{`import SaaSMakerUI
+
+Button("Save changes") { save() }
+    .buttonStyle(SMButtonStyle(.brand))
+
+SMCard {
+    SMSectionHeader("Workspace")
+    SMStatusPill("Ready")
+}`}
  • Buttons, cards, headings and status pills
  • Gallery and dashboard compositions
  • Loading, empty and error states
  • Motion helpers with reduced-motion support
Read SwiftUI source

Use Xcode previews or an Apple app to inspect native rendering. Web stories cover the React library.

}; diff --git a/explorer/stories/navigation-menu.stories.tsx b/explorer/stories/navigation-menu.stories.tsx new file mode 100644 index 0000000..350d4d3 --- /dev/null +++ b/explorer/stories/navigation-menu.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/navigation-menu"; + +const meta = { title: "Navigation/Navigation menu", component: C.NavigationMenu, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/navigation-menu`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return Explore
Source and installationFleet products
GitHub
; } }; diff --git a/explorer/stories/pagination.stories.tsx b/explorer/stories/pagination.stories.tsx new file mode 100644 index 0000000..1e67bd1 --- /dev/null +++ b/explorer/stories/pagination.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/pagination"; + +const meta = { title: "Navigation/Pagination", component: C.Pagination, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/pagination`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = {render:function Render(){const [page,setPage]=useState(0);return ;}}; diff --git a/explorer/stories/popover.stories.tsx b/explorer/stories/popover.stories.tsx new file mode 100644 index 0000000..da7ce87 --- /dev/null +++ b/explorer/stories/popover.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/popover"; + +const meta = { title: "Overlays/Popover", component: C.Popover, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/popover`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return

Preview settings

Themes and device widths are available from the Storybook toolbar.

; } }; diff --git a/explorer/stories/progress.stories.tsx b/explorer/stories/progress.stories.tsx new file mode 100644 index 0000000..d62dc53 --- /dev/null +++ b/explorer/stories/progress.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/progress"; + +const meta = { title: "Feedback/Progress", component: C.Progress, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/progress`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { const [value,setValue]=useState(40);return

Controlled preview values; no upload is running.

; } }; diff --git a/explorer/stories/radio-group.stories.tsx b/explorer/stories/radio-group.stories.tsx new file mode 100644 index 0000000..c76842c --- /dev/null +++ b/explorer/stories/radio-group.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/radio-group"; + +const meta = { title: "Inputs/Radio group", component: C.RadioGroup, parameters: {"docs": {"description": {"component": "Arrow keys move within the group; Tab moves out.\n\nImport from `@saas-maker/ui/components/radio-group`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return {["daily","weekly","monthly"].map(value => )}; } }; diff --git a/explorer/stories/recipes/animated-background.stories.tsx b/explorer/stories/recipes/animated-background.stories.tsx new file mode 100644 index 0000000..20796ac --- /dev/null +++ b/explorer/stories/recipes/animated-background.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/animated-background'; + +const meta={title:"Motion/Animated background",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/animated-background`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return
{["Components","Tokens","Motion"].map(label=>)}
;}}; diff --git a/explorer/stories/recipes/animated-number.stories.tsx b/explorer/stories/recipes/animated-number.stories.tsx new file mode 100644 index 0000000..d297ceb --- /dev/null +++ b/explorer/stories/recipes/animated-number.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/animated-number'; + +const meta={title:"Motion/Animated number",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/animated-number`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [value,setValue]=useState(24);return
;}}; diff --git a/explorer/stories/recipes/autosave-status.stories.tsx b/explorer/stories/recipes/autosave-status.stories.tsx new file mode 100644 index 0000000..40fd15a --- /dev/null +++ b/explorer/stories/recipes/autosave-status.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/autosave-status'; + +const meta={title:"Feedback/Autosave status",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/autosave-status`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [state,setState]=useState<"unsaved"|"saving"|"saved">("unsaved");return
{(["unsaved","saving","saved"] as const).map(value=>)}

State controls only; no background save.

;}}; diff --git a/explorer/stories/recipes/avatar-stack.stories.tsx b/explorer/stories/recipes/avatar-stack.stories.tsx new file mode 100644 index 0000000..6fdabd9 --- /dev/null +++ b/explorer/stories/recipes/avatar-stack.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/avatar-stack'; + +const meta={title:"Data/Avatar stack",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/avatar-stack`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return ;}}; diff --git a/explorer/stories/recipes/carousel.stories.tsx b/explorer/stories/recipes/carousel.stories.tsx new file mode 100644 index 0000000..65ccc01 --- /dev/null +++ b/explorer/stories/recipes/carousel.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/carousel'; + +const meta={title:"Motion/Carousel",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/carousel`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return {["Semantic tokens","Accessible primitives","Composable pages"].map((title,i)=>
0{i+1}

{title}

)}
;}}; diff --git a/explorer/stories/recipes/copy-button.stories.tsx b/explorer/stories/recipes/copy-button.stories.tsx new file mode 100644 index 0000000..535bbb8 --- /dev/null +++ b/explorer/stories/recipes/copy-button.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/copy-button'; + +const meta={title:"Interactions/Copy button",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/copy-button`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return
pnpm storybook
;}}; diff --git a/explorer/stories/recipes/disclosure.stories.tsx b/explorer/stories/recipes/disclosure.stories.tsx new file mode 100644 index 0000000..a40c7ef --- /dev/null +++ b/explorer/stories/recipes/disclosure.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/disclosure'; + +const meta={title:"Motion/Disclosure",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/disclosure`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return

Components read semantic CSS variables from the active theme. Change the toolbar preset to compare.

;}}; diff --git a/explorer/stories/recipes/file-dropzone.stories.tsx b/explorer/stories/recipes/file-dropzone.stories.tsx new file mode 100644 index 0000000..3c2e324 --- /dev/null +++ b/explorer/stories/recipes/file-dropzone.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/file-dropzone'; + +const meta={title:"Interactions/File dropzone",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/file-dropzone`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [files,setFiles]=useState([]);return
setFiles(files.map(file=>file.name))} hint="JSON, text or Markdown · up to 2 MB per file"/>

Selection only. No file contents are transmitted.

{files.join(", ")}
;}}; + +export const UploadLifecycle:Story={render:function Render(){ + const job=useRef<{resolve:()=>void;reject:(error:Error)=>void;progress:(percent:number)=>void}|null>(null); + const [pending,setPending]=useState(false); + return
{setPending(true);try{await new Promise((resolve,reject)=>{job.current={resolve,reject,progress:onProgress};signal.addEventListener('abort',()=>reject(new Error('Cancelled')),{once:true});});}finally{job.current=null;setPending(false);}}}/>

Manual transport harness. Resolve or reject the actual pending promise. Nothing is sent over the network.

; +}}; diff --git a/explorer/stories/recipes/hold-to-delete.stories.tsx b/explorer/stories/recipes/hold-to-delete.stories.tsx new file mode 100644 index 0000000..a80932a --- /dev/null +++ b/explorer/stories/recipes/hold-to-delete.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/hold-to-delete'; + +const meta={title:"Interactions/Hold to delete",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/hold-to-delete`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [deleted,setDeleted]=useState(false);return
{deleted?<>

Example item removed.

:setDeleted(true)}/>}
;}}; diff --git a/explorer/stories/recipes/image-comparison.stories.tsx b/explorer/stories/recipes/image-comparison.stories.tsx new file mode 100644 index 0000000..8e8eff7 --- /dev/null +++ b/explorer/stories/recipes/image-comparison.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/image-comparison'; +import {comparisonBefore,comparisonAfter} from '../fixtures'; +const meta={title:"Motion/Image comparison",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/image-comparison`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return

Drag, touch or focus the slider and use arrow keys.

;}}; diff --git a/explorer/stories/recipes/inline-edit.stories.tsx b/explorer/stories/recipes/inline-edit.stories.tsx new file mode 100644 index 0000000..08ee4a8 --- /dev/null +++ b/explorer/stories/recipes/inline-edit.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/inline-edit'; + +const meta={title:"Interactions/Inline edit",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/inline-edit`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [value,setValue]=useState("Fleet UI library");return ;}}; diff --git a/explorer/stories/recipes/kanban-board.stories.tsx b/explorer/stories/recipes/kanban-board.stories.tsx new file mode 100644 index 0000000..814a234 --- /dev/null +++ b/explorer/stories/recipes/kanban-board.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/kanban-board'; + +const meta={title:"Interactions/Kanban board",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/kanban-board`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [columns,setColumns]=useState([{id:"research",title:"Research",cards:[{id:"a",title:"Review upstream API"},{id:"b",title:"Check source license"}]},{id:"adapt",title:"Adapt",cards:[{id:"c",title:"Use semantic tokens"}]},{id:"verify",title:"Verify",cards:[{id:"d",title:"Test keyboard controls"}]}]);return ;}}; diff --git a/explorer/stories/recipes/morphing-button.stories.tsx b/explorer/stories/recipes/morphing-button.stories.tsx new file mode 100644 index 0000000..f452a9f --- /dev/null +++ b/explorer/stories/recipes/morphing-button.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/morphing-button'; + +const meta={title:"Interactions/Morphing button",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/morphing-button`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [fail,setFail]=useState(false); return
{if(fail)throw new Error("Save rejected");}}/>

Local promise only. Toggle rejection to inspect retry.

;}}; diff --git a/explorer/stories/recipes/morphing-dialog.stories.tsx b/explorer/stories/recipes/morphing-dialog.stories.tsx new file mode 100644 index 0000000..279b78a --- /dev/null +++ b/explorer/stories/recipes/morphing-dialog.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/morphing-dialog'; + +const meta={title:"Motion/Morphing dialog",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/morphing-dialog`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return One shared systemExplore the implementationOne shared systemComponents, motion and tokens share an accessible foundation. Escape closes the dialog and restores focus.;}}; diff --git a/explorer/stories/recipes/morphing-popover.stories.tsx b/explorer/stories/recipes/morphing-popover.stories.tsx new file mode 100644 index 0000000..23958cb --- /dev/null +++ b/explorer/stories/recipes/morphing-popover.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/morphing-popover'; + +const meta={title:"Motion/Morphing popover",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/morphing-popover`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return Library details

Fleet UI

A shared foundation for product interfaces.

;}}; diff --git a/explorer/stories/recipes/multi-step-form.stories.tsx b/explorer/stories/recipes/multi-step-form.stories.tsx new file mode 100644 index 0000000..2bf6cf6 --- /dev/null +++ b/explorer/stories/recipes/multi-step-form.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/multi-step-form'; + +const meta={title:"Interactions/Multi-step form",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/multi-step-form`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [reject,setReject]=useState(false);return
{if(reject)throw new Error("Example request rejected. Your choices are preserved.");}}/>

Demonstrates a local callback. No account is created.

;}}; diff --git a/explorer/stories/recipes/notification-bell.stories.tsx b/explorer/stories/recipes/notification-bell.stories.tsx new file mode 100644 index 0000000..1e793bf --- /dev/null +++ b/explorer/stories/recipes/notification-bell.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/notification-bell'; + +const meta={title:"Feedback/Notification bell",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/notification-bell`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [items,setItems]=useState([{id:"1",title:"Explorer preview is ready",time:"Example notification"}]);return
;}}; diff --git a/explorer/stories/recipes/pricing-calculator.stories.tsx b/explorer/stories/recipes/pricing-calculator.stories.tsx new file mode 100644 index 0000000..9872c6a --- /dev/null +++ b/explorer/stories/recipes/pricing-calculator.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/pricing-calculator'; + +const meta={title:"Data/Pricing calculator",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/pricing-calculator`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return

Illustrative pricing; these are not Fleet product offers.

;}}; diff --git a/explorer/stories/recipes/progress-stepper.stories.tsx b/explorer/stories/recipes/progress-stepper.stories.tsx new file mode 100644 index 0000000..7e7db9b --- /dev/null +++ b/explorer/stories/recipes/progress-stepper.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/progress-stepper'; + +const meta={title:"Feedback/Progress stepper",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/progress-stepper`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [step,setStep]=useState(0);return
;}}; diff --git a/explorer/stories/recipes/reorder-list.stories.tsx b/explorer/stories/recipes/reorder-list.stories.tsx new file mode 100644 index 0000000..5e24e84 --- /dev/null +++ b/explorer/stories/recipes/reorder-list.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/reorder-list'; + +const meta={title:"Interactions/Reorder list",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/reorder-list`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [items,setItems]=useState([{id:"tokens",label:"Theme tokens",meta:"Foundation"},{id:"inputs",label:"Input components",meta:"Interaction"},{id:"blocks",label:"Page blocks",meta:"Composition"}]);return ;}}; diff --git a/explorer/stories/recipes/segmented-control.stories.tsx b/explorer/stories/recipes/segmented-control.stories.tsx new file mode 100644 index 0000000..c5c6773 --- /dev/null +++ b/explorer/stories/recipes/segmented-control.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/segmented-control'; + +const meta={title:"Interactions/Segmented control",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/segmented-control`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [value,setValue]=useState("Components");return

{value}

;}}; diff --git a/explorer/stories/recipes/sliding-number.stories.tsx b/explorer/stories/recipes/sliding-number.stories.tsx new file mode 100644 index 0000000..28f07f1 --- /dev/null +++ b/explorer/stories/recipes/sliding-number.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/sliding-number'; + +const meta={title:"Motion/Sliding number",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/sliding-number`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [value,setValue]=useState(24);return
;}}; diff --git a/explorer/stories/recipes/sortable-grid.stories.tsx b/explorer/stories/recipes/sortable-grid.stories.tsx new file mode 100644 index 0000000..8f5304c --- /dev/null +++ b/explorer/stories/recipes/sortable-grid.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/sortable-grid'; + +const meta={title:"Interactions/Sortable grid",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/sortable-grid`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [items,setItems]=useState(["Components","Patterns","Tokens","Motion","Blocks","Templates"].map(label=>({id:label,label,icon:})));return ;}}; diff --git a/explorer/stories/recipes/storage-meter.stories.tsx b/explorer/stories/recipes/storage-meter.stories.tsx new file mode 100644 index 0000000..fa8200d --- /dev/null +++ b/explorer/stories/recipes/storage-meter.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/storage-meter'; + +const meta={title:"Data/Storage meter",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/storage-meter`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){return

Illustrative storage only.

;}}; diff --git a/explorer/stories/recipes/tag-input.stories.tsx b/explorer/stories/recipes/tag-input.stories.tsx new file mode 100644 index 0000000..b9a2af2 --- /dev/null +++ b/explorer/stories/recipes/tag-input.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/tag-input'; + +const meta={title:"Interactions/Tag input",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/tag-input`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [tags,setTags]=useState(["React","Accessible"]);return ;}}; diff --git a/explorer/stories/recipes/text-effect.stories.tsx b/explorer/stories/recipes/text-effect.stories.tsx new file mode 100644 index 0000000..c1e9f73 --- /dev/null +++ b/explorer/stories/recipes/text-effect.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/text-effect'; + +const meta={title:"Motion/Text effect",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/text-effect`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [key,setKey]=useState(0);return
Build with a shared visual language.
;}}; diff --git a/explorer/stories/recipes/text-morph.stories.tsx b/explorer/stories/recipes/text-morph.stories.tsx new file mode 100644 index 0000000..a529e07 --- /dev/null +++ b/explorer/stories/recipes/text-morph.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/text-morph'; + +const meta={title:"Motion/Text morph",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/text-morph`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [word,setWord]=useState("Components");return
{word}
;}}; diff --git a/explorer/stories/recipes/toolbar-dynamic.stories.tsx b/explorer/stories/recipes/toolbar-dynamic.stories.tsx new file mode 100644 index 0000000..9481149 --- /dev/null +++ b/explorer/stories/recipes/toolbar-dynamic.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/toolbar-dynamic'; +import ToolbarDynamic from '@saas-maker/ui/recipes/toolbar-dynamic'; +const meta={title:"Motion/Dynamic toolbar",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/toolbar-dynamic`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [query,setQuery]=useState("");return
{query?`Local search: ${query}`:"Expand the search field."}
;}}; diff --git a/explorer/stories/recipes/transition-panel.stories.tsx b/explorer/stories/recipes/transition-panel.stories.tsx new file mode 100644 index 0000000..f20a558 --- /dev/null +++ b/explorer/stories/recipes/transition-panel.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/transition-panel'; + +const meta={title:"Motion/Transition panel",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/transition-panel`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [index,setIndex]=useState(0);return
{["Tokens","Components","Pages"].map((label,i)=>)}
{["Semantic colors keep the system coherent.","Accessible primitives support each interaction.","Blocks compose into real product pages."].map(text=>
{text}
)}
;}}; diff --git a/explorer/stories/recipes/tree-view.stories.tsx b/explorer/stories/recipes/tree-view.stories.tsx new file mode 100644 index 0000000..0884e67 --- /dev/null +++ b/explorer/stories/recipes/tree-view.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import {useState,useRef} from 'react'; +import { Button } from '@saas-maker/ui/components/button'; +import { Input } from '@saas-maker/ui/components/input'; +import { Box, FileText, Folder, Search, Check, ArrowRight, Cloud, Code, Globe, Settings } from 'lucide-react'; +import * as C from '@saas-maker/ui/recipes/tree-view'; + +const meta={title:"Navigation/Tree view",parameters:{docs:{description:{component:"Import from `@saas-maker/ui/recipes/tree-view`. Example data is local to this story. Use the toolbar to compare presets, light/dark and reduced motion. Upstream attribution and pinned source are in THIRD_PARTY_NOTICES.md and packages/ui/upstream.json."}}}} satisfies Meta; +export default meta; +type Story=StoryObj; +export const Playground:Story={render:function Render(){const [selected,setSelected]=useState("");return
{selected||"Select a file"}
;}}; diff --git a/explorer/stories/resizable.stories.tsx b/explorer/stories/resizable.stories.tsx new file mode 100644 index 0000000..b2f020b --- /dev/null +++ b/explorer/stories/resizable.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/resizable"; + +const meta = { title: "Navigation/Resizable panels", component: C.ResizablePanelGroup, parameters: {"docs": {"description": {"component": "Resize with a pointer or focus the divider and use arrow keys.\n\nImport from `@saas-maker/ui/components/resizable`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
Components
Preview and controls
; } }; diff --git a/explorer/stories/scroll-area.stories.tsx b/explorer/stories/scroll-area.stories.tsx new file mode 100644 index 0000000..2c57b60 --- /dev/null +++ b/explorer/stories/scroll-area.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/scroll-area"; + +const meta = { title: "Navigation/Scroll area", component: C.ScrollArea, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/scroll-area`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return

Library categories

{["Foundations","Inputs","Navigation","Overlays","Feedback","Data","Interactions","Motion","Effects","Blocks","Templates","Native"].map(name=>

{name}

)}
; } }; diff --git a/explorer/stories/select.stories.tsx b/explorer/stories/select.stories.tsx new file mode 100644 index 0000000..8564c5f --- /dev/null +++ b/explorer/stories/select.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/select"; + +const meta = { title: "Inputs/Select", component: C.Select, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/select`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { const [project,setProject]=useState("ui-library");return
FleetUI LibraryApp HealthCodeVetterUnavailable project

Selected: {project}

; } }; diff --git a/explorer/stories/separator.stories.tsx b/explorer/stories/separator.stories.tsx new file mode 100644 index 0000000..0d09bdf --- /dev/null +++ b/explorer/stories/separator.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/separator"; + +const meta = { title: "Foundations/Separator", component: C.Separator, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/separator`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return

One shared design system.

WebSwiftUITokens
; } }; diff --git a/explorer/stories/sheet.stories.tsx b/explorer/stories/sheet.stories.tsx new file mode 100644 index 0000000..6aa0bfe --- /dev/null +++ b/explorer/stories/sheet.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/sheet"; + +const meta = { title: "Overlays/Sheet", component: C.Sheet, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/sheet`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return Component inspectorInspect shared properties without leaving the current view.
Use Escape or the close button to return to the trigger.
; } }; diff --git a/explorer/stories/skeleton.stories.tsx b/explorer/stories/skeleton.stories.tsx new file mode 100644 index 0000000..1995456 --- /dev/null +++ b/explorer/stories/skeleton.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/skeleton"; + +const meta = { title: "Feedback/Skeleton", component: C.Skeleton, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/skeleton`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return

Keep placeholders aligned with the actual content geometry.

; } }; diff --git a/explorer/stories/slider.stories.tsx b/explorer/stories/slider.stories.tsx new file mode 100644 index 0000000..4a9c3f5 --- /dev/null +++ b/explorer/stories/slider.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/slider"; + +const meta = { title: "Inputs/Slider", component: C.Slider, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/slider`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { const [range,setRange]=useState([25,75]);return
; } }; diff --git a/explorer/stories/sonner.stories.tsx b/explorer/stories/sonner.stories.tsx new file mode 100644 index 0000000..256a603 --- /dev/null +++ b/explorer/stories/sonner.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/sonner"; +import { toast } from "sonner"; +const meta = { title: "Feedback/Toast", component: C.Toaster, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/sonner`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return

Local preview only; no product data changes.

; } }; diff --git a/explorer/stories/start.stories.tsx b/explorer/stories/start.stories.tsx new file mode 100644 index 0000000..c5d6f8b --- /dev/null +++ b/explorer/stories/start.stories.tsx @@ -0,0 +1,7 @@ +import type {Meta,StoryObj} from '@storybook/react-vite'; +import {Button} from '@saas-maker/ui/components/button'; +import {Badge} from '@saas-maker/ui/components/badge'; +import {ArrowUpRight,Box,Layers,MoveRight} from 'lucide-react'; +const meta={title:'Start here/Overview',tags:['!autodocs'],parameters:{layout:'padded',docs:{description:{component:'The live explorer for the shared Fleet UI library. Every example uses the actual exported source.'}}}} satisfies Meta; +export default meta; +export const Explore:StoryObj={render:()=>
Fleet UIOpen source

The shared library

A foundation for
every interface.

Explore components, complete patterns and motion in the same system. Change the theme, inspect the code, and try each interaction.

{[{title:'Components',body:'Inputs, navigation, overlays, feedback and data views.',path:'/story/foundations-button--playground'},{title:'Interactions',body:'Editing, selection, upload, reorder and multi-step flows.',path:'/story/interactions-kanban-board--playground'},{title:'Motion & effects',body:'Shared layout, text transitions and focused visual treatments.',path:'/story/motion-morphing-dialog--playground'},{title:'Page systems',body:'Blocks and content-driven Gallery, Workbench and Console templates.',path:'/story/templates-console--workspace'}].map(item=>

{item.title}

{item.body}

)}

Use this explorer

  1. 01 / CompareSwitch preset, light/dark and system/reduced motion in the toolbar.
  2. 02 / InspectOpen Guide for composition code and documented import paths.
  3. 03 / TestUse controls, keyboard navigation, viewport sizes and the accessibility panel.

Built with upstream

Selective MIT adaptations from shadcn/ui, Xevrion UI Lab, Motion Primitives and Magic UI. Their notices and pinned source revisions are retained in the repository. Transitions.dev is a motion reference; its restricted collection is not redistributed.

Examples use local illustrative data. They do not create accounts or send files. This is the shared library; product adoption and portfolio quality scores are assessed separately.

}; diff --git a/explorer/stories/switch.stories.tsx b/explorer/stories/switch.stories.tsx new file mode 100644 index 0000000..c4c9369 --- /dev/null +++ b/explorer/stories/switch.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/switch"; + +const meta = { title: "Inputs/Switch", component: C.Switch, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/switch`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { const [on,setOn]=useState(true);return

Preview setting only: {on ? "enabled" : "disabled"}.

; } }; diff --git a/explorer/stories/table.stories.tsx b/explorer/stories/table.stories.tsx new file mode 100644 index 0000000..efcf5eb --- /dev/null +++ b/explorer/stories/table.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/table"; + +const meta = { title: "Data/Table", component: C.Table, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/table`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return Shared package roles.PackagePurpose{[{name:"ui",purpose:"Components, blocks, and tokens"},{name:"motion",purpose:"Progressive scroll motion"},{name:"templates",purpose:"Gallery, Workbench, and Console"}].map(row=>{row.name}{row.purpose})}; } }; diff --git a/explorer/stories/tabs.stories.tsx b/explorer/stories/tabs.stories.tsx new file mode 100644 index 0000000..873e25d --- /dev/null +++ b/explorer/stories/tabs.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/tabs"; + +const meta = { title: "Navigation/Tabs", component: C.Tabs, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/tabs`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return ComponentsTokensSwiftUIAccessible primitives and composed interactions.Shared color, type, spacing, and surface roles.Native controls use the same semantic design language.; } }; diff --git a/explorer/stories/templates.stories.tsx b/explorer/stories/templates.stories.tsx new file mode 100644 index 0000000..eafeb1e --- /dev/null +++ b/explorer/stories/templates.stories.tsx @@ -0,0 +1,20 @@ +import type {Meta,StoryObj} from '@storybook/react-vite'; +import {useState} from 'react'; +import {ConsolePage} from '@saas-maker/templates/console-page'; +import {GalleryPage} from '@saas-maker/templates/gallery-page'; +import {WorkbenchPage} from '@saas-maker/templates/workbench-page'; +import {galleryContent,workbenchContent} from '@saas-maker/templates/schema'; +import {Button} from '@saas-maker/ui/components/button'; +import {DataTable} from '@saas-maker/ui/components/data-table'; +import {FilterBar,FilterSearch} from '@saas-maker/ui/blocks/filter-bar'; +import {SearchPalette} from '@saas-maker/ui/blocks/search-palette'; +import {Box} from 'lucide-react'; +import kith from '../../site/src/content/kith.json'; +import codevetter from '../../site/src/content/codevetter.json'; +const meta={title:'Templates/Console',parameters:{layout:'fullscreen',docs:{description:{component:'Complete compositions using the shipped templates. Landing pages use existing repository content snapshots, with local assets. Follow product links to verify current availability.'}}}} satisfies Meta;export default meta; +const rows=[{name:'Button',kind:'Component',path:'components/button'},{name:'Dialog',kind:'Component',path:'components/dialog'},{name:'Kanban board',kind:'Interaction',path:'recipes/kanban-board'},{name:'Globe',kind:'Effect',path:'effects/globe'}]; +export const Workspace:StoryObj={render:function Render(){const [query,setQuery]=useState('');const [selected,setSelected]=useState('');return ,href:'#'}} title="Library" description="Explore the shared source. This workspace uses local example data." nav={[{title:'Library',items:[{label:'All modules',href:'#',active:true,count:rows.length},{label:'Source',href:'https://github.com/sass-maker/ui-library',external:true}]}]} breadcrumbs={[{label:'Fleet'},{label:'UI library'}]} actions={} sidebarFooter={Example workspace}>
row.name.toLowerCase().includes(query.toLowerCase()))} columns={[{id:'name',header:'Name',value:row=>row.name},{id:'kind',header:'Kind',value:row=>row.kind},{id:'path',header:'Import path',value:row=>row.path}]} getRowId={row=>row.path}/>

{selected}

rows.filter(row=>row.name.toLowerCase().includes(q.toLowerCase())).map(row=>({id:row.path,title:row.name,subtitle:row.kind}))}]} onSelect={row=>setSelected(`Selected ${row.title}`)}/>
;}}; +// Keep asset requests relative so the same stories work under GitHub Pages /ui-library/. +function relativeAssets(data:T):T{return JSON.parse(JSON.stringify(data).replaceAll('"/demo/','"./demo/'));} +export const Gallery:StoryObj={name:'Gallery · Kith',render:()=> }; +export const Workbench:StoryObj={name:'Workbench · CodeVetter',render:()=> }; diff --git a/explorer/stories/textarea.stories.tsx b/explorer/stories/textarea.stories.tsx new file mode 100644 index 0000000..b78776d --- /dev/null +++ b/explorer/stories/textarea.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/textarea"; + +const meta = { title: "Inputs/Textarea", component: C.Textarea, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/textarea`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
; } }; diff --git a/explorer/stories/toggle-group.stories.tsx b/explorer/stories/toggle-group.stories.tsx new file mode 100644 index 0000000..7984cbd --- /dev/null +++ b/explorer/stories/toggle-group.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/toggle-group"; + +const meta = { title: "Inputs/Toggle group", component: C.ToggleGroup, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/toggle-group`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return ; } }; diff --git a/explorer/stories/toggle.stories.tsx b/explorer/stories/toggle.stories.tsx new file mode 100644 index 0000000..ea4b7b0 --- /dev/null +++ b/explorer/stories/toggle.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/toggle"; + +const meta = { title: "Inputs/Toggle", component: C.Toggle, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/toggle`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
; } }; diff --git a/explorer/stories/tokens.stories.tsx b/explorer/stories/tokens.stories.tsx new file mode 100644 index 0000000..ee0dc42 --- /dev/null +++ b/explorer/stories/tokens.stories.tsx @@ -0,0 +1,4 @@ +import type {Meta,StoryObj} from '@storybook/react-vite'; +const meta={title:'Foundations/Tokens',parameters:{docs:{description:{component:'theme.css is the source of truth for semantic roles, typography and spacing. Switch presets and mode in the toolbar. Generated JSON and Swift tokens share this source.'}}}} satisfies Meta;export default meta; +export const Palette:StoryObj={render:()=>

Semantic color roles

{['background','foreground','surface','card','muted','muted-foreground','primary','brand','brand-soft','brand-ink','border','destructive','success','warning'].map(role=>

--{role}

)}
}; +export const Typography:StoryObj={render:()=>

Eyebrow / Mono

The shared visual language.

A clear hierarchy, comfortable measure and purposeful contrast.

Section heading

Body text uses the theme’s sans family. Display headings and accent words follow the preset. Product identity comes from tokens and content.

--font-sans · --font-display · --font-mono
}; diff --git a/explorer/stories/tooltip.stories.tsx b/explorer/stories/tooltip.stories.tsx new file mode 100644 index 0000000..a3edb8b --- /dev/null +++ b/explorer/stories/tooltip.stories.tsx @@ -0,0 +1,11 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { Button } from "@saas-maker/ui/components/button"; +import { Check, Search, Plus, Settings, Bold, Italic, ArrowRight, FolderOpen, Mail, Upload, Sparkles } from "lucide-react"; +import * as C from "@saas-maker/ui/components/tooltip"; + +const meta = { title: "Overlays/Tooltip", component: C.Tooltip, parameters: {"docs": {"description": {"component": "\n\nImport from `@saas-maker/ui/components/tooltip`. Preview theme, color mode, motion preference, and device width from the toolbar. Example data stays in this explorer."}}} } satisfies Meta; +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: function Render() { return
{[{label:"Search components",icon:},{label:"Create project",icon:},{label:"Open settings",icon:}].map(item=>{item.label})}
; } }; diff --git a/explorer/tsconfig.json b/explorer/tsconfig.json new file mode 100644 index 0000000..246eb3f --- /dev/null +++ b/explorer/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { "types": ["vite/client", "node"], "allowSyntheticDefaultImports": true }, + "include": [".storybook/**/*.ts", ".storybook/**/*.tsx", "stories/**/*.tsx", "stories/**/*.ts"] +} diff --git a/package.json b/package.json index 1d16888..2f94800 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "dev": "pnpm --filter @saas-maker/ui-site dev", "build": "pnpm --filter @saas-maker/ui-site build && pnpm registry:build", - "check": "pnpm test && pnpm typecheck && pnpm --filter @saas-maker/ui-site check", + "check": "pnpm test && pnpm typecheck && pnpm storybook:check && pnpm check:explorer && pnpm --filter @saas-maker/ui-site check", "check:browser": "node site/scripts/browser-check.mjs", "registry:build": "shadcn build --output site/dist/r", "typecheck": "tsc -p tsconfig.json", @@ -19,7 +19,11 @@ "landings:check": "pnpm --dir archives/ios-landings run check", "landings:check:one": "pnpm --dir archives/ios-landings run check:one", "landings:deploy": "pnpm --dir archives/ios-landings run deploy", - "landings:deploy:daddyrad": "pnpm --dir archives/ios-landings run deploy:daddyrad" + "landings:deploy:daddyrad": "pnpm --dir archives/ios-landings run deploy:daddyrad", + "storybook": "pnpm --filter @saas-maker/ui-explorer dev", + "storybook:build": "pnpm --filter @saas-maker/ui-explorer build", + "storybook:check": "pnpm --filter @saas-maker/ui-explorer check", + "check:explorer": "node explorer/scripts/check-explorer.mjs" }, "devDependencies": { "@types/react": "19.3.0", diff --git a/packages/tokens/tokens.json b/packages/tokens/tokens.json index a04fb61..f3adc20 100644 --- a/packages/tokens/tokens.json +++ b/packages/tokens/tokens.json @@ -110,21 +110,21 @@ 1 ], "destructive": [ - 0.874, - 0.133, - 0.147, + 0.774, + 0.019, + 0.086, 1 ], "success": [ 0, - 0.601, - 0.339, + 0.447, + 0.231, 1 ], "warning": [ - 0.896, - 0.62, - 0.131, + 0.529, + 0.306, + 0, 1 ] }, @@ -261,21 +261,21 @@ 1 ], "destructive": [ - 0.874, - 0.133, - 0.147, + 0.774, + 0.019, + 0.086, 1 ], "success": [ 0, - 0.601, - 0.339, + 0.447, + 0.231, 1 ], "warning": [ - 0.896, - 0.62, - 0.131, + 0.529, + 0.306, + 0, 1 ] }, @@ -412,21 +412,21 @@ 1 ], "destructive": [ - 0.874, - 0.133, - 0.147, + 1, + 0.522, + 0.522, 1 ], "success": [ - 0, - 0.601, - 0.339, + 0.459, + 0.839, + 0.639, 1 ], "warning": [ - 0.896, - 0.62, - 0.131, + 0.929, + 0.78, + 0.416, 1 ] }, @@ -533,9 +533,9 @@ 1 ], "brand": [ - 0.736, - 0.299, - 0.192, + 0.656, + 0.225, + 0.117, 1 ], "brand-foreground": [ @@ -551,9 +551,9 @@ 1 ], "accent-ink": [ - 0.736, - 0.299, - 0.192, + 0.656, + 0.225, + 0.117, 1 ], "tone-ink": [ @@ -563,21 +563,21 @@ 1 ], "destructive": [ - 0.874, - 0.133, - 0.147, + 0.774, + 0.019, + 0.086, 1 ], "success": [ 0, - 0.601, - 0.339, + 0.447, + 0.231, 1 ], "warning": [ - 0.896, - 0.62, - 0.131, + 0.529, + 0.306, + 0, 1 ] }, @@ -714,21 +714,21 @@ 1 ], "destructive": [ - 0.874, - 0.133, - 0.147, + 0.774, + 0.019, + 0.086, 1 ], "success": [ 0, - 0.601, - 0.339, + 0.447, + 0.231, 1 ], "warning": [ - 0.896, - 0.62, - 0.131, + 0.529, + 0.306, + 0, 1 ] }, @@ -835,9 +835,9 @@ 0.16 ], "brand": [ - 0.769, - 0.349, - 0.184, + 0.604, + 0.231, + 0.106, 1 ], "brand-foreground": [ @@ -853,9 +853,9 @@ 1 ], "accent-ink": [ - 0.769, - 0.349, - 0.184, + 0.604, + 0.231, + 0.106, 1 ], "tone-ink": [ @@ -865,21 +865,21 @@ 1 ], "destructive": [ - 0.874, - 0.133, - 0.147, + 0.774, + 0.019, + 0.086, 1 ], "success": [ 0, - 0.601, - 0.339, + 0.447, + 0.231, 1 ], "warning": [ - 0.896, - 0.62, - 0.131, + 0.529, + 0.306, + 0, 1 ] }, @@ -986,9 +986,9 @@ 1 ], "brand": [ - 0.29, - 0.486, - 0.949, + 0.459, + 0.608, + 0.992, 1 ], "brand-foreground": [ @@ -998,15 +998,15 @@ 1 ], "brand-soft": [ - 0.29, - 0.486, - 0.949, + 0.459, + 0.608, + 0.992, 0.18 ], "accent-ink": [ - 0.29, - 0.486, - 0.949, + 0.459, + 0.608, + 0.992, 1 ], "tone-ink": [ @@ -1922,21 +1922,21 @@ 1 ], "destructive": [ - 0.874, - 0.133, - 0.147, + 1, + 0.522, + 0.522, 1 ], "success": [ - 0, - 0.601, - 0.339, + 0.459, + 0.839, + 0.639, 1 ], "warning": [ - 0.896, - 0.62, - 0.131, + 0.929, + 0.78, + 0.416, 1 ] }, diff --git a/packages/ui/package.json b/packages/ui/package.json index bb61c6f..a13c4d7 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -25,7 +25,9 @@ "./footer-html": { "types": "./footer-html.d.ts", "default": "./footer-html.js" - } + }, + "./recipes/*": "./src/recipes/*.tsx", + "./effects/*": "./src/effects/*.tsx" }, "dependencies": { "@tanstack/react-table": "9.2.4", @@ -36,7 +38,15 @@ "radix-ui": "1.6.7", "sonner": "2.0.8", "tailwind-merge": "3.7.0", - "tw-animate-css": "1.4.0" + "tw-animate-css": "1.4.0", + "motion": "13.4.3", + "input-otp": "1.5.0", + "react-day-picker": "10.0.2", + "react-resizable-panels": "4.14.8", + "vaul": "1.1.2", + "react-use-measure": "2.1.7", + "cobe": "2.0.1", + "@base-ui/react": "1.9.0" }, "peerDependencies": { "react": ">=19", diff --git a/packages/ui/src/blocks/features.tsx b/packages/ui/src/blocks/features.tsx index 8deb007..1407140 100644 --- a/packages/ui/src/blocks/features.tsx +++ b/packages/ui/src/blocks/features.tsx @@ -41,8 +41,8 @@ export function FeatureGrid({ columns === 4 && "sm:[&:nth-child(2n+1)]:pl-0 lg:[&:nth-child(2n+1)]:pl-7 lg:[&:nth-child(4n+1)]:pl-0", )} > - {f.kicker && {f.kicker}} - {f.icon && {f.icon}} + {f.kicker && {f.kicker}} + {f.icon && {f.icon}}

{f.title}

{f.body}

{f.meta &&
{f.meta}
} @@ -64,7 +64,7 @@ export function FeatureGrid({ )} > {f.icon && ( - + {f.icon} )} @@ -91,7 +91,7 @@ export function FeatureSpread({ {items.map((it, i) => (
- {it.kicker &&

{it.kicker}

} + {it.kicker &&

{it.kicker}

}

{it.title}

{it.body}
{it.footer &&
{it.footer}
} @@ -115,7 +115,7 @@ export function Steps({
    {items.map((s, i) => (
  1. - {String(i + 1).padStart(2, "0")} + {String(i + 1).padStart(2, "0")}

    {s.title}

    {s.body}

    {s.detail &&
    {s.detail}
    } diff --git a/packages/ui/src/blocks/search-palette.tsx b/packages/ui/src/blocks/search-palette.tsx index 3aade63..6c78652 100644 --- a/packages/ui/src/blocks/search-palette.tsx +++ b/packages/ui/src/blocks/search-palette.tsx @@ -65,7 +65,7 @@ export function SearchTrigger({ label = "Search", className }: { label?: string; ); diff --git a/packages/ui/src/components/alert-dialog.tsx b/packages/ui/src/components/alert-dialog.tsx new file mode 100644 index 0000000..cf2f6bd --- /dev/null +++ b/packages/ui/src/components/alert-dialog.tsx @@ -0,0 +1,201 @@ +/** Adapted from shadcn-ui/ui (3d98900e9b73c45f035f8109e9d41ba6a51685e7). + * https://github.com/shadcn-ui/ui/blob/3d98900e9b73c45f035f8109e9d41ba6a51685e7/apps/v4/registry/new-york-v4/ui/alert-dialog.tsx + * MIT; original copyright and permission notice in THIRD_PARTY_NOTICES.md. + * Fleet changes: semantic tokens, framework-independent imports, and isolated demos. + */ +"use client" + +import * as React from "react" +import { cn } from "../lib/utils" +import { AlertDialog as AlertDialogPrimitive } from "radix-ui" + +import { Button } from "./button" + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + size = "default", + ...props +}: React.ComponentProps & { + size?: "default" | "sm" +}) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
    + ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
    + ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogMedia({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
    + ) +} + +function AlertDialogAction({ + className, + variant = "default", + size = "default", + ...props +}: React.ComponentProps & + Pick, "variant" | "size">) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + variant = "outline", + size = "default", + ...props +}: React.ComponentProps & + Pick, "variant" | "size">) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogMedia, + AlertDialogOverlay, + AlertDialogPortal, + AlertDialogTitle, + AlertDialogTrigger, +} diff --git a/packages/ui/src/components/alert.tsx b/packages/ui/src/components/alert.tsx new file mode 100644 index 0000000..ef794b5 --- /dev/null +++ b/packages/ui/src/components/alert.tsx @@ -0,0 +1,70 @@ +/** Adapted from shadcn-ui/ui (3d98900e9b73c45f035f8109e9d41ba6a51685e7). + * https://github.com/shadcn-ui/ui/blob/3d98900e9b73c45f035f8109e9d41ba6a51685e7/apps/v4/registry/new-york-v4/ui/alert.tsx + * MIT; original copyright and permission notice in THIRD_PARTY_NOTICES.md. + * Fleet changes: semantic tokens, framework-independent imports, and isolated demos. + */ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "../lib/utils" + +const alertVariants = cva( + "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
    + ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
    + ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
    + ) +} + +export { Alert, AlertTitle, AlertDescription } diff --git a/packages/ui/src/components/badge.tsx b/packages/ui/src/components/badge.tsx index b859605..1dc768a 100644 --- a/packages/ui/src/components/badge.tsx +++ b/packages/ui/src/components/badge.tsx @@ -12,7 +12,7 @@ const badgeVariants = cva( secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", destructive: - "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90", + "bg-destructive text-background focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90", outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground", diff --git a/packages/ui/src/components/button.tsx b/packages/ui/src/components/button.tsx index 6d9c9fb..7b3d1c1 100644 --- a/packages/ui/src/components/button.tsx +++ b/packages/ui/src/components/button.tsx @@ -11,7 +11,7 @@ const buttonVariants = cva( default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/88", brand: "bg-brand text-brand-foreground shadow-xs hover:bg-brand/90", destructive: - "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40", + "bg-destructive text-background hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", secondary: diff --git a/packages/ui/src/components/calendar.tsx b/packages/ui/src/components/calendar.tsx new file mode 100644 index 0000000..816027c --- /dev/null +++ b/packages/ui/src/components/calendar.tsx @@ -0,0 +1,226 @@ +/** Adapted from shadcn-ui/ui (3d98900e9b73c45f035f8109e9d41ba6a51685e7). + * https://github.com/shadcn-ui/ui/blob/3d98900e9b73c45f035f8109e9d41ba6a51685e7/apps/v4/registry/new-york-v4/ui/calendar.tsx + * MIT; original copyright and permission notice in THIRD_PARTY_NOTICES.md. + * Fleet changes: semantic tokens, framework-independent imports, and isolated demos. + */ +"use client" + +import * as React from "react" +import { cn } from "../lib/utils" +import { + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, +} from "lucide-react" +import { + DayPicker, + getDefaultClassNames, + type DayButton, +} from "react-day-picker" + +import { Button, buttonVariants } from "./button" + +function Calendar({ + className, + classNames, + showOutsideDays = true, + captionLayout = "label", + buttonVariant = "ghost", + formatters, + components, + ...props +}: React.ComponentProps & { + buttonVariant?: React.ComponentProps["variant"] +}) { + const defaultClassNames = getDefaultClassNames() + + return ( + svg]:rotate-180`, + String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, + className + )} + captionLayout={captionLayout} + formatters={{ + formatMonthDropdown: (date) => + date.toLocaleString("default", { month: "short" }), + ...formatters, + }} + classNames={{ + root: cn("w-fit", defaultClassNames.root), + months: cn( + "relative flex flex-col gap-4 md:flex-row", + defaultClassNames.months + ), + month: cn("flex w-full flex-col gap-4", defaultClassNames.month), + nav: cn( + "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1", + defaultClassNames.nav + ), + button_previous: cn( + buttonVariants({ variant: buttonVariant }), + "size-(--cell-size) p-0 select-none aria-disabled:opacity-50", + defaultClassNames.button_previous + ), + button_next: cn( + buttonVariants({ variant: buttonVariant }), + "size-(--cell-size) p-0 select-none aria-disabled:opacity-50", + defaultClassNames.button_next + ), + month_caption: cn( + "flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)", + defaultClassNames.month_caption + ), + dropdowns: cn( + "flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium", + defaultClassNames.dropdowns + ), + dropdown_root: cn( + "relative rounded-md border border-input shadow-xs has-focus:border-ring has-focus:ring-[3px] has-focus:ring-ring/50", + defaultClassNames.dropdown_root + ), + dropdown: cn( + "absolute inset-0 bg-popover opacity-0", + defaultClassNames.dropdown + ), + caption_label: cn( + "font-medium select-none", + captionLayout === "label" + ? "text-sm" + : "flex h-8 items-center gap-1 rounded-md pr-1 pl-2 text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground", + defaultClassNames.caption_label + ), + month_grid: cn("w-full border-collapse", defaultClassNames.month_grid), + weekdays: cn("flex", defaultClassNames.weekdays), + weekday: cn( + "flex-1 rounded-md text-[0.8rem] font-normal text-muted-foreground select-none", + defaultClassNames.weekday + ), + week: cn("mt-2 flex w-full", defaultClassNames.week), + week_number_header: cn( + "w-(--cell-size) select-none", + defaultClassNames.week_number_header + ), + week_number: cn( + "text-[0.8rem] text-muted-foreground select-none", + defaultClassNames.week_number + ), + day: cn( + "group/day relative aspect-square h-full w-full p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-md", + props.showWeekNumber + ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md" + : "[&:first-child[data-selected=true]_button]:rounded-l-md", + defaultClassNames.day + ), + range_start: cn( + "rounded-l-md bg-accent", + defaultClassNames.range_start + ), + range_middle: cn("rounded-none", defaultClassNames.range_middle), + range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end), + today: cn( + "rounded-md bg-accent text-accent-foreground data-[selected=true]:rounded-none", + defaultClassNames.today + ), + outside: cn( + "text-muted-foreground aria-selected:text-muted-foreground", + defaultClassNames.outside + ), + disabled: cn( + "text-muted-foreground opacity-50", + defaultClassNames.disabled + ), + hidden: cn("invisible", defaultClassNames.hidden), + ...classNames, + }} + components={{ + Root: ({ className, rootRef, ...props }) => { + return ( +
    + ) + }, + Chevron: ({ className, orientation, ...props }) => { + if (orientation === "left") { + return ( + + ) + } + + if (orientation === "right") { + return ( + + ) + } + + return ( + + ) + }, + DayButton: CalendarDayButton, + WeekNumber: ({ children, ...props }) => { + return ( + +
    + {children} +
    + + ) + }, + ...components, + }} + {...props} + /> + ) +} + +function CalendarDayButton({ + className, + day, + modifiers, + ...props +}: React.ComponentProps) { + const defaultClassNames = getDefaultClassNames() + + const ref = React.useRef(null) + React.useEffect(() => { + if (modifiers.focused) ref.current?.focus() + }, [modifiers.focused]) + + return ( +