Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2d1d0da
Add React workspace with official shadcn Base UI components
silveltman Oct 9, 2026
db11cf6
Keep upstream client directives in the React components
silveltman Oct 9, 2026
92b9e6c
Port the Fulldev UI components to React
silveltman Oct 9, 2026
3148d9b
Port every Fulldev UI block to React with a preview gallery
silveltman Oct 9, 2026
cfe5380
Build, validate and check the React registry with the Astro one
silveltman Oct 9, 2026
b17497d
Show every React component family in the gallery
silveltman Oct 9, 2026
e1c6d93
Keep the React gallery clean in development and accessible demos
silveltman Oct 9, 2026
152f34c
Generate every style's classes for the React gallery
silveltman Oct 9, 2026
a69e9f2
Lint the React hooks and helpers with the React rules too
silveltman Oct 9, 2026
33ef834
Document installing and developing the React port
silveltman Oct 9, 2026
085a2b7
Resolve every Lucide and Simple Icons name in the React Icon
silveltman Oct 10, 2026
c0edc96
Scroll long Drawer content and export the Drawer parts
silveltman Oct 10, 2026
7a1b725
Compose caller refs with internal refs in Marquee and Doc1
silveltman Oct 10, 2026
d0232e7
Localize the mobile sidebar sheet title and description
silveltman Oct 10, 2026
4504b8d
Pick gallery pages from a menu on small screens
silveltman Oct 10, 2026
017bafe
Add a changeset for the React registry and document the React APIs
silveltman Oct 10, 2026
241464c
Regenerate the React registry for the Icon, Drawer, ref and Sidebar f…
silveltman Oct 10, 2026
e077442
Note what the content-name Icon costs in a client bundle
silveltman Oct 10, 2026
44bb4e4
Open the Sidebar1 search from the shortcut with the mobile sidebar cl…
silveltman Oct 10, 2026
147a5b7
Route the gallery only on #/ hashes and keep in-page anchors
silveltman Oct 10, 2026
3306c03
Give gallery demos distinct landmark names and valid list items
silveltman Oct 10, 2026
ed94349
Regenerate the React registry for the Sidebar1 search fix
silveltman Oct 10, 2026
ac54a9e
Keep the gallery page next to a heading anchor across reloads and styles
silveltman Oct 10, 2026
7a87ccb
Keep the gallery page for empty # links
silveltman Oct 10, 2026
e51cd35
Split the docs into Astro and React editions with the official shadcn…
silveltman Oct 10, 2026
80749e8
Cover every official shadcn/ui docs example in the React edition
silveltman Oct 10, 2026
f782a9c
Port the official shadcn/ui examples to native Astro and fix Toast
silveltman Oct 10, 2026
8e095dd
Fix the native Astro examples found in review
silveltman Oct 10, 2026
45e81da
Point upstream docs links at shadcn/ui and fix the docs build checks
silveltman Oct 10, 2026
e7b4162
Fix the type and unused-export checks
silveltman Oct 10, 2026
2e58746
Keep responsive drawers open across the breakpoint
silveltman Oct 10, 2026
471103b
Serve the built React previews in astro preview
silveltman Oct 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
11 changes: 11 additions & 0 deletions .changeset/react-registry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"fulldev-ui": minor
---

Add Fulldev UI for React: every shadcn/ui Base UI component, the Fulldev components and all 82 blocks as React components, in every shadcn/ui style. Install them with the shadcn CLI from the React registry, `https://ui.full.dev/r/react/styles/{style}/{name}.json`, starting with `npx shadcn@latest init https://ui.full.dev/r/react/styles/base-vega/base.json`. The npm package does not include the React registry.

Add the examples from the shadcn/ui documentation as `@fulldev/<component>-examples` items: the official React source in the React registry, native ports in the Astro registry for the components Fulldev UI has in Astro.

The docs now have an Astro and a React edition at `/astro/` and `/react/`; the former `/docs/`, `/components/` and `/blocks/` addresses redirect to the Astro edition. `Sidebar1` takes optional header content below its logo (a `header` slot in Astro, a `header` prop in React).

The Astro `SidebarProvider` now leaves a sidebar nested inside another provider alone: the outer one no longer updates its trigger, rail and tooltips, the nested one keeps its state out of the shared `sidebar_state` cookie, and a provider whose inline script did not run initializes on `astro:page-load`.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ A shadcn-compatible set of components built for [Astro][astro], designed for con
- **100+ Components & Blocks** — Ready-to-use UI components and pre-built page blocks
- **Tailwind CSS v4** — Styled with the latest Tailwind CSS
- **TypeScript** — Full TypeScript support
- **React**: the same components and blocks for React; see [react/README.md](react/README.md)

## Installation

Expand Down
47 changes: 46 additions & 1 deletion astro.config.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,63 @@
import { readdirSync } from "node:fs"
import { defineConfig, fontProviders } from "astro/config"
import { unified } from "@astrojs/markdown-remark"
import mdx from "@astrojs/mdx"
import liveCode from "astro-live-code"

import fulldevIntegration from "./src/lib/integration"

// The docs used to live at /docs/, /components/ and /blocks/; they are the
// Astro edition now. Netlify answers these with a 301 (netlify.toml).
// Docs tables scroll inside the content column instead of widening the page.
type Node = { type: string; tagName?: string; children?: Node[] }
const scrollTables = () => (tree: Node) => {
const wrap = (node: Node) => {
node.children = node.children?.map((child) => {
if (child.type === "element" && child.tagName === "table")
return {
type: "element",
tagName: "div",
properties: { className: ["typeset-scroll"] },
children: [child],
} as Node
wrap(child)
return child
})
}
wrap(tree)
}

const legacyRedirects = Object.fromEntries(
readdirSync("src/content/pages/astro", { recursive: true })
.map((file) => String(file).replace(/(\/?index)?\.mdx$/, ""))
.filter((path) => /^(docs|components|blocks)/.test(path))
.map((path) => [`/${path}/`, `/astro/${path}/`])
)

// React previews come from the gallery in react/, at /preview/react/. In
// development its Vite server runs on REACT_PREVIEW_PORT (pnpm dev:react).
const reactPreviewPort = process.env.REACT_PREVIEW_PORT ?? "4322"

export default defineConfig({
redirects: legacyRedirects,
server: {
host: "127.0.0.1",
port: 4321,
},
vite: {
server: {
allowedHosts: ["otis.tailb5cb80.ts.net"],
proxy: {
"/preview/react/": {
target: `http://127.0.0.1:${reactPreviewPort}`,
ws: true,
},
},
},
// Vite's preview server would inherit the proxy; astro preview serves the
// built galleries from dist/preview/react/ instead.
preview: {
proxy: {},
},
// The docs create page imports this; prebundle it so the first dev visit
// does not trigger a dependency re-optimization.
Expand All @@ -35,7 +80,7 @@ export default defineConfig({
markdown: {
// astro-live-code adds a remark plugin, which only runs on the unified
// processor. Astro 7 defaults to Sätteri.
processor: unified(),
processor: unified({ rehypePlugins: [scrollTables] }),
shikiConfig: {
themes: {
light: "github-light-high-contrast",
Expand Down
53 changes: 52 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import js from "@eslint/js"
import astro from "eslint-plugin-astro"
import reactHooks from "eslint-plugin-react-hooks"
import { defineConfig, globalIgnores } from "eslint/config"
import globals from "globals"
import tseslint from "typescript-eslint"
Expand All @@ -11,12 +12,62 @@ export default defineConfig([
extends: [js.configs.recommended, tseslint.configs.recommended],
languageOptions: { globals: { ...globals.browser, ...globals.node } },
},
{
// The React port, with the same rules as the shadcn/ui React templates.
files: ["react/**/*.{ts,tsx}"],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
],
languageOptions: { globals: globals.browser },
},
{
// Official shadcn/ui source, kept as the CLI installs it.
files: [
"react/src/components/ui/carousel.tsx",
"react/src/hooks/use-mobile.ts",
],
rules: { "react-hooks/set-state-in-effect": "off" },
},
{
// The official shadcn/ui docs examples and the docs helpers they import,
// unchanged.
files: [
"react/src/components/examples/**/*.tsx",
"react/src/components/language-selector.tsx",
"react/src/components/markdown.tsx",
"react/src/components/message-animated.tsx",
"react/src/lib/ai.ts",
"react/src/lib/message-animations.ts",
"react/src/hooks/use-copy-to-clipboard.ts",
"react/src/hooks/use-media-query.tsx",
],
rules: {
"@typescript-eslint/no-unused-expressions": "off",
"@typescript-eslint/no-unused-vars": "off",
"no-useless-assignment": "off",
"react-hooks/refs": "off",
"react-hooks/set-state-in-effect": "off",
},
},
...astro.configs.recommended,
...astro.configs["jsx-a11y-recommended"],
{
// Ports of the official shadcn/ui examples keep their content, such as
// alt="Photo" and href="#".
files: ["src/components/examples/**/*.astro"],
rules: {
"astro/jsx-a11y/anchor-is-valid": "off",
"astro/jsx-a11y/img-redundant-alt": "off",
},
},
{
// A site installs whole components, so tools such as knip can only tell
// which ones it uses when components import each other through index.ts.
files: ["**/*.{js,mjs,ts,astro}"],
files: ["**/*.{js,mjs,ts,tsx,astro}"],
// The React gallery and its build read from the repository.
ignores: ["react/gallery/**", "react/vite.config.ts"],
rules: {
"no-restricted-imports": [
"error",
Expand Down
30 changes: 23 additions & 7 deletions html-validate.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,30 @@ export default defineFlatConfig([
// the page's own breadcrumb or table of contents, so the landmark names
// repeat. A site renders each of these landmarks once.
files: [
"dist/blocks/banner/index.html",
"dist/blocks/footer/index.html",
"dist/blocks/header/index.html",
"dist/components/banner/index.html",
"dist/components/breadcrumb/index.html",
"dist/components/pagination/index.html",
"dist/components/toc/index.html",
"dist/astro/blocks/banner/index.html",
"dist/astro/blocks/footer/index.html",
"dist/astro/blocks/header/index.html",
"dist/astro/components/banner/index.html",
"dist/astro/components/breadcrumb/index.html",
"dist/astro/components/navigation-menu/index.html",
"dist/astro/components/pagination/index.html",
"dist/astro/components/sidebar/index.html",
"dist/astro/components/toc/index.html",
],
rules: { "unique-landmark": "off" },
},
{
// The official sidebar example is a full app layout: its SidebarInset is
// the app's main element, rendered here inside the docs page's own main.
files: ["dist/astro/components/sidebar/index.html"],
rules: { "no-multiple-main": "off", "element-permitted-content": "off" },
},
{
// ItemGroup and Item follow shadcn/ui: ARIA list roles on div and a, so
// an item can be a link.
files: ["dist/astro/components/item/index.html"],
rules: {
"prefer-native-element": ["error", { exclude: ["list", "listitem"] }],
},
},
])
71 changes: 48 additions & 23 deletions knip.jsonc
Original file line number Diff line number Diff line change
@@ -1,27 +1,52 @@
{
"$schema": "https://unpkg.com/knip@6/schema.json",
"entry": [
// The registry publishes these; sites import a component through its index.
"src/components/ui/*/index.ts",
"src/components/blocks/*.astro",
"src/lib/utils.ts",
// The page route loads layouts through import.meta.glob.
"src/layouts/*.astro",
// Docs pages, loaded by the content collection, import the examples.
"src/content/**/*.mdx",
// astro.config.ts passes this to astro-live-code by path.
"src/components/live-code.astro",
// Read by the html-validate CLI in the build.
"html-validate.config.mjs",
],
// Read as text by src/lib/styles.ts and the registry scripts.
"ignoreFiles": ["registry/styles/**/*.css"],
"ignoreIssues": {
// Part of the installed price component, shipped unchanged.
"src/components/ui/price/format-price-value.ts": ["exports"],
"workspaces": {
".": {
"entry": [
// The registry publishes these; sites import a component through its index.
"src/components/ui/*/index.ts",
"src/components/blocks/*.astro",
"src/lib/utils.ts",
// The page route loads layouts through import.meta.glob.
"src/layouts/*.astro",
// Example files install from the registry; the docs load them through
// import.meta.glob.
"src/components/examples/*/*.astro",
// Docs pages, loaded by the content collection, import the examples.
"src/content/**/*.mdx",
// astro.config.ts passes this to astro-live-code by path.
"src/components/live-code.astro",
// Read by the html-validate CLI in the build.
"html-validate.config.mjs",
],
// Read as text by src/lib/styles.ts and the registry scripts.
"ignoreFiles": ["registry/styles/**/*.css"],
"ignoreIssues": {
// Part of the installed price component, shipped unchanged.
"src/components/ui/price/format-price-value.ts": ["exports"],
},
"ignoreDependencies": [
// Read from node_modules by the Icon component.
"lucide-static",
],
},
"react": {
"entry": [
// The React registry publishes these as installable files.
"src/components/ui/*.tsx",
"src/components/blocks/*.tsx",
"src/components/examples/*/*.tsx",
"src/hooks/*.{ts,tsx}",
"src/lib/*.ts",
// The gallery loads its demos through import.meta.glob.
"gallery/ui/*.tsx",
"gallery/blocks/*.tsx",
],
"ignoreIssues": {
// shadcn/ui docs helpers that example items install, shipped unchanged.
"src/components/language-selector.tsx": ["exports", "types"],
"src/components/message-animated.tsx": ["types"],
},
},
},
"ignoreDependencies": [
// Read from node_modules by the Icon component.
"lucide-static",
],
}
4 changes: 3 additions & 1 deletion linkinator.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
"recurse": true,
"checkFragments": true,
"skip": ["^https?://(?!127\\.0\\.0\\.1[:/])"],
"verbosity": "error"
"skipFragment": ["^/"],
"verbosity": "error",
"retryErrors": true
}
20 changes: 20 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,23 @@ to = "https://eu.i.posthog.com/:splat"
host = "eu.i.posthog.com"
status = 200
force = true

# The docs moved to the Astro edition; the React gallery became the React
# edition, with its previews at /preview/react/.
[[redirects]]
from = "/docs/*"
to = "/astro/docs/:splat"
status = 301
force = true

[[redirects]]
from = "/components/*"
to = "/astro/components/:splat"
status = 301
force = true

[[redirects]]
from = "/blocks/*"
to = "/astro/blocks/:splat"
status = 301
force = true
13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
"README.md",
"registry.json",
"public/r",
"!public/r/react",
"src/components/blocks",
"src/components/examples",
"src/components/ui",
"src/lib/utils.ts",
"src/styles/global.css"
Expand All @@ -55,17 +57,17 @@
"dev": "pnpm stop && astro dev --host 127.0.0.1 --port 4321",
"stop": "lsof -tiTCP:4321 -sTCP:LISTEN | xargs kill 2>/dev/null || true",
"preview": "pnpm stop && pnpm build && astro preview --host 127.0.0.1 --port 4321",
"typecheck": "tsc --noEmit --noCheck && astro check --minimumFailingSeverity hint",
"typecheck": "tsc --noEmit --noCheck && astro check --minimumFailingSeverity hint && tsc --noEmit -p react",
"check": "prettier --check . && pnpm typecheck && eslint . && knip && node scripts/images.ts --check && pnpm registry:check && pnpm test",
"test": "node --test tests/*.test.mjs",
"fix": "prettier --write . && eslint . --fix",
"images": "node scripts/images.ts",
"ui:add": "node scripts/ui-add.ts",
"build": "astro build && html-validate dist && linkinator dist",
"build": "astro build && pnpm --dir react build && html-validate dist && linkinator dist",
"a11y": "pnpm build && playwright install chromium --only-shell && node scripts/a11y.mjs",
"registry:build": "pnpm registry:meta && node scripts/registry-adapt.mjs && shadcn build --output node_modules/.cache/fulldev-registry && node scripts/registry-styles.mjs && prettier --write registry.json public/r registry/styles/adapted --log-level warn",
"registry:check": "pnpm registry:validate && pnpm registry:build && git diff --exit-code -- registry.json public/r registry/styles/adapted",
"registry:validate": "node scripts/registry-validate.mjs",
"registry:build": "pnpm registry:meta && node scripts/registry-adapt.mjs && shadcn build --output node_modules/.cache/fulldev-registry && node scripts/registry-styles.mjs && shadcn build -c react -o ../node_modules/.cache/fulldev-registry-react && node scripts/registry-styles.mjs node_modules/.cache/fulldev-registry-react react/registry.json public/r/react && prettier --write registry.json react/registry.json public/r registry/styles/adapted --log-level warn",
"registry:check": "pnpm registry:validate && pnpm registry:build && git diff --exit-code -- registry.json react/registry.json public/r registry/styles/adapted",
"registry:validate": "node scripts/registry-validate.mjs && cd react && node ../scripts/registry-validate.mjs",
"release": "changeset version && pnpm install --lockfile-only",
"release:publish": "pnpm registry:check && changeset publish",
"registry:meta": "node scripts/registry-meta.mjs"
Expand Down Expand Up @@ -127,6 +129,7 @@
"eslint": "^10.11.0",
"eslint-plugin-astro": "^3.2.1",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react-hooks": "^7.1.1",
"globals": "^17.13.0",
"html-validate": "^11.16.1",
"knip": "^6.39.0",
Expand Down
Loading
Loading