@webjsdev/ui 0.3.16 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -10
- package/THIRD_PARTY_NOTICES.md +349 -0
- package/package.json +4 -2
- package/packages/registry/blocks/_shared/placeholder.svg +1 -0
- package/packages/registry/blocks/charts/chart-area-axes.ts +72 -0
- package/packages/registry/blocks/charts/chart-area-default.ts +64 -0
- package/packages/registry/blocks/charts/chart-area-gradient.ts +82 -0
- package/packages/registry/blocks/charts/chart-area-icons.ts +77 -0
- package/packages/registry/blocks/charts/chart-area-interactive.ts +204 -0
- package/packages/registry/blocks/charts/chart-area-legend.ts +72 -0
- package/packages/registry/blocks/charts/chart-area-linear.ts +64 -0
- package/packages/registry/blocks/charts/chart-area-stacked-expand.ts +77 -0
- package/packages/registry/blocks/charts/chart-area-stacked.ts +71 -0
- package/packages/registry/blocks/charts/chart-area-step.ts +68 -0
- package/packages/registry/blocks/charts/chart-bar-active.ts +93 -0
- package/packages/registry/blocks/charts/chart-bar-default.ts +60 -0
- package/packages/registry/blocks/charts/chart-bar-horizontal.ts +62 -0
- package/packages/registry/blocks/charts/chart-bar-interactive.ts +185 -0
- package/packages/registry/blocks/charts/chart-bar-label-custom.ts +78 -0
- package/packages/registry/blocks/charts/chart-bar-label.ts +66 -0
- package/packages/registry/blocks/charts/chart-bar-mixed.ts +87 -0
- package/packages/registry/blocks/charts/chart-bar-multiple.ts +67 -0
- package/packages/registry/blocks/charts/chart-bar-negative.ts +61 -0
- package/packages/registry/blocks/charts/chart-bar-stacked.ts +68 -0
- package/packages/registry/blocks/charts/chart-line-default.ts +61 -0
- package/packages/registry/blocks/charts/chart-line-dots-colors.ts +86 -0
- package/packages/registry/blocks/charts/chart-line-dots-custom.ts +75 -0
- package/packages/registry/blocks/charts/chart-line-dots.ts +65 -0
- package/packages/registry/blocks/charts/chart-line-interactive.ts +185 -0
- package/packages/registry/blocks/charts/chart-line-label-custom.ts +94 -0
- package/packages/registry/blocks/charts/chart-line-label.ts +73 -0
- package/packages/registry/blocks/charts/chart-line-linear.ts +61 -0
- package/packages/registry/blocks/charts/chart-line-multiple.ts +71 -0
- package/packages/registry/blocks/charts/chart-line-step.ts +61 -0
- package/packages/registry/blocks/charts/chart-pie-donut-active.ts +86 -0
- package/packages/registry/blocks/charts/chart-pie-donut-text.ts +89 -0
- package/packages/registry/blocks/charts/chart-pie-donut.ts +77 -0
- package/packages/registry/blocks/charts/chart-pie-interactive.ts +120 -0
- package/packages/registry/blocks/charts/chart-pie-label-custom.ts +82 -0
- package/packages/registry/blocks/charts/chart-pie-label-list.ts +86 -0
- package/packages/registry/blocks/charts/chart-pie-label.ts +77 -0
- package/packages/registry/blocks/charts/chart-pie-legend.ts +79 -0
- package/packages/registry/blocks/charts/chart-pie-separator-none.ts +77 -0
- package/packages/registry/blocks/charts/chart-pie-simple.ts +77 -0
- package/packages/registry/blocks/charts/chart-pie-stacked.ts +99 -0
- package/packages/registry/blocks/charts/chart-radar-default.ts +61 -0
- package/packages/registry/blocks/charts/chart-radar-dots.ts +61 -0
- package/packages/registry/blocks/charts/chart-radar-grid-circle-fill.ts +61 -0
- package/packages/registry/blocks/charts/chart-radar-grid-circle-no-lines.ts +61 -0
- package/packages/registry/blocks/charts/chart-radar-grid-circle.ts +61 -0
- package/packages/registry/blocks/charts/chart-radar-grid-custom.ts +61 -0
- package/packages/registry/blocks/charts/chart-radar-grid-fill.ts +61 -0
- package/packages/registry/blocks/charts/chart-radar-grid-none.ts +60 -0
- package/packages/registry/blocks/charts/chart-radar-icons.ts +76 -0
- package/packages/registry/blocks/charts/chart-radar-label-custom.ts +77 -0
- package/packages/registry/blocks/charts/chart-radar-legend.ts +70 -0
- package/packages/registry/blocks/charts/chart-radar-lines-only.ts +68 -0
- package/packages/registry/blocks/charts/chart-radar-multiple.ts +68 -0
- package/packages/registry/blocks/charts/chart-radar-radius.ts +68 -0
- package/packages/registry/blocks/charts/chart-radial-grid.ts +80 -0
- package/packages/registry/blocks/charts/chart-radial-label.ts +85 -0
- package/packages/registry/blocks/charts/chart-radial-shape.ts +81 -0
- package/packages/registry/blocks/charts/chart-radial-simple.ts +79 -0
- package/packages/registry/blocks/charts/chart-radial-stacked.ts +69 -0
- package/packages/registry/blocks/charts/chart-radial-text.ts +82 -0
- package/packages/registry/blocks/charts/chart-tooltip-advanced.ts +84 -0
- package/packages/registry/blocks/charts/chart-tooltip-default.ts +62 -0
- package/packages/registry/blocks/charts/chart-tooltip-formatter.ts +75 -0
- package/packages/registry/blocks/charts/chart-tooltip-icons.ts +68 -0
- package/packages/registry/blocks/charts/chart-tooltip-indicator-line.ts +62 -0
- package/packages/registry/blocks/charts/chart-tooltip-indicator-none.ts +62 -0
- package/packages/registry/blocks/charts/chart-tooltip-label-custom.ts +65 -0
- package/packages/registry/blocks/charts/chart-tooltip-label-formatter.ts +66 -0
- package/packages/registry/blocks/charts/chart-tooltip-label-none.ts +62 -0
- package/packages/registry/blocks/dashboard-01/components/app-sidebar.ts +108 -0
- package/packages/registry/blocks/dashboard-01/components/chart-area-interactive.ts +276 -0
- package/packages/registry/blocks/dashboard-01/components/dashboard-data.ts +89 -0
- package/packages/registry/blocks/dashboard-01/components/data-table.ts +637 -0
- package/packages/registry/blocks/dashboard-01/components/nav-documents.ts +65 -0
- package/packages/registry/blocks/dashboard-01/components/nav-main.ts +61 -0
- package/packages/registry/blocks/dashboard-01/components/nav-secondary.ts +40 -0
- package/packages/registry/blocks/dashboard-01/components/nav-user.ts +66 -0
- package/packages/registry/blocks/dashboard-01/components/section-cards.ts +59 -0
- package/packages/registry/blocks/dashboard-01/components/site-header.ts +52 -0
- package/packages/registry/blocks/dashboard-01/page.ts +39 -0
- package/packages/registry/blocks/login-01/components/login-form.ts +70 -0
- package/packages/registry/blocks/login-01/page.ts +19 -0
- package/packages/registry/blocks/login-02/components/login-form.ts +68 -0
- package/packages/registry/blocks/login-02/page.ts +36 -0
- package/packages/registry/blocks/login-03/components/login-form.ts +89 -0
- package/packages/registry/blocks/login-03/page.ts +28 -0
- package/packages/registry/blocks/login-04/components/login-form.ts +91 -0
- package/packages/registry/blocks/login-04/page.ts +20 -0
- package/packages/registry/blocks/login-05/components/login-form.ts +75 -0
- package/packages/registry/blocks/login-05/page.ts +19 -0
- package/packages/registry/blocks/preview/cards/activate-agent-dialog.ts +77 -0
- package/packages/registry/blocks/preview/cards/analytics-card.ts +64 -0
- package/packages/registry/blocks/preview/cards/anomaly-alert.ts +35 -0
- package/packages/registry/blocks/preview/cards/bar-chart-card.ts +91 -0
- package/packages/registry/blocks/preview/cards/book-appointment.ts +51 -0
- package/packages/registry/blocks/preview/cards/codespaces-card.ts +172 -0
- package/packages/registry/blocks/preview/cards/contributions-activity.ts +63 -0
- package/packages/registry/blocks/preview/cards/contributors.ts +51 -0
- package/packages/registry/blocks/preview/cards/environment-variables.ts +47 -0
- package/packages/registry/blocks/preview/cards/feedback-form.ts +61 -0
- package/packages/registry/blocks/preview/cards/file-upload.ts +51 -0
- package/packages/registry/blocks/preview/cards/github-profile.ts +66 -0
- package/packages/registry/blocks/preview/cards/icon-preview-grid.ts +47 -0
- package/packages/registry/blocks/preview/cards/invite-team.ts +78 -0
- package/packages/registry/blocks/preview/cards/invoice.ts +91 -0
- package/packages/registry/blocks/preview/cards/live-waveform.ts +330 -0
- package/packages/registry/blocks/preview/cards/no-team-members.ts +61 -0
- package/packages/registry/blocks/preview/cards/not-found.ts +46 -0
- package/packages/registry/blocks/preview/cards/observability-card.ts +44 -0
- package/packages/registry/blocks/preview/cards/pie-chart-card.ts +84 -0
- package/packages/registry/blocks/preview/cards/report-bug.ts +71 -0
- package/packages/registry/blocks/preview/cards/shipping-address.ts +79 -0
- package/packages/registry/blocks/preview/cards/shortcuts.ts +57 -0
- package/packages/registry/blocks/preview/cards/skeleton-loading.ts +37 -0
- package/packages/registry/blocks/preview/cards/sleep-report.ts +85 -0
- package/packages/registry/blocks/preview/cards/style-overview.ts +64 -0
- package/packages/registry/blocks/preview/cards/typography-specimen.ts +107 -0
- package/packages/registry/blocks/preview/cards/ui-elements.ts +164 -0
- package/packages/registry/blocks/preview/cards/usage-card.ts +63 -0
- package/packages/registry/blocks/preview/cards/visitors.ts +71 -0
- package/packages/registry/blocks/preview/cards/weekly-fitness-summary.ts +54 -0
- package/packages/registry/blocks/preview/page.ts +103 -0
- package/packages/registry/blocks/preview-02/cards/account-access.ts +70 -0
- package/packages/registry/blocks/preview-02/cards/card-overview.ts +75 -0
- package/packages/registry/blocks/preview-02/cards/claimable-balance.ts +54 -0
- package/packages/registry/blocks/preview-02/cards/contribution-history.ts +80 -0
- package/packages/registry/blocks/preview-02/cards/cover-art.ts +41 -0
- package/packages/registry/blocks/preview-02/cards/dividend-income.ts +76 -0
- package/packages/registry/blocks/preview-02/cards/empty-connect-bank.ts +38 -0
- package/packages/registry/blocks/preview-02/cards/empty-distribute-track.ts +38 -0
- package/packages/registry/blocks/preview-02/cards/empty-explore-catalog.ts +39 -0
- package/packages/registry/blocks/preview-02/cards/faq.ts +79 -0
- package/packages/registry/blocks/preview-02/cards/front-door.ts +40 -0
- package/packages/registry/blocks/preview-02/cards/index-investing.ts +31 -0
- package/packages/registry/blocks/preview-02/cards/kitchen-island.ts +90 -0
- package/packages/registry/blocks/preview-02/cards/loading-card.ts +34 -0
- package/packages/registry/blocks/preview-02/cards/new-milestone.ts +53 -0
- package/packages/registry/blocks/preview-02/cards/notification-settings.ts +126 -0
- package/packages/registry/blocks/preview-02/cards/payments.ts +88 -0
- package/packages/registry/blocks/preview-02/cards/payout-threshold.ts +92 -0
- package/packages/registry/blocks/preview-02/cards/power-usage.ts +84 -0
- package/packages/registry/blocks/preview-02/cards/preferences.ts +89 -0
- package/packages/registry/blocks/preview-02/cards/qr-connect.ts +54 -0
- package/packages/registry/blocks/preview-02/cards/receiving-method.ts +89 -0
- package/packages/registry/blocks/preview-02/cards/recent-transactions.ts +101 -0
- package/packages/registry/blocks/preview-02/cards/release-catalog.ts +73 -0
- package/packages/registry/blocks/preview-02/cards/roller-shades.ts +110 -0
- package/packages/registry/blocks/preview-02/cards/savings-progress.ts +68 -0
- package/packages/registry/blocks/preview-02/cards/savings-targets.ts +132 -0
- package/packages/registry/blocks/preview-02/cards/sidebar-nav.ts +88 -0
- package/packages/registry/blocks/preview-02/cards/social-links.ts +57 -0
- package/packages/registry/blocks/preview-02/cards/stock-performance.ts +106 -0
- package/packages/registry/blocks/preview-02/cards/syncing-state.ts +37 -0
- package/packages/registry/blocks/preview-02/cards/transfer-funds.ts +111 -0
- package/packages/registry/blocks/preview-02/cards/upcoming-payments.ts +69 -0
- package/packages/registry/blocks/preview-02/page.ts +111 -0
- package/packages/registry/blocks/preview-03/cards/files-chat.ts +75 -0
- package/packages/registry/blocks/preview-03/cards/group-chat.ts +114 -0
- package/packages/registry/blocks/preview-03/cards/reasoning-chat.ts +88 -0
- package/packages/registry/blocks/preview-03/cards/simple-chat.ts +53 -0
- package/packages/registry/blocks/preview-03/components/chat-card.ts +126 -0
- package/packages/registry/blocks/preview-03/components/chat-player.ts +174 -0
- package/packages/registry/blocks/preview-03/components/part-file.ts +77 -0
- package/packages/registry/blocks/preview-03/components/part-reasoning.ts +25 -0
- package/packages/registry/blocks/preview-03/components/part-source.ts +48 -0
- package/packages/registry/blocks/preview-03/components/part-text.ts +69 -0
- package/packages/registry/blocks/preview-03/components/part-tool.ts +71 -0
- package/packages/registry/blocks/preview-03/components/prompt-input.ts +86 -0
- package/packages/registry/blocks/preview-03/page.ts +39 -0
- package/packages/registry/blocks/sidebar-01/components/app-sidebar.ts +113 -0
- package/packages/registry/blocks/sidebar-01/components/search-form.ts +33 -0
- package/packages/registry/blocks/sidebar-01/components/version-switcher.ts +50 -0
- package/packages/registry/blocks/sidebar-01/page.ts +58 -0
- package/packages/registry/blocks/sidebar-02/components/app-sidebar.ts +131 -0
- package/packages/registry/blocks/sidebar-02/components/search-form.ts +33 -0
- package/packages/registry/blocks/sidebar-02/components/version-switcher.ts +50 -0
- package/packages/registry/blocks/sidebar-02/page.ts +53 -0
- package/packages/registry/blocks/sidebar-03/components/app-sidebar.ts +134 -0
- package/packages/registry/blocks/sidebar-03/page.ts +60 -0
- package/packages/registry/blocks/sidebar-04/components/app-sidebar.ts +136 -0
- package/packages/registry/blocks/sidebar-04/page.ts +58 -0
- package/packages/registry/blocks/sidebar-05/components/app-sidebar.ts +147 -0
- package/packages/registry/blocks/sidebar-05/components/search-form.ts +33 -0
- package/packages/registry/blocks/sidebar-05/page.ts +58 -0
- package/packages/registry/blocks/sidebar-06/components/app-sidebar.ts +105 -0
- package/packages/registry/blocks/sidebar-06/components/nav-main.ts +56 -0
- package/packages/registry/blocks/sidebar-06/components/sidebar-opt-in-form.ts +44 -0
- package/packages/registry/blocks/sidebar-06/page.ts +57 -0
- package/packages/registry/blocks/sidebar-07/components/app-sidebar.ts +108 -0
- package/packages/registry/blocks/sidebar-07/components/nav-main.ts +61 -0
- package/packages/registry/blocks/sidebar-07/components/nav-projects.ts +65 -0
- package/packages/registry/blocks/sidebar-07/components/nav-user.ts +70 -0
- package/packages/registry/blocks/sidebar-07/components/team-switcher.ts +66 -0
- package/packages/registry/blocks/sidebar-07/page.ts +59 -0
- package/packages/registry/blocks/sidebar-08/components/app-sidebar.ts +122 -0
- package/packages/registry/blocks/sidebar-08/components/nav-main.ts +68 -0
- package/packages/registry/blocks/sidebar-08/components/nav-projects.ts +65 -0
- package/packages/registry/blocks/sidebar-08/components/nav-secondary.ts +40 -0
- package/packages/registry/blocks/sidebar-08/components/nav-user.ts +70 -0
- package/packages/registry/blocks/sidebar-08/page.ts +59 -0
- package/packages/registry/blocks/sidebar-09/components/app-sidebar.ts +213 -0
- package/packages/registry/blocks/sidebar-09/components/nav-user.ts +70 -0
- package/packages/registry/blocks/sidebar-09/page.ts +52 -0
- package/packages/registry/blocks/sidebar-10/components/app-sidebar.ts +125 -0
- package/packages/registry/blocks/sidebar-10/components/nav-actions.ts +84 -0
- package/packages/registry/blocks/sidebar-10/components/nav-favorites.ts +66 -0
- package/packages/registry/blocks/sidebar-10/components/nav-main.ts +37 -0
- package/packages/registry/blocks/sidebar-10/components/nav-secondary.ts +43 -0
- package/packages/registry/blocks/sidebar-10/components/nav-workspaces.ts +78 -0
- package/packages/registry/blocks/sidebar-10/components/team-switcher.ts +63 -0
- package/packages/registry/blocks/sidebar-10/page.ts +46 -0
- package/packages/registry/blocks/sidebar-11/components/app-sidebar.ts +114 -0
- package/packages/registry/blocks/sidebar-11/page.ts +63 -0
- package/packages/registry/blocks/sidebar-12/components/app-sidebar.ts +63 -0
- package/packages/registry/blocks/sidebar-12/components/calendars.ts +53 -0
- package/packages/registry/blocks/sidebar-12/components/date-picker.ts +26 -0
- package/packages/registry/blocks/sidebar-12/components/nav-user.ts +70 -0
- package/packages/registry/blocks/sidebar-12/page.ts +43 -0
- package/packages/registry/blocks/sidebar-13/components/settings-dialog.ts +114 -0
- package/packages/registry/blocks/sidebar-13/page.ts +21 -0
- package/packages/registry/blocks/sidebar-14/components/app-sidebar.ts +120 -0
- package/packages/registry/blocks/sidebar-14/page.ts +55 -0
- package/packages/registry/blocks/sidebar-15/components/calendars.ts +53 -0
- package/packages/registry/blocks/sidebar-15/components/date-picker.ts +26 -0
- package/packages/registry/blocks/sidebar-15/components/nav-favorites.ts +66 -0
- package/packages/registry/blocks/sidebar-15/components/nav-main.ts +35 -0
- package/packages/registry/blocks/sidebar-15/components/nav-secondary.ts +43 -0
- package/packages/registry/blocks/sidebar-15/components/nav-user.ts +70 -0
- package/packages/registry/blocks/sidebar-15/components/nav-workspaces.ts +75 -0
- package/packages/registry/blocks/sidebar-15/components/sidebar-left.ts +126 -0
- package/packages/registry/blocks/sidebar-15/components/sidebar-right.ts +62 -0
- package/packages/registry/blocks/sidebar-15/components/team-switcher.ts +63 -0
- package/packages/registry/blocks/sidebar-15/page.ts +47 -0
- package/packages/registry/blocks/sidebar-16/components/app-sidebar.ts +124 -0
- package/packages/registry/blocks/sidebar-16/components/nav-main.ts +67 -0
- package/packages/registry/blocks/sidebar-16/components/nav-projects.ts +65 -0
- package/packages/registry/blocks/sidebar-16/components/nav-secondary.ts +40 -0
- package/packages/registry/blocks/sidebar-16/components/nav-user.ts +70 -0
- package/packages/registry/blocks/sidebar-16/components/search-form.ts +27 -0
- package/packages/registry/blocks/sidebar-16/components/site-header.ts +42 -0
- package/packages/registry/blocks/sidebar-16/page.ts +38 -0
- package/packages/registry/blocks/signup-01/components/signup-form.ts +78 -0
- package/packages/registry/blocks/signup-01/page.ts +19 -0
- package/packages/registry/blocks/signup-02/components/signup-form.ts +78 -0
- package/packages/registry/blocks/signup-02/page.ts +36 -0
- package/packages/registry/blocks/signup-03/components/signup-form.ts +79 -0
- package/packages/registry/blocks/signup-03/page.ts +28 -0
- package/packages/registry/blocks/signup-04/components/signup-form.ts +100 -0
- package/packages/registry/blocks/signup-04/page.ts +20 -0
- package/packages/registry/blocks/signup-05/components/signup-form.ts +75 -0
- package/packages/registry/blocks/signup-05/page.ts +20 -0
- package/packages/registry/components/accordion.ts +20 -7
- package/packages/registry/components/alert-dialog.ts +58 -13
- package/packages/registry/components/alert.ts +11 -6
- package/packages/registry/components/attachment.ts +119 -0
- package/packages/registry/components/avatar.ts +17 -8
- package/packages/registry/components/badge.ts +16 -9
- package/packages/registry/components/breadcrumb.ts +6 -6
- package/packages/registry/components/bubble.ts +95 -0
- package/packages/registry/components/button-group.ts +73 -0
- package/packages/registry/components/button.ts +35 -25
- package/packages/registry/components/calendar.ts +566 -0
- package/packages/registry/components/card.ts +8 -8
- package/packages/registry/components/carousel.ts +352 -0
- package/packages/registry/components/chart-element.ts +547 -0
- package/packages/registry/components/chart-geometry.ts +457 -0
- package/packages/registry/components/chart.ts +932 -0
- package/packages/registry/components/checkbox.ts +22 -84
- package/packages/registry/components/code-block.ts +191 -0
- package/packages/registry/components/collapsible.ts +5 -2
- package/packages/registry/components/combobox.ts +802 -0
- package/packages/registry/components/command.ts +618 -0
- package/packages/registry/components/context-menu.ts +302 -0
- package/packages/registry/components/dialog.ts +156 -45
- package/packages/registry/components/direction.ts +43 -0
- package/packages/registry/components/drawer.ts +226 -0
- package/packages/registry/components/dropdown-menu.ts +262 -83
- package/packages/registry/components/empty.ts +88 -0
- package/packages/registry/components/field.ts +165 -0
- package/packages/registry/components/floating.ts +124 -0
- package/packages/registry/components/hover-card.ts +19 -8
- package/packages/registry/components/input-group.ts +118 -0
- package/packages/registry/components/input-otp.ts +365 -0
- package/packages/registry/components/input.ts +3 -3
- package/packages/registry/components/item.ts +134 -0
- package/packages/registry/components/kbd.ts +2 -2
- package/packages/registry/components/label.ts +1 -1
- package/packages/registry/components/marker.ts +63 -0
- package/packages/registry/components/menubar.ts +388 -0
- package/packages/registry/components/message-scroller.ts +470 -0
- package/packages/registry/components/message.ts +69 -0
- package/packages/registry/components/native-select.ts +5 -5
- package/packages/registry/components/navigation-menu.ts +564 -0
- package/packages/registry/components/pagination.ts +5 -5
- package/packages/registry/components/popover.ts +30 -84
- package/packages/registry/components/progress.ts +7 -3
- package/packages/registry/components/questionnaire.ts +840 -0
- package/packages/registry/components/radio-group.ts +18 -73
- package/packages/registry/components/resizable.ts +330 -0
- package/packages/registry/components/scroll-area.ts +46 -0
- package/packages/registry/components/select.ts +650 -0
- package/packages/registry/components/separator.ts +1 -1
- package/packages/registry/components/sheet.ts +128 -0
- package/packages/registry/components/sidebar.ts +457 -0
- package/packages/registry/components/skeleton.ts +1 -1
- package/packages/registry/components/slider.ts +333 -0
- package/packages/registry/components/sonner.ts +74 -7
- package/packages/registry/components/spinner.ts +66 -0
- package/packages/registry/components/switch.ts +117 -9
- package/packages/registry/components/table.ts +11 -9
- package/packages/registry/components/tabs.ts +76 -25
- package/packages/registry/components/textarea.ts +1 -1
- package/packages/registry/components/toggle-group.ts +30 -11
- package/packages/registry/components/toggle.ts +20 -13
- package/packages/registry/components/tooltip.ts +68 -7
- package/packages/registry/components/trace.ts +75 -0
- package/packages/registry/lib/icons-hugeicons.ts +955 -0
- package/packages/registry/lib/icons-phosphor.ts +955 -0
- package/packages/registry/lib/icons-remixicon.ts +955 -0
- package/packages/registry/lib/icons-tabler.ts +955 -0
- package/packages/registry/lib/icons.ts +768 -0
- package/packages/registry/lib/utils.ts +49 -4
- package/packages/registry/registry.json +4092 -60
- package/packages/registry/styles/shadcn-tailwind.css +654 -0
- package/packages/registry/styles/style-luma.css +1987 -0
- package/packages/registry/styles/style-lyra.css +1965 -0
- package/packages/registry/styles/style-maia.css +1987 -0
- package/packages/registry/styles/style-mira.css +1992 -0
- package/packages/registry/styles/style-new-york.css +1597 -0
- package/packages/registry/styles/style-nova.css +1990 -0
- package/packages/registry/styles/style-rhea.css +1987 -0
- package/packages/registry/styles/style-sera.css +1977 -0
- package/packages/registry/styles/style-vega.css +1990 -0
- package/packages/registry/styles/supplement.json +54 -0
- package/packages/registry/styles/tw-animate.css +6 -0
- package/packages/registry/themes/base-colors.js +8 -2
- package/packages/registry/themes/fonts.js +120 -0
- package/packages/registry/themes/index.css +2 -0
- package/packages/registry/themes/items.js +61 -0
- package/packages/registry/themes/preset.js +302 -0
- package/packages/registry/themes/themes.js +1193 -0
- package/src/commands/add.js +236 -12
- package/src/commands/diff.js +34 -20
- package/src/commands/info.js +7 -0
- package/src/commands/init.js +125 -8
- package/src/commands/list.js +43 -11
- package/src/commands/view.js +33 -5
- package/src/registry/extract.js +192 -2
- package/src/registry/icons.js +31 -0
- package/src/registry/local.js +15 -1
- package/src/registry/schema.js +13 -0
- package/src/registry/style-bake.js +506 -0
- package/src/utils/theme.js +110 -21
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The style bake (#1666, D8): turn a registry component written in BASE FORM
|
|
3
|
+
* into plain Tailwind for one chosen style.
|
|
4
|
+
*
|
|
5
|
+
* A base-form component carries shadcn's semantic class names alongside the
|
|
6
|
+
* layout classes it keeps inline, the way shadcn's own
|
|
7
|
+
* `registry/bases/radix/ui/*.tsx` does:
|
|
8
|
+
*
|
|
9
|
+
* const BASE = 'cn-button group/button inline-flex shrink-0 items-center ...';
|
|
10
|
+
*
|
|
11
|
+
* A STYLE is a file of nested rules that map each of those names to Tailwind
|
|
12
|
+
* through `@apply`, all under one `.style-<name>` wrapper:
|
|
13
|
+
*
|
|
14
|
+
* .style-nova { .cn-button { @apply rounded-lg border text-sm ...; } }
|
|
15
|
+
*
|
|
16
|
+
* The bake replaces each `cn-*` name with the classes its style maps it to,
|
|
17
|
+
* ahead of the classes already in the string, and drops the name. It runs
|
|
18
|
+
* wherever an app or an agent READS code: `add` and `diff` (through
|
|
19
|
+
* `transformForProject`), `webjs ui view`, and the MCP `ui` tool. So an
|
|
20
|
+
* installed file, and anything an agent copies, is plain readable Tailwind
|
|
21
|
+
* with no `cn-*` name in it, and `cn()` merging keeps working on it.
|
|
22
|
+
*
|
|
23
|
+
* This is a port of shadcn's `createStyleMap` and `transformStyle`
|
|
24
|
+
* (`packages/shadcn/src/styles/` in the shadcn repo), written without postcss
|
|
25
|
+
* or ts-morph so the CLI gains no dependency. Two deliberate differences:
|
|
26
|
+
*
|
|
27
|
+
* - shadcn inlines a name only at its FIRST occurrence in a file. Here every
|
|
28
|
+
* occurrence is inlined, because a Tier-2 template can render the same part
|
|
29
|
+
* in two branches and both must be styled.
|
|
30
|
+
* - shadcn merges with tailwind-merge. Here the mapped classes are placed
|
|
31
|
+
* BEFORE the string's own classes with exact duplicates dropped, and the
|
|
32
|
+
* component's runtime `cn()` (or the class attribute as written) resolves
|
|
33
|
+
* the rest. Placing them first is what lets an inline layout class win a
|
|
34
|
+
* conflict, the same precedence `twMerge(mapped, original)` gives.
|
|
35
|
+
*
|
|
36
|
+
* A `cn-*` name must sit in static text: a string literal, or the static part
|
|
37
|
+
* of a `class="..."` attribute in an `html` template. A name produced inside a
|
|
38
|
+
* `${...}` hole is never seen and never baked.
|
|
39
|
+
*/
|
|
40
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
41
|
+
import { join } from 'node:path';
|
|
42
|
+
import { REGISTRY_ROOT } from './local.js';
|
|
43
|
+
|
|
44
|
+
const CN_PREFIX = 'cn-';
|
|
45
|
+
const CN_TOKEN = /^cn-[\w-]+$/;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Names that survive the bake, because they are hooks rather than styles: a
|
|
49
|
+
* selector elsewhere targets them, or the CLI rewrites them at install time.
|
|
50
|
+
* shadcn's own list (`ALLOWLIST` in transform-style-map.ts).
|
|
51
|
+
*/
|
|
52
|
+
export const CN_ALLOWLIST = new Set([
|
|
53
|
+
'cn-menu-target',
|
|
54
|
+
'cn-menu-translucent',
|
|
55
|
+
'cn-logical-sides',
|
|
56
|
+
'cn-rtl-flip',
|
|
57
|
+
'cn-font-heading',
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
/** The styles the kit ships, in the order a picker lists them. `new-york` is the default. */
|
|
61
|
+
export const STYLE_NAMES = ['new-york', 'vega', 'nova', 'maia', 'lyra', 'mira', 'luma', 'sera', 'rhea'];
|
|
62
|
+
export const DEFAULT_STYLE = 'new-york';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Normalise a `components.json` `style` value to a style the kit ships.
|
|
66
|
+
* `'default'` (the schema's default) and an absent value mean `new-york`.
|
|
67
|
+
* @param {string | undefined | null} style
|
|
68
|
+
*/
|
|
69
|
+
export function resolveStyleName(style) {
|
|
70
|
+
if (!style || style === 'default') return DEFAULT_STYLE;
|
|
71
|
+
return STYLE_NAMES.includes(style) ? style : DEFAULT_STYLE;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Style map: CSS with @apply rules -> { 'cn-x': 'tailwind classes' }
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Build the style map from a style file's CSS. Mirrors shadcn's
|
|
80
|
+
* `createStyleMap`: every rule whose body holds `@apply` contributes its
|
|
81
|
+
* classes to the LAST `cn-*` class of each selector in its selector list,
|
|
82
|
+
* with `&` removed; a later rule for the same name is PREPENDED to what the
|
|
83
|
+
* earlier ones gave it. A rule with no `@apply` contributes nothing.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} css
|
|
86
|
+
* @returns {Record<string, string>}
|
|
87
|
+
*/
|
|
88
|
+
export function createStyleMap(css) {
|
|
89
|
+
/** @type {Record<string, string>} */
|
|
90
|
+
const result = {};
|
|
91
|
+
const src = stripCssComments(css);
|
|
92
|
+
/** @type {string[][]} */
|
|
93
|
+
const stack = []; // selector lists of the enclosing rules (unused for the subject, kept for clarity)
|
|
94
|
+
let i = 0;
|
|
95
|
+
let buf = '';
|
|
96
|
+
/** @type {{ selectors: string[], applies: string[] }[]} */
|
|
97
|
+
const frames = [];
|
|
98
|
+
while (i < src.length) {
|
|
99
|
+
const ch = src[i];
|
|
100
|
+
if (ch === '{') {
|
|
101
|
+
const prelude = buf.trim();
|
|
102
|
+
buf = '';
|
|
103
|
+
const selectors = prelude.startsWith('@') ? [] : splitSelectors(prelude);
|
|
104
|
+
frames.push({ selectors, applies: [] });
|
|
105
|
+
stack.push(selectors);
|
|
106
|
+
i++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (ch === '}') {
|
|
110
|
+
const frame = frames.pop();
|
|
111
|
+
stack.pop();
|
|
112
|
+
buf = '';
|
|
113
|
+
if (frame && frame.applies.length) {
|
|
114
|
+
const classes = frame.applies.join(' ');
|
|
115
|
+
for (const sel of frame.selectors) {
|
|
116
|
+
const subject = subjectClass(sel.replace(/\s*&\s*/g, '').trim());
|
|
117
|
+
if (!subject) continue;
|
|
118
|
+
result[subject] = result[subject] ? `${classes} ${result[subject]}` : classes;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
i++;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (ch === ';') {
|
|
125
|
+
const decl = buf.trim();
|
|
126
|
+
buf = '';
|
|
127
|
+
const m = /^@apply\s+([\s\S]+)$/.exec(decl);
|
|
128
|
+
if (m && frames.length) {
|
|
129
|
+
const value = m[1].trim().replace(/\s+/g, ' ');
|
|
130
|
+
if (value) frames[frames.length - 1].applies.push(value);
|
|
131
|
+
}
|
|
132
|
+
i++;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
buf += ch;
|
|
136
|
+
i++;
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** @param {string} css */
|
|
142
|
+
function stripCssComments(css) {
|
|
143
|
+
return css.replace(/\/\*[\s\S]*?\*\//g, ' ');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Split a selector list on top-level commas (not inside () or []). */
|
|
147
|
+
function splitSelectors(prelude) {
|
|
148
|
+
const out = [];
|
|
149
|
+
let depth = 0;
|
|
150
|
+
let cur = '';
|
|
151
|
+
for (const ch of prelude) {
|
|
152
|
+
if (ch === '(' || ch === '[') depth++;
|
|
153
|
+
else if (ch === ')' || ch === ']') depth--;
|
|
154
|
+
if (ch === ',' && depth === 0) { out.push(cur.trim()); cur = ''; continue; }
|
|
155
|
+
cur += ch;
|
|
156
|
+
}
|
|
157
|
+
if (cur.trim()) out.push(cur.trim());
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The last `.cn-*` class in a selector, ignoring anything inside [] or (). */
|
|
162
|
+
function subjectClass(selector) {
|
|
163
|
+
let depth = 0;
|
|
164
|
+
let last = null;
|
|
165
|
+
for (let i = 0; i < selector.length; i++) {
|
|
166
|
+
const ch = selector[i];
|
|
167
|
+
if (ch === '(' || ch === '[') { depth++; continue; }
|
|
168
|
+
if (ch === ')' || ch === ']') { depth--; continue; }
|
|
169
|
+
if (depth === 0 && ch === '.' && selector.startsWith(CN_PREFIX, i + 1)) {
|
|
170
|
+
const m = /^[\w-]+/.exec(selector.slice(i + 1));
|
|
171
|
+
if (m) last = m[0];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return last;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** @type {Map<string, Record<string, string>>} */
|
|
178
|
+
const MAP_CACHE = new Map();
|
|
179
|
+
|
|
180
|
+
/** Absolute path of a shipped style file. */
|
|
181
|
+
export function styleFilePath(name) {
|
|
182
|
+
return join(REGISTRY_ROOT, 'styles', `style-${name}.css`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The style map for one shipped style, read from the packaged registry and
|
|
187
|
+
* cached. An unknown or missing style yields an empty map, which bakes every
|
|
188
|
+
* `cn-*` name away to nothing rather than leaking it.
|
|
189
|
+
* @param {string | undefined | null} style
|
|
190
|
+
*/
|
|
191
|
+
export function loadStyleMap(style) {
|
|
192
|
+
const name = resolveStyleName(style);
|
|
193
|
+
const cached = MAP_CACHE.get(name);
|
|
194
|
+
if (cached) return cached;
|
|
195
|
+
const file = styleFilePath(name);
|
|
196
|
+
const map = existsSync(file) ? createStyleMap(readFileSync(file, 'utf8')) : {};
|
|
197
|
+
MAP_CACHE.set(name, map);
|
|
198
|
+
return map;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Class segments: where in a JS/TS source a class list can sit
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @typedef {{ start: number, end: number, quote: "'" | '"' | '`' | 'attr' }} Segment
|
|
207
|
+
* `start` / `end` delimit the RAW source text of the segment (between the
|
|
208
|
+
* quotes, or the static run of a class attribute). `quote` is the delimiter
|
|
209
|
+
* the text must stay legal inside.
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
const REGEX_PRECEDERS = new Set(['(', ',', '=', ':', '[', '!', '&', '|', '?', '{', '}', ';', '+', '-', '*', '%', '<', '>', '~', '^']);
|
|
213
|
+
const REGEX_KEYWORDS = new Set(['return', 'typeof', 'case', 'do', 'else', 'in', 'of', 'new', 'delete', 'void', 'throw', 'yield', 'await']);
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Find every string literal in a JS/TS source, plus every static run of a
|
|
217
|
+
* `class="..."` attribute inside an `html` tagged template. This one function
|
|
218
|
+
* is what the bake rewrites, what the fixture records and what the snapshot
|
|
219
|
+
* test compares, so the three cannot disagree about which strings carry
|
|
220
|
+
* classes. Comments and regex literals are skipped. A template literal with
|
|
221
|
+
* no holes is a plain string; a template with holes contributes nothing of
|
|
222
|
+
* its own unless it is an `html` template, where only class attribute text
|
|
223
|
+
* counts.
|
|
224
|
+
*
|
|
225
|
+
* @param {string} src
|
|
226
|
+
* @returns {Segment[]}
|
|
227
|
+
*/
|
|
228
|
+
export function classSegments(src) {
|
|
229
|
+
/** @type {Segment[]} */
|
|
230
|
+
const segs = [];
|
|
231
|
+
let i = 0;
|
|
232
|
+
let prevSig = ''; // last significant code char, for regex detection
|
|
233
|
+
let prevWord = '';
|
|
234
|
+
/** @type {{ html: boolean, inClass: boolean, attrQuote: string, runStart: number }[]} */
|
|
235
|
+
const tpl = []; // open template literals (innermost last)
|
|
236
|
+
/** @type {number[]} */
|
|
237
|
+
const holeDepth = []; // brace depth inside each open ${ } hole
|
|
238
|
+
|
|
239
|
+
const n = src.length;
|
|
240
|
+
const inCode = () => tpl.length === 0 || holeDepth.length === tpl.length;
|
|
241
|
+
|
|
242
|
+
while (i < n) {
|
|
243
|
+
if (!inCode()) {
|
|
244
|
+
// Inside the static text of the innermost template.
|
|
245
|
+
const t = tpl[tpl.length - 1];
|
|
246
|
+
const ch = src[i];
|
|
247
|
+
if (ch === '\\') { i += 2; continue; }
|
|
248
|
+
if (ch === '`') {
|
|
249
|
+
if (t.html && t.inClass && i > t.runStart) segs.push({ start: t.runStart, end: i, quote: 'attr' });
|
|
250
|
+
tpl.pop();
|
|
251
|
+
i++;
|
|
252
|
+
prevSig = ')';
|
|
253
|
+
prevWord = '';
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (ch === '$' && src[i + 1] === '{') {
|
|
257
|
+
if (t.html && t.inClass && i > t.runStart) segs.push({ start: t.runStart, end: i, quote: 'attr' });
|
|
258
|
+
holeDepth.push(0);
|
|
259
|
+
i += 2;
|
|
260
|
+
prevSig = '{';
|
|
261
|
+
prevWord = '';
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (t.html) {
|
|
265
|
+
if (t.inClass) {
|
|
266
|
+
if (ch === t.attrQuote) {
|
|
267
|
+
if (i > t.runStart) segs.push({ start: t.runStart, end: i, quote: 'attr' });
|
|
268
|
+
t.inClass = false;
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
const m = /^class=(["'])/.exec(src.slice(i, i + 7));
|
|
272
|
+
if (m && /[\s<]/.test(src[i - 1] || ' ')) {
|
|
273
|
+
t.inClass = true;
|
|
274
|
+
t.attrQuote = m[1];
|
|
275
|
+
i += 7;
|
|
276
|
+
t.runStart = i;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
i++;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Code.
|
|
286
|
+
const ch = src[i];
|
|
287
|
+
if (ch === '/' && src[i + 1] === '/') { const e = src.indexOf('\n', i); i = e < 0 ? n : e; continue; }
|
|
288
|
+
if (ch === '/' && src[i + 1] === '*') { const e = src.indexOf('*/', i + 2); i = e < 0 ? n : e + 2; continue; }
|
|
289
|
+
if (ch === "'" || ch === '"') {
|
|
290
|
+
const start = i + 1;
|
|
291
|
+
let j = start;
|
|
292
|
+
while (j < n && src[j] !== ch) { if (src[j] === '\\') j++; if (src[j] === '\n') break; j++; }
|
|
293
|
+
segs.push({ start, end: j, quote: ch });
|
|
294
|
+
i = j + 1;
|
|
295
|
+
prevSig = ')';
|
|
296
|
+
prevWord = '';
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (ch === '`') {
|
|
300
|
+
// A template with no holes is a plain string. Decide by scanning ahead.
|
|
301
|
+
const isHtml = /\bhtml\s*$/.test(src.slice(Math.max(0, i - 12), i));
|
|
302
|
+
let j = i + 1;
|
|
303
|
+
let hasHole = false;
|
|
304
|
+
while (j < n && src[j] !== '`') {
|
|
305
|
+
if (src[j] === '\\') { j += 2; continue; }
|
|
306
|
+
if (src[j] === '$' && src[j + 1] === '{') { hasHole = true; break; }
|
|
307
|
+
j++;
|
|
308
|
+
}
|
|
309
|
+
if (!hasHole && !isHtml) {
|
|
310
|
+
segs.push({ start: i + 1, end: j, quote: '`' });
|
|
311
|
+
i = j + 1;
|
|
312
|
+
prevSig = ')';
|
|
313
|
+
prevWord = '';
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
tpl.push({ html: isHtml, inClass: false, attrQuote: '"', runStart: 0 });
|
|
317
|
+
i++;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (ch === '/' ) {
|
|
321
|
+
const regexAllowed = !prevSig || REGEX_PRECEDERS.has(prevSig) || REGEX_KEYWORDS.has(prevWord);
|
|
322
|
+
if (regexAllowed) {
|
|
323
|
+
let j = i + 1;
|
|
324
|
+
let inClassSet = false;
|
|
325
|
+
while (j < n) {
|
|
326
|
+
const c = src[j];
|
|
327
|
+
if (c === '\\') { j += 2; continue; }
|
|
328
|
+
if (c === '\n') break;
|
|
329
|
+
if (inClassSet) { if (c === ']') inClassSet = false; }
|
|
330
|
+
else if (c === '[') inClassSet = true;
|
|
331
|
+
else if (c === '/') break;
|
|
332
|
+
j++;
|
|
333
|
+
}
|
|
334
|
+
j++;
|
|
335
|
+
while (j < n && /[a-z]/i.test(src[j])) j++;
|
|
336
|
+
i = j;
|
|
337
|
+
prevSig = ')';
|
|
338
|
+
prevWord = '';
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (holeDepth.length && holeDepth.length === tpl.length) {
|
|
343
|
+
if (ch === '{') holeDepth[holeDepth.length - 1]++;
|
|
344
|
+
else if (ch === '}') {
|
|
345
|
+
if (holeDepth[holeDepth.length - 1] === 0) {
|
|
346
|
+
holeDepth.pop();
|
|
347
|
+
const t = tpl[tpl.length - 1];
|
|
348
|
+
i++;
|
|
349
|
+
t.runStart = i; // a class attribute continues after the hole
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
holeDepth[holeDepth.length - 1]--;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (/[A-Za-z_$]/.test(ch)) {
|
|
356
|
+
let j = i;
|
|
357
|
+
while (j < n && /[\w$]/.test(src[j])) j++;
|
|
358
|
+
prevWord = src.slice(i, j);
|
|
359
|
+
prevSig = 'a';
|
|
360
|
+
i = j;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (!/\s/.test(ch)) { prevSig = ch; prevWord = ''; }
|
|
364
|
+
i++;
|
|
365
|
+
}
|
|
366
|
+
return segs;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* The cooked value of a segment: what the string MEANS at runtime, with its
|
|
371
|
+
* escapes resolved. Class attribute text is taken as written.
|
|
372
|
+
* @param {string} src
|
|
373
|
+
* @param {Segment} seg
|
|
374
|
+
*/
|
|
375
|
+
export function segmentValue(src, seg) {
|
|
376
|
+
const raw = src.slice(seg.start, seg.end);
|
|
377
|
+
if (seg.quote === 'attr') return raw;
|
|
378
|
+
return raw.replace(/\\(u\{[0-9a-fA-F]+\}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2}|[\s\S])/g, (_, e) => {
|
|
379
|
+
if (e[0] === 'u' && e[1] === '{') return String.fromCodePoint(parseInt(e.slice(2, -1), 16));
|
|
380
|
+
if (e[0] === 'u' && e.length === 5) return String.fromCharCode(parseInt(e.slice(1), 16));
|
|
381
|
+
if (e[0] === 'x' && e.length === 3) return String.fromCharCode(parseInt(e.slice(1), 16));
|
|
382
|
+
return { n: '\n', t: '\t', r: '\r', b: '\b', f: '\f', v: '\v', 0: '\0' }[e] ?? e;
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Write a cooked value back inside the delimiter it sits in.
|
|
388
|
+
*
|
|
389
|
+
* The result must read right TWICE: as JavaScript (or an HTML attribute), and
|
|
390
|
+
* as the raw text Tailwind's scanner reads, which never unescapes anything. So
|
|
391
|
+
* a quote is never escaped with a backslash or an entity if it can be helped:
|
|
392
|
+
* `[class*=\'size-\']` is valid JavaScript, but Tailwind compiles the
|
|
393
|
+
* backslashes into the selector and the rule never matches. bakeSource()
|
|
394
|
+
* avoids it for a JS string by changing the string's delimiter (see there).
|
|
395
|
+
* In an HTML attribute a double quote becomes a single one, which means the
|
|
396
|
+
* same in a CSS string or attribute value. Escaping is the last resort, for a
|
|
397
|
+
* value carrying both quotes, which no style emits.
|
|
398
|
+
*/
|
|
399
|
+
function escapeFor(value, quote) {
|
|
400
|
+
if (quote === 'attr') {
|
|
401
|
+
if (!value.includes('"')) return value;
|
|
402
|
+
return value.includes("'") ? value.replace(/"/g, '"') : value.replace(/"/g, "'");
|
|
403
|
+
}
|
|
404
|
+
let out = value.replace(/\\/g, '\\\\');
|
|
405
|
+
if (quote === '`') return out.replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
406
|
+
return out.replace(new RegExp(quote, 'g'), `\\${quote}`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ---------------------------------------------------------------------------
|
|
410
|
+
// The bake
|
|
411
|
+
// ---------------------------------------------------------------------------
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Bake one class list. Every `cn-*` name is replaced by the classes the map
|
|
415
|
+
* gives it (none, when the map has no entry), the mapped classes go FIRST,
|
|
416
|
+
* the allowlisted hooks stay, and exact duplicates are dropped.
|
|
417
|
+
*
|
|
418
|
+
* @param {string} value a cooked class list
|
|
419
|
+
* @param {Record<string, string>} styleMap
|
|
420
|
+
*/
|
|
421
|
+
export function bakeClassList(value, styleMap) {
|
|
422
|
+
const tokens = value.split(/\s+/).filter(Boolean);
|
|
423
|
+
if (!tokens.some((t) => CN_TOKEN.test(t))) return value;
|
|
424
|
+
const mapped = [];
|
|
425
|
+
const rest = [];
|
|
426
|
+
for (const t of tokens) {
|
|
427
|
+
if (CN_TOKEN.test(t)) {
|
|
428
|
+
if (CN_ALLOWLIST.has(t)) rest.push(t);
|
|
429
|
+
else if (styleMap[t]) mapped.push(...styleMap[t].split(/\s+/).filter(Boolean));
|
|
430
|
+
} else {
|
|
431
|
+
rest.push(t);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const seen = new Set();
|
|
435
|
+
const out = [];
|
|
436
|
+
for (const t of [...mapped, ...rest]) {
|
|
437
|
+
if (seen.has(t)) continue;
|
|
438
|
+
seen.add(t);
|
|
439
|
+
out.push(t);
|
|
440
|
+
}
|
|
441
|
+
// Keep the original's leading / trailing whitespace, so a class list that
|
|
442
|
+
// was concatenated into a longer string still has its separators.
|
|
443
|
+
const lead = /^\s*/.exec(value)[0];
|
|
444
|
+
const trail = /\s*$/.exec(value)[0];
|
|
445
|
+
return out.length ? lead + out.join(' ') + trail : (lead || trail ? ' ' : '');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Bake a whole source file for one style map. A file with no `cn-*` name
|
|
450
|
+
* comes back unchanged, byte for byte.
|
|
451
|
+
*
|
|
452
|
+
* @param {string} src
|
|
453
|
+
* @param {Record<string, string>} styleMap
|
|
454
|
+
*/
|
|
455
|
+
export function bakeSource(src, styleMap) {
|
|
456
|
+
if (!src.includes(CN_PREFIX)) return src;
|
|
457
|
+
const segs = classSegments(src);
|
|
458
|
+
let out = '';
|
|
459
|
+
let last = 0;
|
|
460
|
+
for (const seg of segs) {
|
|
461
|
+
const value = segmentValue(src, seg);
|
|
462
|
+
if (!/(^|\s)cn-[\w-]/.test(value)) continue;
|
|
463
|
+
const baked = bakeClassList(value, styleMap);
|
|
464
|
+
// A class that carries the string's own quote (the styles' `[class*='size-']`
|
|
465
|
+
// inside a single-quoted string) moves the string to the other quote, the
|
|
466
|
+
// way a person writes it, rather than escaping: Tailwind reads the raw
|
|
467
|
+
// text and would keep the backslashes.
|
|
468
|
+
const q = seg.quote;
|
|
469
|
+
const other = q === "'" ? '"' : q === '"' ? "'" : '';
|
|
470
|
+
if (other && baked.includes(q) && !baked.includes(other)) {
|
|
471
|
+
out += src.slice(last, seg.start - 1) + other + escapeFor(baked, other) + other;
|
|
472
|
+
last = seg.end + 1;
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
out += src.slice(last, seg.start) + escapeFor(baked, q);
|
|
476
|
+
last = seg.end;
|
|
477
|
+
}
|
|
478
|
+
return out + src.slice(last);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Bake for a project: the style named in its `components.json`, `new-york`
|
|
483
|
+
* when there is none. This is the entry `transformForProject` and the kit
|
|
484
|
+
* projector call.
|
|
485
|
+
*
|
|
486
|
+
* @param {string} src
|
|
487
|
+
* @param {string | undefined | null} style
|
|
488
|
+
*/
|
|
489
|
+
export function bakeForStyle(src, style) {
|
|
490
|
+
return bakeSource(src, loadStyleMap(style));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Every `cn-*` name left in a baked source outside the allowlist. A test uses
|
|
495
|
+
* it to prove nothing an app or agent reads ever carries one.
|
|
496
|
+
* @param {string} src
|
|
497
|
+
*/
|
|
498
|
+
export function leftoverCnNames(src) {
|
|
499
|
+
const names = new Set();
|
|
500
|
+
for (const seg of classSegments(src)) {
|
|
501
|
+
for (const t of segmentValue(src, seg).split(/\s+/)) {
|
|
502
|
+
if (CN_TOKEN.test(t) && !CN_ALLOWLIST.has(t)) names.add(t);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return [...names];
|
|
506
|
+
}
|
package/src/utils/theme.js
CHANGED
|
@@ -5,64 +5,153 @@
|
|
|
5
5
|
* `--foreground`, `--destructive`, ...) that the app must define. `init` plants
|
|
6
6
|
* them; `add` self-heals if they are missing (an app that copied a component
|
|
7
7
|
* without the tokens paints unstyled). Both go through {@link ensureTheme} so
|
|
8
|
-
* the contract is one place: a token block is written
|
|
9
|
-
*
|
|
8
|
+
* the contract is one place: a token block is written once, keyed by the
|
|
9
|
+
* {@link THEME_MARKER}, and a genuine failure to write is reported (never
|
|
10
10
|
* swallowed) so `init` can exit non-zero instead of leaving a clean exit code
|
|
11
11
|
* on an unstyled install.
|
|
12
12
|
*
|
|
13
|
+
* The block ends at {@link THEME_END_MARKER} (#1666), which is what lets
|
|
14
|
+
* `init --preset` (or `--overwrite`) REPLACE it in place later: everything
|
|
15
|
+
* from the start marker through the end marker is the kit's, and everything
|
|
16
|
+
* around it is the app's. A block written before the end marker existed has
|
|
17
|
+
* no such boundary, so it is never replaced, only reported, since guessing
|
|
18
|
+
* where it stops would cut into the app's own CSS.
|
|
19
|
+
*
|
|
13
20
|
* @module utils/theme
|
|
14
21
|
*/
|
|
15
22
|
|
|
16
23
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
17
24
|
import { dirname, join } from 'node:path';
|
|
18
25
|
import { getRegistryItem } from '../registry/fetcher.js';
|
|
26
|
+
import { buildThemeVars } from '../../packages/registry/themes/themes.js';
|
|
27
|
+
import { presetFontVars } from '../../packages/registry/themes/preset.js';
|
|
19
28
|
|
|
20
|
-
/**
|
|
29
|
+
/** The line that opens the theme block. */
|
|
21
30
|
export const THEME_MARKER = '/* @webjsdev/ui theme */';
|
|
22
31
|
|
|
32
|
+
/** The line that closes it (#1666). */
|
|
33
|
+
export const THEME_END_MARKER = '/* @webjsdev/ui theme end */';
|
|
34
|
+
|
|
23
35
|
function ensureDir(d) {
|
|
24
36
|
if (!existsSync(d)) mkdirSync(d, { recursive: true });
|
|
25
37
|
}
|
|
26
38
|
|
|
39
|
+
function escapeRe(s) {
|
|
40
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Set CSS variables inside the first `<selector> {` block of `css`: a variable
|
|
45
|
+
* the block declares has its value replaced where it stands, and one it does
|
|
46
|
+
* not is appended at the end of the block. Comments and every other line are
|
|
47
|
+
* left as they are.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} css
|
|
50
|
+
* @param {string} selector e.g. `:root`, `.dark`, `@theme inline`
|
|
51
|
+
* @param {Record<string, string>} vars bare names (`primary`, not `--primary`)
|
|
52
|
+
*/
|
|
53
|
+
export function setBlockVars(css, selector, vars) {
|
|
54
|
+
const entries = Object.entries(vars || {});
|
|
55
|
+
if (!entries.length) return css;
|
|
56
|
+
const re = new RegExp(`(${escapeRe(selector)}\\s*\\{)([\\s\\S]*?)(\\n\\})`);
|
|
57
|
+
if (!re.test(css)) return css;
|
|
58
|
+
return css.replace(re, (_m, open, body, close) => {
|
|
59
|
+
let next = body;
|
|
60
|
+
const missing = [];
|
|
61
|
+
for (const [key, value] of entries) {
|
|
62
|
+
const varRe = new RegExp(`(\\n\\s*--${escapeRe(key)}\\s*:\\s*)[^;\\n]+`);
|
|
63
|
+
if (varRe.test(next)) next = next.replace(varRe, (_v, lead) => lead + value);
|
|
64
|
+
else missing.push(` --${key}: ${value};`);
|
|
65
|
+
}
|
|
66
|
+
return open + next + (missing.length ? '\n' + missing.join('\n') : '') + close;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
27
70
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
71
|
+
* The theme block for a design-system preset: the kit's theme file (the
|
|
72
|
+
* neutral `themes/index.css`, so the `@theme inline` map, the variants and the
|
|
73
|
+
* base rules stay the kit's) with every token set to the preset's values
|
|
74
|
+
* (shadcn's palettes, see `themes/themes.js`) and the font tokens added to
|
|
75
|
+
* `@theme inline`.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} baseCss the `theme-neutral` file content
|
|
78
|
+
* @param {import('../../packages/registry/themes/preset.js').PresetConfig} config a resolved preset
|
|
79
|
+
*/
|
|
80
|
+
export function presetThemeBlock(baseCss, config) {
|
|
81
|
+
const { light, dark } = buildThemeVars(config);
|
|
82
|
+
let css = setBlockVars(baseCss, ':root', light);
|
|
83
|
+
css = setBlockVars(css, '.dark', dark);
|
|
84
|
+
css = setBlockVars(css, '@theme inline', presetFontVars(config));
|
|
85
|
+
return withEndMarker(css);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A block that does not already close with the end marker gets one. */
|
|
89
|
+
function withEndMarker(block) {
|
|
90
|
+
if (block.includes(THEME_END_MARKER)) return block;
|
|
91
|
+
return block.replace(/\s*$/, '\n') + '\n' + THEME_END_MARKER + '\n';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Ensure the theme tokens exist in the project's Tailwind CSS file.
|
|
96
|
+
*
|
|
97
|
+
* With no block present it appends one. With a block present it is a no-op,
|
|
98
|
+
* unless `opts.replace` is set, in which case a block carrying the end marker
|
|
99
|
+
* is replaced in place and a block without one is left alone and reported as
|
|
100
|
+
* `'legacy'`. The block written is `opts.block` when given (a preset's), else
|
|
101
|
+
* the `theme-<baseColor>` registry item's file. Returns a status so the caller
|
|
30
102
|
* decides how to react (`init` fails hard on `'failed'`; `add` warns).
|
|
31
103
|
*
|
|
32
104
|
* @param {string} cwd
|
|
33
105
|
* @param {string} baseColor one of the base-colour names (`neutral`, ...)
|
|
34
106
|
* @param {string} cssPath project-relative path to the Tailwind CSS file
|
|
35
107
|
* @param {string} [registryUrl]
|
|
36
|
-
* @
|
|
108
|
+
* @param {{ replace?: boolean, block?: string }} [opts]
|
|
109
|
+
* @returns {Promise<{ status: 'written'|'replaced'|'present'|'legacy'|'failed', cssPath: string, error?: string }>}
|
|
37
110
|
*/
|
|
38
|
-
export async function ensureTheme(cwd, baseColor, cssPath, registryUrl) {
|
|
111
|
+
export async function ensureTheme(cwd, baseColor, cssPath, registryUrl, opts = {}) {
|
|
39
112
|
if (!cssPath || !baseColor) {
|
|
40
113
|
return { status: 'failed', cssPath: cssPath || '', error: 'missing tailwind.css / tailwind.baseColor in components.json' };
|
|
41
114
|
}
|
|
42
115
|
const target = join(cwd, cssPath);
|
|
43
116
|
const existing = existsSync(target) ? readFileSync(target, 'utf8') : '';
|
|
44
|
-
|
|
117
|
+
const start = existing.indexOf(THEME_MARKER);
|
|
118
|
+
if (start >= 0 && !opts.replace) {
|
|
45
119
|
return { status: 'present', cssPath };
|
|
46
120
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
} catch (e) {
|
|
51
|
-
return { status: 'failed', cssPath, error: e && e.message ? e.message : String(e) };
|
|
121
|
+
const end = start >= 0 ? existing.indexOf(THEME_END_MARKER, start) : -1;
|
|
122
|
+
if (start >= 0 && end < 0) {
|
|
123
|
+
return { status: 'legacy', cssPath };
|
|
52
124
|
}
|
|
53
|
-
|
|
125
|
+
|
|
126
|
+
let themeBlock = opts.block || '';
|
|
54
127
|
if (!themeBlock) {
|
|
55
|
-
|
|
128
|
+
let item;
|
|
129
|
+
try {
|
|
130
|
+
item = await getRegistryItem(`theme-${baseColor}`, registryUrl);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
return { status: 'failed', cssPath, error: e && e.message ? e.message : String(e) };
|
|
133
|
+
}
|
|
134
|
+
themeBlock = item && item.files && item.files[0] ? item.files[0].content || '' : '';
|
|
135
|
+
if (!themeBlock) {
|
|
136
|
+
return { status: 'failed', cssPath, error: `theme-${baseColor} has no content` };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
themeBlock = withEndMarker(themeBlock);
|
|
140
|
+
|
|
141
|
+
let next;
|
|
142
|
+
if (start >= 0) {
|
|
143
|
+
// Replace from the start marker through the end of the end-marker line.
|
|
144
|
+
const lineEnd = existing.indexOf('\n', end);
|
|
145
|
+
const after = lineEnd < 0 ? '' : existing.slice(lineEnd + 1);
|
|
146
|
+
next = existing.slice(0, start) + themeBlock + after;
|
|
147
|
+
} else {
|
|
148
|
+
next = existing + (existing && !existing.endsWith('\n') ? '\n' : '') + themeBlock;
|
|
56
149
|
}
|
|
57
150
|
try {
|
|
58
151
|
ensureDir(dirname(target));
|
|
59
|
-
writeFileSync(
|
|
60
|
-
target,
|
|
61
|
-
existing + (existing && !existing.endsWith('\n') ? '\n' : '') + themeBlock,
|
|
62
|
-
'utf8',
|
|
63
|
-
);
|
|
152
|
+
writeFileSync(target, next, 'utf8');
|
|
64
153
|
} catch (e) {
|
|
65
154
|
return { status: 'failed', cssPath, error: e && e.message ? e.message : String(e) };
|
|
66
155
|
}
|
|
67
|
-
return { status: 'written', cssPath };
|
|
156
|
+
return { status: start >= 0 ? 'replaced' : 'written', cssPath };
|
|
68
157
|
}
|