@sableui/react 0.6.2 → 0.7.1
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/AGENTS.md +2 -2
- package/CHANGELOG.md +315 -0
- package/CONTRIBUTING.md +16 -10
- package/DESIGN.md +3 -3
- package/README.md +3 -3
- package/bin/sable-lint.mjs +18 -0
- package/bin/sable.mjs +470 -62
- package/dist/blocks/api-keys-block.js +44 -0
- package/dist/blocks/api-keys-block.meta.js +21 -0
- package/dist/blocks/chat-thread-block.js +17 -0
- package/dist/blocks/chat-thread-block.meta.js +20 -0
- package/dist/blocks/command-palette-block.js +24 -0
- package/dist/blocks/command-palette-block.meta.js +21 -0
- package/dist/blocks/cta-band-block.js +27 -0
- package/dist/blocks/cta-band-block.meta.js +20 -0
- package/dist/blocks/danger-zone-block.js +39 -0
- package/dist/blocks/danger-zone-block.meta.js +21 -0
- package/dist/blocks/device-comparison-block.js +69 -0
- package/dist/blocks/device-comparison-block.meta.js +21 -0
- package/dist/blocks/event-card-block.js +40 -0
- package/dist/blocks/event-card-block.meta.js +21 -0
- package/dist/blocks/faq-block.js +38 -0
- package/dist/blocks/faq-block.meta.js +20 -0
- package/dist/blocks/feature-grid-block.js +38 -0
- package/dist/blocks/feature-grid-block.meta.js +21 -0
- package/dist/blocks/feedback-form-block.js +30 -0
- package/dist/blocks/feedback-form-block.meta.js +21 -0
- package/dist/blocks/goal-progress-block.js +24 -0
- package/dist/blocks/goal-progress-block.meta.js +21 -0
- package/dist/blocks/hero-block.js +33 -0
- package/dist/blocks/hero-block.meta.js +21 -0
- package/dist/blocks/index.js +162 -1964
- package/dist/blocks/invite-team-block.js +36 -0
- package/dist/blocks/invite-team-block.meta.js +20 -0
- package/dist/blocks/invoice-history-block.js +41 -0
- package/dist/blocks/invoice-history-block.meta.js +21 -0
- package/dist/blocks/kpi-sparkline-block.js +29 -0
- package/dist/blocks/kpi-sparkline-block.meta.js +20 -0
- package/dist/blocks/media-player-block.js +68 -0
- package/dist/blocks/media-player-block.meta.js +21 -0
- package/dist/blocks/newsletter-block.js +35 -0
- package/dist/blocks/newsletter-block.meta.js +21 -0
- package/dist/blocks/not-found-block.js +27 -0
- package/dist/blocks/not-found-block.meta.js +20 -0
- package/dist/blocks/notification-list-block.js +32 -0
- package/dist/blocks/notification-list-block.meta.js +21 -0
- package/dist/blocks/notification-settings-block.js +25 -0
- package/dist/blocks/notification-settings-block.meta.js +20 -0
- package/dist/blocks/order-tracking-block.js +26 -0
- package/dist/blocks/order-tracking-block.meta.js +20 -0
- package/dist/blocks/pricing-card-block.js +46 -0
- package/dist/blocks/pricing-card-block.meta.js +21 -0
- package/dist/blocks/pricing-table-block.js +88 -0
- package/dist/blocks/pricing-table-block.meta.js +20 -0
- package/dist/blocks/product-card-block.js +26 -0
- package/dist/blocks/product-card-block.meta.js +21 -0
- package/dist/blocks/profile-card-block.js +58 -0
- package/dist/blocks/profile-card-block.meta.js +21 -0
- package/dist/blocks/profile-form-block.js +70 -0
- package/dist/blocks/profile-form-block.meta.js +21 -0
- package/dist/blocks/revenue-breakdown-block.js +38 -0
- package/dist/blocks/revenue-breakdown-block.meta.js +21 -0
- package/dist/blocks/sign-in-block.js +38 -0
- package/dist/blocks/sign-in-block.meta.js +21 -0
- package/dist/blocks/sign-up-block.js +48 -0
- package/dist/blocks/sign-up-block.meta.js +21 -0
- package/dist/blocks/site-footer-block.js +54 -0
- package/dist/blocks/site-footer-block.meta.js +20 -0
- package/dist/blocks/stat-row-block.js +28 -0
- package/dist/blocks/stat-row-block.meta.js +21 -0
- package/dist/blocks/steps-block.js +37 -0
- package/dist/blocks/steps-block.meta.js +20 -0
- package/dist/blocks/task-list-block.js +52 -0
- package/dist/blocks/task-list-block.meta.js +21 -0
- package/dist/blocks/team-members-block.js +49 -0
- package/dist/blocks/team-members-block.meta.js +21 -0
- package/dist/blocks/testimonial-block.js +49 -0
- package/dist/blocks/testimonial-block.meta.js +20 -0
- package/dist/blocks/traffic-sources-block.js +39 -0
- package/dist/blocks/traffic-sources-block.meta.js +21 -0
- package/dist/blocks/traffic-trend-block.js +61 -0
- package/dist/blocks/traffic-trend-block.meta.js +21 -0
- package/dist/blocks/upload-list-block.js +39 -0
- package/dist/blocks/upload-list-block.meta.js +21 -0
- package/dist/blocks/verify-code-block.js +28 -0
- package/dist/blocks/verify-code-block.meta.js +21 -0
- package/dist/blocks/wallet-block.js +48 -0
- package/dist/blocks/wallet-block.meta.js +21 -0
- package/dist/components/accordion/accordion.js +63 -0
- package/dist/components/accordion/accordion.meta.js +155 -0
- package/dist/components/accordion/index.js +15 -0
- package/dist/components/app-header/app-header.js +30 -0
- package/dist/components/app-header/app-header.meta.js +88 -0
- package/dist/components/app-header/index.js +7 -0
- package/dist/components/app-shell/app-shell-dock.js +37 -0
- package/dist/components/app-shell/app-shell.js +67 -0
- package/dist/components/app-shell/app-shell.meta.js +176 -0
- package/dist/components/app-shell/index.js +7 -0
- package/dist/components/area-chart/area-chart.js +10 -0
- package/dist/components/area-chart/area-chart.meta.js +218 -0
- package/dist/components/area-chart/index.js +7 -0
- package/dist/components/attachment/attachment.js +176 -0
- package/dist/components/attachment/attachment.meta.js +179 -0
- package/dist/components/attachment/attachment.variants.js +47 -0
- package/dist/components/attachment/index.js +7 -0
- package/dist/components/avatar/avatar.js +192 -0
- package/dist/components/avatar/avatar.meta.js +233 -0
- package/dist/components/avatar/avatar.variants.js +44 -0
- package/dist/components/avatar/index.js +22 -0
- package/dist/components/badge/badge.js +26 -0
- package/dist/components/badge/badge.meta.js +171 -0
- package/dist/components/badge/badge.variants.js +103 -0
- package/dist/components/badge/index.js +11 -0
- package/dist/components/banner/banner.js +76 -0
- package/dist/components/banner/banner.meta.js +172 -0
- package/dist/components/banner/banner.variants.js +44 -0
- package/dist/components/banner/index.js +9 -0
- package/dist/components/bar-chart/bar-chart.js +10 -0
- package/dist/components/bar-chart/bar-chart.meta.js +245 -0
- package/dist/components/bar-chart/index.js +7 -0
- package/dist/components/bottom-nav/bottom-nav.js +63 -0
- package/dist/components/bottom-nav/bottom-nav.meta.js +95 -0
- package/dist/components/bottom-nav/index.js +11 -0
- package/dist/components/breadcrumbs/breadcrumbs.js +90 -0
- package/dist/components/breadcrumbs/breadcrumbs.meta.js +111 -0
- package/dist/components/breadcrumbs/index.js +17 -0
- package/dist/components/browser/browser.js +39 -0
- package/dist/components/browser/browser.meta.js +80 -0
- package/dist/components/browser/index.js +7 -0
- package/dist/components/bubble/bubble.js +110 -0
- package/dist/components/bubble/bubble.meta.js +158 -0
- package/dist/components/bubble/bubble.variants.js +90 -0
- package/dist/components/bubble/index.js +13 -0
- package/dist/components/button/button.js +87 -0
- package/dist/components/button/button.meta.js +302 -0
- package/dist/components/button/button.variants.js +137 -0
- package/dist/components/button/index.js +9 -0
- package/dist/components/calendar/calendar.js +89 -0
- package/dist/components/calendar/calendar.meta.js +136 -0
- package/dist/components/calendar/index.js +7 -0
- package/dist/components/card/card.js +203 -0
- package/dist/components/card/card.meta.js +212 -0
- package/dist/components/card/index.js +23 -0
- package/dist/components/carousel/carousel.js +428 -0
- package/dist/components/carousel/carousel.meta.js +100 -0
- package/dist/components/carousel/index.js +7 -0
- package/dist/components/chat-composer/chat-composer.js +174 -0
- package/dist/components/chat-composer/chat-composer.meta.js +241 -0
- package/dist/components/chat-composer/chat-composer.variants.js +17 -0
- package/dist/components/chat-composer/index.js +7 -0
- package/dist/components/checkbox/checkbox-mark.js +28 -0
- package/dist/components/checkbox/checkbox.js +30 -0
- package/dist/components/checkbox/checkbox.meta.js +127 -0
- package/dist/components/checkbox/index.js +7 -0
- package/dist/components/checkbox-card/checkbox-card.js +94 -0
- package/dist/components/checkbox-card/checkbox-card.meta.js +154 -0
- package/dist/components/checkbox-card/checkbox-card.variants.js +43 -0
- package/dist/components/checkbox-card/index.js +11 -0
- package/dist/components/code/code.js +19 -0
- package/dist/components/code/code.meta.js +71 -0
- package/dist/components/code/index.js +7 -0
- package/dist/components/code-block/code-block.js +84 -0
- package/dist/components/code-block/code-block.meta.js +103 -0
- package/dist/components/code-block/index.js +8 -0
- package/dist/components/collapsible/collapsible.js +38 -0
- package/dist/components/collapsible/collapsible.meta.js +117 -0
- package/dist/components/collapsible/index.js +15 -0
- package/dist/components/color-field/color-field.js +293 -0
- package/dist/components/color-field/color-field.meta.js +142 -0
- package/dist/components/color-field/color.js +69 -0
- package/dist/components/color-field/index.js +15 -0
- package/dist/components/combobox/combobox.js +486 -0
- package/dist/components/combobox/combobox.meta.js +217 -0
- package/dist/components/combobox/index.js +37 -0
- package/dist/components/command-menu/command-menu-trigger.js +122 -0
- package/dist/components/command-menu/command-menu.js +433 -0
- package/dist/components/command-menu/command-menu.meta.js +279 -0
- package/dist/components/command-menu/index.js +21 -0
- package/dist/components/context-card/context-card.js +62 -0
- package/dist/components/context-card/context-card.meta.js +106 -0
- package/dist/components/context-card/index.js +17 -0
- package/dist/components/context-menu/context-menu.js +92 -0
- package/dist/components/context-menu/context-menu.meta.js +138 -0
- package/dist/components/context-menu/index.js +23 -0
- package/dist/components/data-table/data-table.js +142 -0
- package/dist/components/data-table/data-table.meta.js +146 -0
- package/dist/components/data-table/index.js +9 -0
- package/dist/components/date-picker/date-picker.js +196 -0
- package/dist/components/date-picker/date-picker.meta.js +164 -0
- package/dist/components/date-picker/index.js +7 -0
- package/dist/components/date-tile/date-tile.js +29 -0
- package/dist/components/date-tile/date-tile.meta.js +72 -0
- package/dist/components/date-tile/index.js +7 -0
- package/dist/components/destructive-action-modal/destructive-action-modal.js +130 -0
- package/dist/components/destructive-action-modal/destructive-action-modal.meta.js +136 -0
- package/dist/components/destructive-action-modal/index.js +9 -0
- package/dist/components/dialog/dialog.js +207 -0
- package/dist/components/dialog/dialog.meta.js +239 -0
- package/dist/components/dialog/index.js +45 -0
- package/dist/components/disclosure-picker/disclosure-picker.js +417 -0
- package/dist/components/disclosure-picker/disclosure-picker.meta.js +213 -0
- package/dist/components/disclosure-picker/index.js +21 -0
- package/dist/components/empty-state/empty-state.js +52 -0
- package/dist/components/empty-state/empty-state.meta.js +158 -0
- package/dist/components/empty-state/index.js +7 -0
- package/dist/components/entity/entity.js +70 -0
- package/dist/components/entity/entity.meta.js +108 -0
- package/dist/components/entity/entity.variants.js +16 -0
- package/dist/components/entity/index.js +7 -0
- package/dist/components/feedback/feedback.js +93 -0
- package/dist/components/feedback/feedback.meta.js +109 -0
- package/dist/components/feedback/index.js +7 -0
- package/dist/components/fieldset/fieldset.js +80 -0
- package/dist/components/fieldset/fieldset.meta.js +160 -0
- package/dist/components/fieldset/index.js +17 -0
- package/dist/components/file-dropzone/file-dropzone.js +193 -0
- package/dist/components/file-dropzone/file-dropzone.meta.js +210 -0
- package/dist/components/file-dropzone/file-dropzone.variants.js +26 -0
- package/dist/components/file-dropzone/index.js +13 -0
- package/dist/components/font-picker/font-picker.js +77 -0
- package/dist/components/font-picker/font-picker.meta.js +119 -0
- package/dist/components/font-picker/index.js +7 -0
- package/dist/components/form/form.js +145 -0
- package/dist/components/form/form.meta.js +142 -0
- package/dist/components/form/index.js +11 -0
- package/dist/components/gliding-highlight/gliding-highlight.js +50 -0
- package/dist/components/gliding-highlight/gliding-highlight.meta.js +135 -0
- package/dist/components/gliding-highlight/index.js +9 -0
- package/dist/components/gradient-art/gradient-art.js +168 -0
- package/dist/components/gradient-art/gradient-art.meta.js +81 -0
- package/dist/components/gradient-art/index.js +7 -0
- package/dist/components/gradient-art/renderer.js +336 -0
- package/dist/components/inline-status/index.js +7 -0
- package/dist/components/inline-status/inline-status.js +33 -0
- package/dist/components/inline-status/inline-status.meta.js +83 -0
- package/dist/components/inline-status/inline-status.variants.js +18 -0
- package/dist/components/input/index.js +7 -0
- package/dist/components/input/input.js +33 -0
- package/dist/components/input/input.meta.js +155 -0
- package/dist/components/kbd/index.js +8 -0
- package/dist/components/kbd/kbd.js +54 -0
- package/dist/components/kbd/kbd.meta.js +95 -0
- package/dist/components/label/index.js +7 -0
- package/dist/components/label/label.js +51 -0
- package/dist/components/label/label.meta.js +98 -0
- package/dist/components/line-chart/index.js +7 -0
- package/dist/components/line-chart/line-chart.js +10 -0
- package/dist/components/line-chart/line-chart.meta.js +222 -0
- package/dist/components/marker/index.js +7 -0
- package/dist/components/marker/marker.js +39 -0
- package/dist/components/marker/marker.meta.js +132 -0
- package/dist/components/marker/marker.variants.js +25 -0
- package/dist/components/marquee/index.js +7 -0
- package/dist/components/marquee/marquee.js +146 -0
- package/dist/components/marquee/marquee.meta.js +106 -0
- package/dist/components/media-image/index.js +7 -0
- package/dist/components/media-image/media-image.js +56 -0
- package/dist/components/media-image/media-image.meta.js +92 -0
- package/dist/components/media-tile/index.js +7 -0
- package/dist/components/media-tile/media-tile.js +135 -0
- package/dist/components/media-tile/media-tile.meta.js +133 -0
- package/dist/components/media-tile/media-tile.variants.js +48 -0
- package/dist/components/menu/index.js +33 -0
- package/dist/components/menu/menu.js +168 -0
- package/dist/components/menu/menu.meta.js +176 -0
- package/dist/components/message/index.js +7 -0
- package/dist/components/message/message.js +83 -0
- package/dist/components/message/message.meta.js +150 -0
- package/dist/components/message/message.variants.js +41 -0
- package/dist/components/message-scroller/index.js +9 -0
- package/dist/components/message-scroller/message-scroller.js +300 -0
- package/dist/components/message-scroller/message-scroller.meta.js +172 -0
- package/dist/components/meter/index.js +20 -0
- package/dist/components/meter/meter.js +112 -0
- package/dist/components/meter/meter.meta.js +227 -0
- package/dist/components/meter/meter.variants.js +61 -0
- package/dist/components/middle-truncate/index.js +7 -0
- package/dist/components/middle-truncate/middle-truncate.js +28 -0
- package/dist/components/middle-truncate/middle-truncate.meta.js +77 -0
- package/dist/components/note/index.js +9 -0
- package/dist/components/note/note.js +73 -0
- package/dist/components/note/note.meta.js +112 -0
- package/dist/components/note/note.variants.js +22 -0
- package/dist/components/notification-badge/index.js +15 -0
- package/dist/components/notification-badge/notification-badge.js +48 -0
- package/dist/components/notification-badge/notification-badge.meta.js +107 -0
- package/dist/components/notification-badge/notification-badge.variants.js +57 -0
- package/dist/components/number-field/index.js +7 -0
- package/dist/components/number-field/number-field.js +70 -0
- package/dist/components/number-field/number-field.meta.js +109 -0
- package/dist/components/otp-field/index.js +7 -0
- package/dist/components/otp-field/otp-field.js +70 -0
- package/dist/components/otp-field/otp-field.meta.js +197 -0
- package/dist/components/overflow-menu/index.js +7 -0
- package/dist/components/overflow-menu/overflow-menu.js +26 -0
- package/dist/components/overflow-menu/overflow-menu.meta.js +100 -0
- package/dist/components/page-header/index.js +7 -0
- package/dist/components/page-header/page-header.js +34 -0
- package/dist/components/page-header/page-header.meta.js +104 -0
- package/dist/components/page-loader/index.js +7 -0
- package/dist/components/page-loader/page-loader.js +37 -0
- package/dist/components/page-loader/page-loader.meta.js +85 -0
- package/dist/components/pagination/index.js +7 -0
- package/dist/components/pagination/pagination.js +105 -0
- package/dist/components/pagination/pagination.meta.js +141 -0
- package/dist/components/panel-group/index.js +10 -0
- package/dist/components/panel-group/panel-group.js +168 -0
- package/dist/components/panel-group/panel-group.meta.js +153 -0
- package/dist/components/panel-section/index.js +9 -0
- package/dist/components/panel-section/panel-section.js +115 -0
- package/dist/components/panel-section/panel-section.meta.js +156 -0
- package/dist/components/phone/index.js +7 -0
- package/dist/components/phone/phone.js +50 -0
- package/dist/{chunk-KFZ43RKU.js → components/phone/phone.meta.js} +3 -50
- package/dist/{chunk-NEQNXI2G.js → components/phone/status-icons.js} +4 -4
- package/dist/components/pie-chart/index.js +7 -0
- package/dist/components/pie-chart/pie-chart.js +191 -0
- package/dist/components/pie-chart/pie-chart.meta.js +210 -0
- package/dist/components/pill/index.js +9 -0
- package/dist/components/pill/pill.js +24 -0
- package/dist/components/pill/pill.meta.js +108 -0
- package/dist/components/pill/pill.variants.js +65 -0
- package/dist/components/popover/index.js +23 -0
- package/dist/components/popover/popover.js +100 -0
- package/dist/components/popover/popover.meta.js +136 -0
- package/dist/components/progress/index.js +19 -0
- package/dist/components/progress/progress.js +70 -0
- package/dist/components/progress/progress.meta.js +171 -0
- package/dist/components/progress/progress.variants.js +17 -0
- package/dist/components/radar-chart/index.js +7 -0
- package/dist/components/radar-chart/radar-chart.js +161 -0
- package/dist/components/radar-chart/radar-chart.meta.js +204 -0
- package/dist/components/radial-chart/index.js +7 -0
- package/dist/components/radial-chart/radial-chart.js +175 -0
- package/dist/components/radial-chart/radial-chart.meta.js +216 -0
- package/dist/components/radio/index.js +8 -0
- package/dist/components/radio/radio.js +39 -0
- package/dist/components/radio/radio.meta.js +104 -0
- package/dist/components/radio-card/index.js +7 -0
- package/dist/components/radio-card/radio-card.js +81 -0
- package/dist/components/radio-card/radio-card.meta.js +144 -0
- package/dist/components/radio-card/radio-card.variants.js +43 -0
- package/dist/components/relative-time/index.js +7 -0
- package/dist/components/relative-time/relative-time.js +47 -0
- package/dist/components/relative-time/relative-time.meta.js +77 -0
- package/dist/components/scroller/index.js +7 -0
- package/dist/components/scroller/scroller.js +111 -0
- package/dist/components/scroller/scroller.meta.js +118 -0
- package/dist/components/scrub-field/index.js +7 -0
- package/dist/components/scrub-field/scrub-field.js +124 -0
- package/dist/components/scrub-field/scrub-field.meta.js +139 -0
- package/dist/components/search-input/index.js +7 -0
- package/dist/components/search-input/search-input.js +91 -0
- package/dist/components/search-input/search-input.meta.js +129 -0
- package/dist/components/section-header/index.js +7 -0
- package/dist/components/section-header/section-header.js +39 -0
- package/dist/components/section-header/section-header.meta.js +174 -0
- package/dist/components/segmented-bar/index.js +14 -0
- package/dist/components/segmented-bar/segmented-bar.js +26 -0
- package/dist/components/segmented-bar/segmented-bar.meta.js +108 -0
- package/dist/components/segmented-bar/segmented-bar.variants.js +39 -0
- package/dist/components/select/index.js +25 -0
- package/dist/components/select/select.js +191 -0
- package/dist/components/select/select.meta.js +189 -0
- package/dist/components/separator/index.js +7 -0
- package/dist/components/separator/separator.js +42 -0
- package/dist/components/separator/separator.meta.js +84 -0
- package/dist/components/settings-menu/index.js +11 -0
- package/dist/components/settings-menu/settings-menu.js +101 -0
- package/dist/components/settings-menu/settings-menu.meta.js +177 -0
- package/dist/components/settings-menu/settings-menu.variants.js +15 -0
- package/dist/components/sheet/index.js +29 -0
- package/dist/components/sheet/sheet.js +121 -0
- package/dist/components/sheet/sheet.meta.js +167 -0
- package/dist/components/sheet/sheet.variants.js +21 -0
- package/dist/components/show-more/index.js +7 -0
- package/dist/components/show-more/show-more.js +87 -0
- package/dist/components/show-more/show-more.meta.js +79 -0
- package/dist/components/side-nav/index.js +13 -0
- package/dist/components/side-nav/side-nav.js +75 -0
- package/dist/components/side-nav/side-nav.meta.js +139 -0
- package/dist/components/side-nav/side-nav.variants.js +34 -0
- package/dist/components/sidebar/index.js +21 -0
- package/dist/components/sidebar/sidebar.js +196 -0
- package/dist/components/sidebar/sidebar.meta.js +118 -0
- package/dist/components/skeleton/index.js +7 -0
- package/dist/components/skeleton/skeleton.js +16 -0
- package/dist/components/skeleton/skeleton.meta.js +90 -0
- package/dist/components/slider/index.js +21 -0
- package/dist/components/slider/slider.js +115 -0
- package/dist/components/slider/slider.meta.js +214 -0
- package/dist/components/sparkline/index.js +7 -0
- package/dist/components/sparkline/sparkline.js +84 -0
- package/dist/components/sparkline/sparkline.meta.js +147 -0
- package/dist/components/spinner/index.js +7 -0
- package/dist/components/spinner/spinner.js +59 -0
- package/dist/components/spinner/spinner.meta.js +118 -0
- package/dist/components/status-bar/index.js +9 -0
- package/dist/components/status-bar/status-bar.js +40 -0
- package/dist/components/status-bar/status-bar.meta.js +117 -0
- package/dist/components/status-dot/index.js +9 -0
- package/dist/components/status-dot/status-dot.js +40 -0
- package/dist/components/status-dot/status-dot.meta.js +113 -0
- package/dist/components/status-dot/status-dot.variants.js +28 -0
- package/dist/components/stepper/index.js +17 -0
- package/dist/components/stepper/stepper.js +161 -0
- package/dist/components/stepper/stepper.meta.js +190 -0
- package/dist/components/stepper/stepper.variants.js +72 -0
- package/dist/components/streaming-text/index.js +7 -0
- package/dist/components/streaming-text/streaming-text.js +55 -0
- package/dist/components/streaming-text/streaming-text.meta.js +147 -0
- package/dist/components/switch/index.js +7 -0
- package/dist/components/switch/switch.js +26 -0
- package/dist/components/switch/switch.meta.js +133 -0
- package/dist/components/table/index.js +23 -0
- package/dist/components/table/table.js +117 -0
- package/dist/components/table/table.meta.js +143 -0
- package/dist/components/tabs/index.js +17 -0
- package/dist/components/tabs/tabs.js +58 -0
- package/dist/components/tabs/tabs.meta.js +120 -0
- package/dist/components/textarea/index.js +7 -0
- package/dist/components/textarea/textarea.js +38 -0
- package/dist/components/textarea/textarea.meta.js +117 -0
- package/dist/components/theme-selector/index.js +11 -0
- package/dist/components/theme-selector/theme-selector.js +145 -0
- package/dist/components/theme-selector/theme-selector.meta.js +181 -0
- package/dist/components/theme-switcher/index.js +7 -0
- package/dist/components/theme-switcher/theme-switcher.js +106 -0
- package/dist/components/theme-switcher/theme-switcher.meta.js +99 -0
- package/dist/components/thinking-orb/index.js +9 -0
- package/dist/components/thinking-orb/thinking-orb.js +126 -0
- package/dist/components/thinking-orb/thinking-orb.meta.js +142 -0
- package/dist/components/thinking-orb/thinking-orb.render.js +252 -0
- package/dist/components/toast/index.js +29 -0
- package/dist/components/toast/toast.js +254 -0
- package/dist/components/toast/toast.meta.js +261 -0
- package/dist/components/toggle/index.js +13 -0
- package/dist/components/toggle/toggle.js +51 -0
- package/dist/components/toggle/toggle.meta.js +129 -0
- package/dist/components/toolbar/index.js +27 -0
- package/dist/components/toolbar/toolbar.js +80 -0
- package/dist/components/toolbar/toolbar.meta.js +272 -0
- package/dist/components/toolbar/toolbar.variants.js +16 -0
- package/dist/components/tooltip/index.js +21 -0
- package/dist/components/tooltip/tooltip.js +74 -0
- package/dist/components/tooltip/tooltip.meta.js +156 -0
- package/dist/components/tree/index.js +12 -0
- package/dist/components/tree/tree.js +364 -0
- package/dist/components/tree/tree.meta.js +334 -0
- package/dist/components/video/index.js +7 -0
- package/dist/components/video/video.js +59 -0
- package/dist/components/video/video.meta.js +81 -0
- package/dist/components/workspace/index.js +9 -0
- package/dist/components/workspace/workspace.js +79 -0
- package/dist/components/workspace/workspace.meta.js +183 -0
- package/dist/foundations/accessibility.js +107 -0
- package/dist/foundations/index.js +25 -792
- package/dist/foundations/motion.js +135 -0
- package/dist/foundations/scrolling.js +62 -0
- package/dist/foundations/shape.js +113 -0
- package/dist/foundations/spacing.js +121 -0
- package/dist/foundations/surfaces.js +107 -0
- package/dist/foundations/types.js +0 -0
- package/dist/foundations/typography.js +152 -0
- package/dist/icon/Icon.js +30 -0
- package/dist/icon/IconProvider.js +27 -0
- package/dist/icon/compose.js +13 -0
- package/dist/icon/index.d.ts +13 -5
- package/dist/icon/index.js +13 -2
- package/dist/icon/registry.js +0 -0
- package/dist/icon-sets/brands.generated.js +123 -0
- package/dist/icon-sets/brands.js +10 -123
- package/dist/icon-sets/reicon.d.ts +3 -1
- package/dist/icon-sets/reicon.js +209 -1
- package/dist/index.d.ts +618 -34
- package/dist/index.js +211 -11351
- package/dist/ios/chrome.js +36 -0
- package/dist/ios/index.js +19 -315
- package/dist/ios/liquid-glass.js +103 -0
- package/dist/ios/native-app.js +46 -0
- package/dist/ios/navigation.js +63 -0
- package/dist/ios/phone-frame.js +64 -0
- package/dist/ios/preview-theme.js +83 -0
- package/dist/ios/preview.js +10 -137
- package/dist/ios/sheet.js +85 -0
- package/dist/ios/theme.js +19 -0
- package/dist/layout/AspectBox.js +27 -0
- package/dist/layout/Bleed.js +15 -0
- package/dist/layout/Center.js +21 -0
- package/dist/layout/Columns.js +70 -0
- package/dist/layout/Grid.js +50 -0
- package/dist/layout/GridOverlay.js +41 -0
- package/dist/layout/Page.js +11 -0
- package/dist/layout/Region.js +39 -0
- package/dist/layout/Row.js +45 -0
- package/dist/layout/SafeArea.js +26 -0
- package/dist/layout/Spacer.js +9 -0
- package/dist/layout/SplitPane.js +55 -0
- package/dist/layout/Stack.js +42 -0
- package/dist/layout/_responsive.js +16 -0
- package/dist/layout/_safeArea.js +18 -0
- package/dist/layout/index.d.ts +30 -8
- package/dist/layout/index.js +14 -2
- package/dist/lib/anchored-scroll.js +7 -0
- package/dist/lib/bleed.js +7 -0
- package/dist/lib/chart/cartesian.js +365 -0
- package/dist/lib/chart/chart-frame.js +163 -0
- package/dist/lib/chart/chart-legend.js +13 -0
- package/dist/lib/chart/chart-tooltip.js +38 -0
- package/dist/lib/chart/format.js +24 -0
- package/dist/lib/chart/geometry.js +114 -0
- package/dist/lib/chart/motion.js +34 -0
- package/dist/lib/chart/scale.js +72 -0
- package/dist/lib/chart/types.js +45 -0
- package/dist/lib/chart/use-arrival.js +41 -0
- package/dist/{chunk-SRQWQUTW.js → lib/cn.js} +5 -5
- package/dist/lib/component-guidance.js +215 -0
- package/dist/lib/density.js +54 -0
- package/dist/lib/field-shell.js +11 -0
- package/dist/lib/glyphs.js +9 -0
- package/dist/lib/heading-row.js +36 -0
- package/dist/lib/highlight-glide.js +84 -0
- package/dist/lib/hit-target.js +5 -0
- package/dist/lib/meta.js +0 -0
- package/dist/lib/portal-theme-scope.js +74 -0
- package/dist/{chunk-FKZXAFIY.js → lib/presentation.js} +8 -3
- package/dist/lib/refs.js +14 -0
- package/dist/lib/scrub-gesture.js +50 -0
- package/dist/lib/segmented.js +13 -0
- package/dist/lib/selection-card.js +41 -0
- package/dist/lib/status-tone.js +13 -0
- package/dist/lib/taxonomy.js +214 -0
- package/dist/lib/trim-label.js +20 -0
- package/dist/lib/use-copy.js +27 -0
- package/dist/lib/use-edge-fade.js +32 -0
- package/dist/lib/use-isomorphic-layout-effect.js +8 -0
- package/dist/lib/use-resize-observer.js +18 -0
- package/dist/lib/use-scroll-activity.js +52 -0
- package/dist/lib/use-wheel-to-horizontal.js +65 -0
- package/dist/motion/MotionProvider.js +75 -0
- package/dist/motion/Presence.js +112 -0
- package/dist/motion/Reveal.js +42 -0
- package/dist/motion/TextReveal.js +61 -0
- package/dist/motion/index.js +24 -0
- package/dist/motion/useInView.js +36 -0
- package/dist/motion/useReducedMotion.js +25 -0
- package/dist/motion/viewTransition.js +47 -0
- package/dist/patterns/auth-page.js +90 -0
- package/dist/patterns/auth-page.meta.js +27 -0
- package/dist/patterns/brand-landing-page.js +341 -0
- package/dist/patterns/brand-landing-page.meta.js +28 -0
- package/dist/patterns/chat-panel.js +185 -0
- package/dist/patterns/chat-panel.meta.js +31 -0
- package/dist/patterns/dashboard-page.js +332 -0
- package/dist/patterns/dashboard-page.meta.js +29 -0
- package/dist/patterns/feed-page.js +205 -0
- package/dist/patterns/feed-page.meta.js +25 -0
- package/dist/patterns/index.js +34 -2140
- package/dist/patterns/landing-page.js +356 -0
- package/dist/patterns/landing-page.meta.js +26 -0
- package/dist/patterns/list-detail-page.js +373 -0
- package/dist/patterns/list-detail-page.meta.js +24 -0
- package/dist/patterns/settings-page.js +268 -0
- package/dist/patterns/settings-page.meta.js +27 -0
- package/dist/presets/index.d.ts +7 -1
- package/dist/presets/index.js +53 -185
- package/dist/styles.css +118 -3
- package/dist/{theme-provider-kQszHYji.d.ts → theme-provider-S0NVJs2Y.d.ts} +6 -1
- package/dist/{chunk-NETUFI34.js → themes/_contract.js} +14 -2
- package/dist/themes/atelier/index.js +17 -1
- package/dist/themes/contrast.js +288 -0
- package/dist/themes/ember/ember.css +6 -6
- package/dist/themes/ember/index.js +16 -1
- package/dist/themes/index.d.ts +1 -1
- package/dist/themes/index.js +3 -8
- package/dist/themes/meridian/index.js +16 -1
- package/dist/themes/poppy/index.js +17 -1
- package/dist/themes/prism/index.js +17 -1
- package/dist/themes/prism/prism.css +2 -2
- package/dist/themes/registry.js +59 -0
- package/dist/themes/sable/index.js +16 -1
- package/dist/{chunk-EFLC4NA3.js → themes/theme-provider.js} +38 -54
- package/dist/themes/verdant/index.js +17 -1
- package/llms/blocks/media-player-block.md +20 -7
- package/llms/blocks/product-card-block.md +6 -4
- package/llms/blocks/task-list-block.md +1 -1
- package/llms/blocks/traffic-sources-block.md +1 -1
- package/llms/blocks/traffic-trend-block.md +5 -3
- package/llms/blocks/verify-code-block.md +1 -1
- package/llms/components/app-shell.md +5 -2
- package/llms/components/avatar.md +25 -3
- package/llms/components/button.md +20 -1
- package/llms/components/card.md +1 -1
- package/llms/components/disclosure-picker.md +168 -0
- package/llms/components/font-picker.md +104 -0
- package/llms/components/media-image.md +98 -0
- package/llms/components/media-tile.md +143 -0
- package/llms/components/page-header.md +6 -5
- package/llms/components/pie-chart.md +2 -2
- package/llms/components/radial-chart.md +1 -1
- package/llms/components/section-header.md +144 -0
- package/llms/components/sheet.md +10 -0
- package/llms/components/sidebar.md +4 -3
- package/llms/components/slider.md +14 -2
- package/llms/components/theme-selector.md +44 -4
- package/llms/components/tree.md +3 -3
- package/llms/layout/grid.md +24 -3
- package/llms/layout/region.md +16 -3
- package/llms/patterns/dashboard-page.md +3 -6
- package/llms/patterns/feed-page.md +4 -4
- package/llms/patterns/list-detail-page.md +1 -0
- package/llms/patterns/settings-page.md +2 -5
- package/llms/reference/utilities.md +84 -4
- package/llms/tasks/building-a-page.md +34 -3
- package/llms/tasks/converting-an-app.md +14 -4
- package/llms/tasks/creating-a-preset.md +4 -1
- package/llms/tasks/reviewing-a-design.md +19 -0
- package/llms/tasks/using-presets.md +19 -0
- package/llms-full.txt +1066 -78
- package/llms.txt +26 -9
- package/package.json +1 -1
- package/registry.json +2342 -164
- package/scripts/agent-setup.mjs +90 -0
- package/scripts/design-presets.mjs +59 -6
- package/scripts/grade.mjs +195 -29
- package/scripts/lint-rules.mjs +486 -10
- package/scripts/review-evidence.mjs +28 -1
- package/skills/sable/SKILL.md +67 -24
- package/targets/ios.json +45 -0
- package/dist/chunk-3IWZYBGH.js +0 -398
- package/dist/chunk-7MG4SHYX.js +0 -15
- package/dist/chunk-7QSDYBXO.js +0 -7421
- package/dist/chunk-ADLLDWLW.js +0 -15
- package/dist/chunk-C4Z3CF54.js +0 -15
- package/dist/chunk-EDXLM7YL.js +0 -5078
- package/dist/chunk-EHH4MHO6.js +0 -14
- package/dist/chunk-K7RUHUGC.js +0 -14
- package/dist/chunk-KO3NAEBF.js +0 -15
- package/dist/chunk-L4IEIQQA.js +0 -14
- package/dist/chunk-QJ6FWTUG.js +0 -40
- package/dist/chunk-VVW4UCDI.js +0 -4908
- package/dist/chunk-XAZGMY73.js +0 -187
package/dist/patterns/index.js
CHANGED
|
@@ -1,2141 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}, 900);
|
|
36
|
-
},
|
|
37
|
-
children: [
|
|
38
|
-
/* @__PURE__ */ jsx(Field, { label: "Email", name: "email", validate: (value) => value ? null : "Enter your email address.", children: /* @__PURE__ */ jsx(Input, { type: "email", autoComplete: "email webauthn", placeholder: "you@example.com" }) }),
|
|
39
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 1.5, children: [
|
|
40
|
-
/* @__PURE__ */ jsx(Field, { label: "Password", name: "password", validate: (value) => value ? null : "Enter your password.", children: /* @__PURE__ */ jsx(Input, { type: "password", autoComplete: "current-password" }) }),
|
|
41
|
-
/* @__PURE__ */ jsx("a", { href: "/forgot-password", className: "hit-target self-end text-body-sm text-muted-foreground link-underline motion-interactive", children: "Forgot password?" })
|
|
42
|
-
] }),
|
|
43
|
-
/* @__PURE__ */ jsx(Button, { type: "submit", size: "cta", loading: pending2, loadingLabel: "Signing in", className: "w-full", children: "Sign in" })
|
|
44
|
-
]
|
|
45
|
-
}
|
|
46
|
-
),
|
|
47
|
-
/* @__PURE__ */ jsx(Separator, { label: "or" }),
|
|
48
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 1.5, children: [
|
|
49
|
-
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "cta", className: "w-full", iconStart: /* @__PURE__ */ jsx(Icon, { name: "key" }), children: "Sign in with a passkey" }),
|
|
50
|
-
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "cta", className: "w-full", iconStart: /* @__PURE__ */ jsx(Icon, { name: "wallet" }), children: "Connect a wallet" })
|
|
51
|
-
] }),
|
|
52
|
-
/* @__PURE__ */ jsxs("p", { className: "text-body-md text-muted-foreground", children: [
|
|
53
|
-
"No account?",
|
|
54
|
-
" ",
|
|
55
|
-
/* @__PURE__ */ jsx("a", { href: "/signup", className: "hit-target link-underline motion-interactive text-foreground", children: "Create one" })
|
|
56
|
-
] })
|
|
57
|
-
] }),
|
|
58
|
-
/* @__PURE__ */ jsxs("p", { className: "text-body-xs text-muted-foreground", children: [
|
|
59
|
-
"By signing in you agree to the",
|
|
60
|
-
" ",
|
|
61
|
-
/* @__PURE__ */ jsx("a", { href: "/terms", className: "link-underline motion-interactive", children: "Terms" }),
|
|
62
|
-
" ",
|
|
63
|
-
"and",
|
|
64
|
-
" ",
|
|
65
|
-
/* @__PURE__ */ jsx("a", { href: "/privacy", className: "link-underline motion-interactive", children: "Privacy Policy" }),
|
|
66
|
-
"."
|
|
67
|
-
] })
|
|
68
|
-
] }) }),
|
|
69
|
-
/* @__PURE__ */ jsx("aside", { "aria-label": "Featured release", className: "hidden p-3 lg:flex", children: /* @__PURE__ */ jsx(GradientArt, { seed: "Untitled (gold rush)", className: "flex flex-1 flex-col justify-end rounded-xl p-8", children: /* @__PURE__ */ jsx(Card, { className: "max-w-md", children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
70
|
-
/* @__PURE__ */ jsx(Badge, { tone: "edition", className: "self-start", children: "Edition of 50" }),
|
|
71
|
-
/* @__PURE__ */ jsx("p", { className: "text-title-lg", children: "\u201CEvery edition is a conversation with whoever holds it next.\u201D" }),
|
|
72
|
-
/* @__PURE__ */ jsx(
|
|
73
|
-
Entity,
|
|
74
|
-
{
|
|
75
|
-
className: "p-0",
|
|
76
|
-
leading: /* @__PURE__ */ jsx(Avatar, { variant: "art", fallback: "MO" }),
|
|
77
|
-
title: "Mara Okafor",
|
|
78
|
-
subtitle: "Untitled (gold rush), 2026"
|
|
79
|
-
}
|
|
80
|
-
)
|
|
81
|
-
] }) }) }) }) })
|
|
82
|
-
] });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// src/patterns/auth-page.meta.ts
|
|
86
|
-
var authPageMeta = {
|
|
87
|
-
name: "AuthPage",
|
|
88
|
-
summary: "Split sign-in page: a form column (brand mark, heading, email + password, primary sign-in, passkey and wallet alternatives, sign-up switch, terms) beside a brand panel painted from the theme's tones with a featured release card. Below lg the panel drops away and the form is the whole screen.",
|
|
89
|
-
intent: "Reach for this for sign-in, sign-up, forgot-password, and any single-purpose one-column form route (invite accept, unsubscribe). Not for multi-section forms (use SettingsPage).",
|
|
90
|
-
notes: [
|
|
91
|
-
"The form column is a `form`-capped Region (480px), so the controls keep the same measure whether the panel is present or not.",
|
|
92
|
-
"No card around the form: the page itself is the surface, and the brand panel carries the identity. A card on a blank page reads as a template.",
|
|
93
|
-
"The heading is `text-heading-1` in an h1 \u2014 it is the page outline, so it takes a heading tier, not a card title.",
|
|
94
|
-
"Brand mark, form and terms are a three-part column: mark pinned to the top, form vertically centred in the remaining space, terms pinned to the bottom.",
|
|
95
|
-
"Forgot password sits under the password field, right-aligned, before the submit button \u2014 so it comes right after the field in tab order. Field has no label-row action slot yet.",
|
|
96
|
-
"One primary action. Passkey and wallet are outline buttons under an `or` Separator; switching to sign-up is a quiet `link-underline` link.",
|
|
97
|
-
"Validation uses Field `validate` for product copy instead of the browser's generic required message. A rejected sign-in comes back through Form `errors` on the password field; the submit Button shows `loading` while the request is in flight.",
|
|
98
|
-
"The brand panel artwork is a mesh of the theme's decorative tones over `--primary`, so it re-paints for every theme and mode. The relative-colour `oklch(from var(--tone-*) \u2026)` lift needs an `inline-color` disable with a reason.",
|
|
99
|
-
"The panel is `hidden lg:flex`: on a phone the form is the whole screen, with no decorative scroll above it."
|
|
100
|
-
],
|
|
101
|
-
a11y: [
|
|
102
|
-
'The route renders inside <main> with one h1; the brand panel is an <aside> labelled "Featured release".',
|
|
103
|
-
"Field auto-wires labels, describedby, and invalid state onto the inputs; on submit, the first invalid field is focused.",
|
|
104
|
-
"Autocomplete attributes (`email webauthn`, `current-password`) keep password managers and passkey autofill working.",
|
|
105
|
-
"The Forgot password and Create one links use `hit-target` to meet the 44px touch floor without changing layout.",
|
|
106
|
-
"The artwork swatches are aria-hidden decoration; the brand link's accessible name is the wordmark."
|
|
107
|
-
]
|
|
1
|
+
// src/patterns/index.ts
|
|
2
|
+
import { AuthPage } from "./auth-page.js";
|
|
3
|
+
import { authPageMeta } from "./auth-page.meta.js";
|
|
4
|
+
import { BrandLandingPage } from "./brand-landing-page.js";
|
|
5
|
+
import { brandLandingPageMeta } from "./brand-landing-page.meta.js";
|
|
6
|
+
import { ChatPanel } from "./chat-panel.js";
|
|
7
|
+
import { chatPanelMeta } from "./chat-panel.meta.js";
|
|
8
|
+
import { DashboardPage } from "./dashboard-page.js";
|
|
9
|
+
import { dashboardPageMeta } from "./dashboard-page.meta.js";
|
|
10
|
+
import { FeedPage } from "./feed-page.js";
|
|
11
|
+
import { feedPageMeta } from "./feed-page.meta.js";
|
|
12
|
+
import { LandingPage } from "./landing-page.js";
|
|
13
|
+
import { landingPageMeta } from "./landing-page.meta.js";
|
|
14
|
+
import { ListDetailPage } from "./list-detail-page.js";
|
|
15
|
+
import { listDetailPageMeta } from "./list-detail-page.meta.js";
|
|
16
|
+
import { SettingsPage } from "./settings-page.js";
|
|
17
|
+
import { settingsPageMeta } from "./settings-page.meta.js";
|
|
18
|
+
export {
|
|
19
|
+
AuthPage,
|
|
20
|
+
BrandLandingPage,
|
|
21
|
+
ChatPanel,
|
|
22
|
+
DashboardPage,
|
|
23
|
+
FeedPage,
|
|
24
|
+
LandingPage,
|
|
25
|
+
ListDetailPage,
|
|
26
|
+
SettingsPage,
|
|
27
|
+
authPageMeta,
|
|
28
|
+
brandLandingPageMeta,
|
|
29
|
+
chatPanelMeta,
|
|
30
|
+
dashboardPageMeta,
|
|
31
|
+
feedPageMeta,
|
|
32
|
+
landingPageMeta,
|
|
33
|
+
listDetailPageMeta,
|
|
34
|
+
settingsPageMeta
|
|
108
35
|
};
|
|
109
|
-
var navItems = [
|
|
110
|
-
{ label: "Platform", href: "#platform" },
|
|
111
|
-
{ label: "How it works", href: "#how-it-works" },
|
|
112
|
-
{ label: "Customers", href: "#customers" },
|
|
113
|
-
{ label: "Pricing", href: "/pricing" }
|
|
114
|
-
];
|
|
115
|
-
var spaces = [
|
|
116
|
-
{ name: "Waypoint", value: "98.4%", label: "clear routes" },
|
|
117
|
-
{ name: "Fielddesk", value: "12", label: "open holds" },
|
|
118
|
-
{ name: "Archive", value: "4.8k", label: "sealed records" }
|
|
119
|
-
];
|
|
120
|
-
var routes = [
|
|
121
|
-
{ label: "Intake", state: "Open", tone: "info" },
|
|
122
|
-
{ label: "Review", state: "Live", tone: "edition" },
|
|
123
|
-
{ label: "Archive", state: "Safe", tone: "success" }
|
|
124
|
-
];
|
|
125
|
-
var bars = [
|
|
126
|
-
{ week: "w1", height: "38%", current: false },
|
|
127
|
-
{ week: "w2", height: "56%", current: false },
|
|
128
|
-
{ week: "w3", height: "44%", current: false },
|
|
129
|
-
{ week: "w4", height: "68%", current: false },
|
|
130
|
-
{ week: "w5", height: "52%", current: false },
|
|
131
|
-
{ week: "w6", height: "78%", current: false },
|
|
132
|
-
{ week: "w7", height: "60%", current: false },
|
|
133
|
-
{ week: "w8", height: "72%", current: false },
|
|
134
|
-
{ week: "w9", height: "88%", current: true }
|
|
135
|
-
];
|
|
136
|
-
var sceneMetrics = [
|
|
137
|
-
{ label: "Cleared", value: "1.8M", detail: "last 30 days" },
|
|
138
|
-
{ label: "Review", value: "42s", detail: "median path" },
|
|
139
|
-
{ label: "Spaces", value: "284", detail: "active teams" }
|
|
140
|
-
];
|
|
141
|
-
var values = [
|
|
142
|
-
{
|
|
143
|
-
icon: "lock",
|
|
144
|
-
title: "Private by default",
|
|
145
|
-
body: "Roles, approvals, and handoff history stay with the people who need them. No shared inboxes, no forwarded screenshots."
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
icon: "bell",
|
|
149
|
-
title: "Signals before surprises",
|
|
150
|
-
body: "Cove watches every route and surfaces stalled reviews and expiring approvals while there is still time to act."
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
icon: "clock",
|
|
154
|
-
title: "A trail for every decision",
|
|
155
|
-
body: "Each approval keeps its context, reviewer, and timestamp, so an audit takes an afternoon instead of a quarter."
|
|
156
|
-
}
|
|
157
|
-
];
|
|
158
|
-
var steps = [
|
|
159
|
-
{
|
|
160
|
-
number: "01",
|
|
161
|
-
title: "Connect your spaces",
|
|
162
|
-
body: "Bring teams, vendors, and record stores into one workspace, with roles that mirror how you already work."
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
number: "02",
|
|
166
|
-
title: "Route every approval",
|
|
167
|
-
body: "Requests follow the path you define, with reviewers, deadlines, and escalation built in from the start."
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
number: "03",
|
|
171
|
-
title: "Seal the record",
|
|
172
|
-
body: "Approved work is archived with its full context and is ready for review the moment it closes."
|
|
173
|
-
}
|
|
174
|
-
];
|
|
175
|
-
var outcomes = [
|
|
176
|
-
{ value: "3.2\xD7", label: "faster approvals in the first quarter" },
|
|
177
|
-
{ value: "68%", label: "fewer follow-ups on handoffs" },
|
|
178
|
-
{ value: "1 day", label: "to prepare a quarterly audit" }
|
|
179
|
-
];
|
|
180
|
-
var footerGroups = [
|
|
181
|
-
{
|
|
182
|
-
title: "Platform",
|
|
183
|
-
links: [
|
|
184
|
-
{ label: "Overview", href: "#platform" },
|
|
185
|
-
{ label: "Signals", href: "/signals" },
|
|
186
|
-
{ label: "Approvals", href: "/approvals" },
|
|
187
|
-
{ label: "Records", href: "/records" }
|
|
188
|
-
]
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
title: "Company",
|
|
192
|
-
links: [
|
|
193
|
-
{ label: "About", href: "/about" },
|
|
194
|
-
{ label: "Security", href: "/security" },
|
|
195
|
-
{ label: "Customers", href: "#customers" },
|
|
196
|
-
{ label: "Careers", href: "/careers" }
|
|
197
|
-
]
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
title: "Resources",
|
|
201
|
-
links: [
|
|
202
|
-
{ label: "Guides", href: "/guides" },
|
|
203
|
-
{ label: "Status", href: "/status" },
|
|
204
|
-
{ label: "Changelog", href: "/changelog" },
|
|
205
|
-
{ label: "Docs", href: "/docs" }
|
|
206
|
-
]
|
|
207
|
-
}
|
|
208
|
-
];
|
|
209
|
-
var legalLinks = [
|
|
210
|
-
{ label: "Privacy", href: "/privacy" },
|
|
211
|
-
{ label: "Terms", href: "/terms" },
|
|
212
|
-
{ label: "Contact", href: "/contact" }
|
|
213
|
-
];
|
|
214
|
-
function BrandLandingPage() {
|
|
215
|
-
return /* @__PURE__ */ jsxs("div", { className: "min-h-dvh overflow-hidden bg-background text-foreground", children: [
|
|
216
|
-
/* @__PURE__ */ jsx("header", { className: "relative z-10 pt-4", children: /* @__PURE__ */ jsx(Region, { max: "wide", children: /* @__PURE__ */ jsxs(
|
|
217
|
-
"nav",
|
|
218
|
-
{
|
|
219
|
-
"aria-label": "Main",
|
|
220
|
-
className: "flex min-h-15 items-center justify-between gap-3 rounded-full border border-border bg-card/90 px-3 py-2 shadow-sm backdrop-blur",
|
|
221
|
-
children: [
|
|
222
|
-
/* @__PURE__ */ jsxs("a", { href: "/", className: "flex items-center gap-2 rounded-full px-2 motion-interactive motion-press", children: [
|
|
223
|
-
/* @__PURE__ */ jsx("span", { className: "grid size-8 place-items-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsx(Icon, { name: "sparkles", className: "text-sm" }) }),
|
|
224
|
-
/* @__PURE__ */ jsx("span", { className: "text-title-lg", children: "Cove" })
|
|
225
|
-
] }),
|
|
226
|
-
/* @__PURE__ */ jsx("div", { className: "hidden lg:block", children: /* @__PURE__ */ jsx(Row, { gap: 1, children: navItems.map((item) => /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: item.href }), children: item.label }, item.label)) }) }),
|
|
227
|
-
/* @__PURE__ */ jsxs(Row, { gap: 1, children: [
|
|
228
|
-
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", className: "max-sm:hidden", render: /* @__PURE__ */ jsx("a", { href: "/login" }), children: "Sign in" }),
|
|
229
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/demo" }), children: "Request access" })
|
|
230
|
-
] })
|
|
231
|
-
]
|
|
232
|
-
}
|
|
233
|
-
) }) }),
|
|
234
|
-
/* @__PURE__ */ jsxs("main", { children: [
|
|
235
|
-
/* @__PURE__ */ jsx(Region, { as: "section", max: "wide", className: "pb-20 pt-14 sm:pt-20 lg:pb-28", children: /* @__PURE__ */ jsxs("div", { className: "grid items-center gap-12 lg:grid-cols-[1fr_1.1fr] lg:gap-10", children: [
|
|
236
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 3, className: "max-w-2xl", children: [
|
|
237
|
-
/* @__PURE__ */ jsx(Badge, { appearance: "outline", icon: /* @__PURE__ */ jsx(Icon, { name: "lock", className: "text-sm" }), children: "Private operations, made legible" }),
|
|
238
|
-
/* @__PURE__ */ jsx(
|
|
239
|
-
TextReveal,
|
|
240
|
-
{
|
|
241
|
-
as: "h1",
|
|
242
|
-
text: "A calmer command center for high-trust work",
|
|
243
|
-
className: "text-balance text-display-lg sm:text-display-2xl",
|
|
244
|
-
once: true
|
|
245
|
-
}
|
|
246
|
-
),
|
|
247
|
-
/* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-body-lg text-muted-foreground", children: "Cove gives teams a polished front door and a serious operating surface: approvals, signals, records, and handoffs in one understandable flow." }),
|
|
248
|
-
/* @__PURE__ */ jsxs(Row, { gap: 2, wrap: true, className: "pt-3", children: [
|
|
249
|
-
/* @__PURE__ */ jsx(Button, { size: "cta", render: /* @__PURE__ */ jsx("a", { href: "/demo" }), children: "Book a walkthrough" }),
|
|
250
|
-
/* @__PURE__ */ jsx(Button, { size: "cta", variant: "outline", render: /* @__PURE__ */ jsx("a", { href: "#platform" }), children: "Explore the platform" })
|
|
251
|
-
] })
|
|
252
|
-
] }),
|
|
253
|
-
/* @__PURE__ */ jsx("div", { className: "dark", "aria-hidden": true, inert: true, children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-xl border border-border-floating bg-popover p-3 text-popover-foreground shadow-xl", children: /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/3 border border-border bg-card", children: [
|
|
254
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-4 py-3", children: [
|
|
255
|
-
/* @__PURE__ */ jsxs(Row, { gap: 1, children: [
|
|
256
|
-
/* @__PURE__ */ jsx(StatusDot, { tone: "warning" }),
|
|
257
|
-
/* @__PURE__ */ jsx(StatusDot, { tone: "edition" }),
|
|
258
|
-
/* @__PURE__ */ jsx(StatusDot, { tone: "success" })
|
|
259
|
-
] }),
|
|
260
|
-
/* @__PURE__ */ jsx("span", { className: "text-label-sm text-muted-foreground", children: "Live workspace" })
|
|
261
|
-
] }),
|
|
262
|
-
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 p-3 md:grid-cols-[0.7fr_1.3fr]", children: [
|
|
263
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-3 max-md:hidden", children: [
|
|
264
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-border bg-background p-4", children: [
|
|
265
|
-
/* @__PURE__ */ jsx("p", { className: "text-label-sm text-muted-foreground", children: "Spaces" }),
|
|
266
|
-
/* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: spaces.map((space) => /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/10 bg-card p-3", children: [
|
|
267
|
-
/* @__PURE__ */ jsxs(Row, { justify: "between", children: [
|
|
268
|
-
/* @__PURE__ */ jsx("span", { className: "text-title-sm", children: space.name }),
|
|
269
|
-
/* @__PURE__ */ jsx("span", { className: "text-mono-sm", children: space.value })
|
|
270
|
-
] }),
|
|
271
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-body-xs text-muted-foreground", children: space.label })
|
|
272
|
-
] }, space.name)) })
|
|
273
|
-
] }),
|
|
274
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-border bg-background p-4", children: [
|
|
275
|
-
/* @__PURE__ */ jsxs(Row, { justify: "between", children: [
|
|
276
|
-
/* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Route status" }),
|
|
277
|
-
/* @__PURE__ */ jsx(Badge, { tone: "success", size: "sm", dot: true, children: "Healthy" })
|
|
278
|
-
] }),
|
|
279
|
-
/* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: routes.map((route) => /* @__PURE__ */ jsxs(Row, { justify: "between", className: "rounded-full bg-card px-3 py-2", children: [
|
|
280
|
-
/* @__PURE__ */ jsxs(Row, { gap: 1, children: [
|
|
281
|
-
/* @__PURE__ */ jsx(StatusDot, { tone: route.tone }),
|
|
282
|
-
/* @__PURE__ */ jsx("span", { className: "text-body-sm", children: route.label })
|
|
283
|
-
] }),
|
|
284
|
-
/* @__PURE__ */ jsx("span", { className: "text-label-sm text-muted-foreground", children: route.state })
|
|
285
|
-
] }, route.label)) })
|
|
286
|
-
] })
|
|
287
|
-
] }),
|
|
288
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-border bg-background p-4", children: [
|
|
289
|
-
/* @__PURE__ */ jsxs(Row, { justify: "between", className: "border-b border-border pb-4", children: [
|
|
290
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
291
|
-
/* @__PURE__ */ jsx("p", { className: "text-label-sm text-muted-foreground", children: "Signal map" }),
|
|
292
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-heading-4 sm:text-heading-3", children: "Every approval path in view" })
|
|
293
|
-
] }),
|
|
294
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", children: "Review" })
|
|
295
|
-
] }),
|
|
296
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-5 grid gap-4 sm:mt-6 sm:gap-5", children: [
|
|
297
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-9 items-end gap-1.5 rounded-concentric-xl/10 bg-card p-4 sm:gap-2", children: bars.map((bar) => /* @__PURE__ */ jsx("div", { className: "flex h-36 items-end sm:h-56", children: /* @__PURE__ */ jsx(
|
|
298
|
-
"div",
|
|
299
|
-
{
|
|
300
|
-
className: `w-full rounded-full ${bar.current ? "bg-success" : "bg-secondary"}`,
|
|
301
|
-
style: { height: bar.height }
|
|
302
|
-
}
|
|
303
|
-
) }, bar.week)) }),
|
|
304
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2", children: sceneMetrics.map((metric) => /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/10 border border-border bg-card p-3", children: [
|
|
305
|
-
/* @__PURE__ */ jsx("p", { className: "truncate text-label-sm text-muted-foreground", children: metric.label }),
|
|
306
|
-
/* @__PURE__ */ jsx("p", { className: "mt-2 text-heading-4 sm:text-heading-3", children: metric.value }),
|
|
307
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-body-xs text-muted-foreground max-sm:hidden", children: metric.detail })
|
|
308
|
-
] }, metric.label)) })
|
|
309
|
-
] })
|
|
310
|
-
] })
|
|
311
|
-
] })
|
|
312
|
-
] }) }) })
|
|
313
|
-
] }) }),
|
|
314
|
-
/* @__PURE__ */ jsx("section", { id: "platform", "aria-labelledby": "brand-platform-title", className: "border-y border-border bg-muted/40 py-20 lg:py-28", children: /* @__PURE__ */ jsx(Region, { max: "wide", children: /* @__PURE__ */ jsxs(Stack, { gap: 6, children: [
|
|
315
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 2, className: "max-w-2xl", children: [
|
|
316
|
-
/* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Why Cove" }),
|
|
317
|
-
/* @__PURE__ */ jsx("h2", { id: "brand-platform-title", className: "text-balance text-heading-1", children: "One private workspace for the decisions that matter." }),
|
|
318
|
-
/* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-body-lg text-muted-foreground", children: "Most operations work lives in threads, spreadsheets, and memory. Cove gives it a home: every request routed, every signal visible, every outcome on the record." })
|
|
319
|
-
] }),
|
|
320
|
-
/* @__PURE__ */ jsx(Reveal, { children: /* @__PURE__ */ jsx(Grid, { minItemWidth: "calc(var(--spacing) * 64)", gap: 2, children: values.map((value) => /* @__PURE__ */ jsx(Card, { render: /* @__PURE__ */ jsx("article", {}), className: "h-full", children: /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-2", children: [
|
|
321
|
-
/* @__PURE__ */ jsx("span", { className: "mb-4 grid size-11 place-items-center rounded-md border border-border sable-well", children: /* @__PURE__ */ jsx(Icon, { name: value.icon, className: "text-xl" }) }),
|
|
322
|
-
/* @__PURE__ */ jsx("h3", { className: "text-title-lg", children: value.title }),
|
|
323
|
-
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: value.body })
|
|
324
|
-
] }) }, value.title)) }) })
|
|
325
|
-
] }) }) }),
|
|
326
|
-
/* @__PURE__ */ jsx(Region, { as: "section", id: "how-it-works", "aria-labelledby": "brand-steps-title", max: "wide", className: "py-20 lg:py-28", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-12 lg:grid-cols-[1fr_2fr] lg:gap-16", children: [
|
|
327
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 2, className: "max-w-md", children: [
|
|
328
|
-
/* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "How it works" }),
|
|
329
|
-
/* @__PURE__ */ jsx("h2", { id: "brand-steps-title", className: "text-balance text-heading-1", children: "From request to sealed record." }),
|
|
330
|
-
/* @__PURE__ */ jsx("p", { className: "text-body-md text-muted-foreground", children: "Teams are usually live within a week. Cove adapts to the routes you already run instead of asking you to start over." }),
|
|
331
|
-
/* @__PURE__ */ jsx("div", { className: "pt-3", children: /* @__PURE__ */ jsx(Button, { variant: "outline", iconEnd: /* @__PURE__ */ jsx(Icon, { name: "arrow-right" }), render: /* @__PURE__ */ jsx("a", { href: "/guides/rollout" }), children: "Read the rollout guide" }) })
|
|
332
|
-
] }),
|
|
333
|
-
/* @__PURE__ */ jsx("ol", { className: "grid gap-10 sm:grid-cols-3 sm:gap-6", children: steps.map((step) => /* @__PURE__ */ jsxs("li", { className: "flex flex-col gap-2 border-t border-border-strong pt-6", children: [
|
|
334
|
-
/* @__PURE__ */ jsx("span", { className: "text-mono-sm text-muted-foreground", children: step.number }),
|
|
335
|
-
/* @__PURE__ */ jsx("h3", { className: "mt-3 text-title-lg sm:mt-6", children: step.title }),
|
|
336
|
-
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: step.body })
|
|
337
|
-
] }, step.number)) })
|
|
338
|
-
] }) }),
|
|
339
|
-
/* @__PURE__ */ jsx(Region, { as: "section", id: "customers", "aria-label": "Customer story", max: "wide", className: "pb-20 lg:pb-28", children: /* @__PURE__ */ jsx(Card, { render: /* @__PURE__ */ jsx("figure", {}), children: /* @__PURE__ */ jsxs("div", { className: "grid lg:grid-cols-[1.5fr_1fr]", children: [
|
|
340
|
-
/* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col justify-between gap-10 sm:p-10", children: [
|
|
341
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
342
|
-
/* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Customer story" }),
|
|
343
|
-
/* @__PURE__ */ jsx("blockquote", { className: "text-balance text-heading-3 sm:text-heading-2", children: "\u201CWe used to chase approvals across four tools. Now every request has an owner, a deadline, and a record, and our auditors stopped asking for screenshots.\u201D" })
|
|
344
|
-
] }),
|
|
345
|
-
/* @__PURE__ */ jsxs("figcaption", { className: "flex items-center gap-3", children: [
|
|
346
|
-
/* @__PURE__ */ jsx(Avatar, { variant: "art", size: "lg", fallback: "MO" }),
|
|
347
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
348
|
-
/* @__PURE__ */ jsx("p", { className: "text-title-sm", children: "Maya Okafor" }),
|
|
349
|
-
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: "Head of operations, Fielddesk" })
|
|
350
|
-
] })
|
|
351
|
-
] })
|
|
352
|
-
] }),
|
|
353
|
-
/* @__PURE__ */ jsx("div", { className: "border-border max-lg:border-t lg:border-s", children: /* @__PURE__ */ jsx(CardContent, { className: "flex h-full flex-col justify-center gap-6 sm:p-10", children: outcomes.map((outcome, index) => /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
354
|
-
index > 0 && /* @__PURE__ */ jsx(Separator, {}),
|
|
355
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
356
|
-
/* @__PURE__ */ jsx("p", { className: "text-heading-1 sm:text-display-lg", children: outcome.value }),
|
|
357
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-body-sm text-muted-foreground", children: outcome.label })
|
|
358
|
-
] })
|
|
359
|
-
] }, outcome.label)) }) })
|
|
360
|
-
] }) }) }),
|
|
361
|
-
/* @__PURE__ */ jsx(Region, { as: "section", "aria-labelledby": "brand-cta-title", max: "wide", className: "pb-20 lg:pb-28", children: /* @__PURE__ */ jsx("div", { className: "dark", children: /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-border bg-card px-6 py-12 text-card-foreground sm:px-12 sm:py-16", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-8 lg:flex-row lg:items-end lg:justify-between", children: [
|
|
362
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 3, className: "max-w-2xl", children: [
|
|
363
|
-
/* @__PURE__ */ jsx("h2", { id: "brand-cta-title", className: "text-balance text-heading-1", children: "See your own approval paths in Cove." }),
|
|
364
|
-
/* @__PURE__ */ jsx("p", { className: "text-body-lg text-muted-foreground", children: "A 30-minute walkthrough with your routes, your roles, and your records. No slides." })
|
|
365
|
-
] }),
|
|
366
|
-
/* @__PURE__ */ jsxs(Row, { gap: 2, wrap: true, children: [
|
|
367
|
-
/* @__PURE__ */ jsx(Button, { size: "cta", render: /* @__PURE__ */ jsx("a", { href: "/demo" }), children: "Book a walkthrough" }),
|
|
368
|
-
/* @__PURE__ */ jsx(Button, { size: "cta", variant: "ghost", render: /* @__PURE__ */ jsx("a", { href: "/pricing" }), children: "View pricing" })
|
|
369
|
-
] })
|
|
370
|
-
] }) }) }) })
|
|
371
|
-
] }),
|
|
372
|
-
/* @__PURE__ */ jsx("footer", { className: "border-t border-border bg-muted/35", children: /* @__PURE__ */ jsxs(Region, { max: "wide", className: "py-12 sm:py-16", children: [
|
|
373
|
-
/* @__PURE__ */ jsxs("div", { className: "grid gap-10 lg:grid-cols-[1fr_1.35fr_1fr] lg:gap-8", children: [
|
|
374
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 4, className: "max-w-sm", children: [
|
|
375
|
-
/* @__PURE__ */ jsxs("a", { href: "/", className: "flex w-fit items-center gap-2 rounded-full motion-interactive motion-press", children: [
|
|
376
|
-
/* @__PURE__ */ jsx("span", { className: "grid size-9 place-items-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsx(Icon, { name: "sparkles", className: "text-sm" }) }),
|
|
377
|
-
/* @__PURE__ */ jsx("span", { className: "text-title-lg", children: "Cove" })
|
|
378
|
-
] }),
|
|
379
|
-
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: "A calm operating surface for teams that need trust, speed, and context without the noise." }),
|
|
380
|
-
/* @__PURE__ */ jsx(Badge, { appearance: "plain", tone: "success", dot: true, children: "All systems normal" })
|
|
381
|
-
] }),
|
|
382
|
-
/* @__PURE__ */ jsx("nav", { "aria-label": "Footer", className: "grid grid-cols-3 gap-6", children: footerGroups.map((group) => /* @__PURE__ */ jsxs("div", { children: [
|
|
383
|
-
/* @__PURE__ */ jsx("h2", { className: "text-label-md", children: group.title }),
|
|
384
|
-
/* @__PURE__ */ jsx("ul", { className: "mt-3 space-y-1", children: group.links.map((link) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
385
|
-
"a",
|
|
386
|
-
{
|
|
387
|
-
href: link.href,
|
|
388
|
-
className: "flex min-h-9 items-center text-body-sm text-muted-foreground motion-interactive hover:text-foreground",
|
|
389
|
-
children: link.label
|
|
390
|
-
}
|
|
391
|
-
) }, link.label)) })
|
|
392
|
-
] }, group.title)) }),
|
|
393
|
-
/* @__PURE__ */ jsxs(Card, { className: "h-fit", children: [
|
|
394
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
395
|
-
/* @__PURE__ */ jsx(CardTitle, { children: /* @__PURE__ */ jsx("h2", { children: "The Cove digest" }) }),
|
|
396
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "One note a month on trusted workflows and product releases." })
|
|
397
|
-
] }),
|
|
398
|
-
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs("form", { action: "/newsletter", className: "grid gap-2 sm:grid-cols-[1fr_auto]", children: [
|
|
399
|
-
/* @__PURE__ */ jsx("label", { htmlFor: "brand-footer-email", className: "sr-only", children: "Work email" }),
|
|
400
|
-
/* @__PURE__ */ jsx(
|
|
401
|
-
Input,
|
|
402
|
-
{
|
|
403
|
-
id: "brand-footer-email",
|
|
404
|
-
name: "email",
|
|
405
|
-
type: "email",
|
|
406
|
-
autoComplete: "email",
|
|
407
|
-
placeholder: "you@company.com"
|
|
408
|
-
}
|
|
409
|
-
),
|
|
410
|
-
/* @__PURE__ */ jsx(Button, { type: "submit", className: "w-full sm:w-auto", children: "Subscribe" })
|
|
411
|
-
] }) })
|
|
412
|
-
] })
|
|
413
|
-
] }),
|
|
414
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-12 flex flex-col gap-3 border-t border-border pt-6 text-caption text-muted-foreground sm:flex-row sm:items-center sm:justify-between", children: [
|
|
415
|
-
/* @__PURE__ */ jsx("p", { children: "\xA9 2026 Cove. All rights reserved." }),
|
|
416
|
-
/* @__PURE__ */ jsx(Row, { gap: 4, wrap: true, children: legalLinks.map((link) => /* @__PURE__ */ jsx(
|
|
417
|
-
"a",
|
|
418
|
-
{
|
|
419
|
-
href: link.href,
|
|
420
|
-
className: "flex min-h-9 min-w-9 items-center motion-interactive hover:text-foreground",
|
|
421
|
-
children: link.label
|
|
422
|
-
},
|
|
423
|
-
link.label
|
|
424
|
-
)) })
|
|
425
|
-
] })
|
|
426
|
-
] }) })
|
|
427
|
-
] });
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// src/patterns/brand-landing-page.meta.ts
|
|
431
|
-
var brandLandingPageMeta = {
|
|
432
|
-
name: "BrandLandingPage",
|
|
433
|
-
summary: "High-fidelity brand/product landing page: pill navigation, editorial hero type, a dark product scene, a value section, a how-it-works sequence, a customer story with outcomes, a closing CTA band, and a complete branded footer.",
|
|
434
|
-
intent: "Use this for a polished product marketing route that still needs to show a credible app surface. It is warmer and more tactile than the canonical LandingPage, but avoids trend-driven neon and generic hype cues.",
|
|
435
|
-
notes: [
|
|
436
|
-
"The story runs promise \u2192 product \u2192 value \u2192 process \u2192 proof \u2192 ask: hero, product scene, 'Why Cove' value cards, a three-step how-it-works list, a customer story with outcome figures, then one closing CTA. Each section adds new evidence; no number is repeated from the product scene.",
|
|
437
|
-
"One display headline owns the first viewport; supporting copy stays in body-lg at prose measure. Section headings are heading-1 under a `text-overline` eyebrow.",
|
|
438
|
-
"The nav spends one compact pill surface, with a clear brand signal and two primary paths: sign in and request access.",
|
|
439
|
-
"The product scene and the closing CTA band are dark islands: a `dark` class on the wrapper re-scopes every semantic token, so surfaces, muted ink, Badge tones and Buttons take the theme's own dark values in either page mode. Never fake a dark panel with overlay ink at reduced opacity \u2014 status tones tuned for a light page vanish on it.",
|
|
440
|
-
"The scene is a picture of the product: `aria-hidden` + `inert` keep its sample button and figures out of the tab order and the outline. On narrow screens it drops the side column and keeps the signal map, so it scales instead of stacking into a second page.",
|
|
441
|
-
"The chart keeps past weeks on the quiet `secondary` fill and gives the current week the one accent, so the signal reads at a glance.",
|
|
442
|
-
"Value cards are real Cards with a neutral `sable-well` icon tile; the customer story is a Card rendered as a figure, with outcome figures split by Separator. The scene's nested panels take `rounded-concentric-xl/<inset>` from the shell's radius, so the corners stay parallel at any radius preset.",
|
|
443
|
-
"The footer closes the page with brand reassurance, a plain status Badge, a three-column sitemap (three columns at every width), a digest signup, and legal links.",
|
|
444
|
-
"All figures, names and the testimonial are sample content; replace them with real, verifiable claims."
|
|
445
|
-
],
|
|
446
|
-
a11y: [
|
|
447
|
-
"The brand nav is a labeled nav landmark with real links and visible text.",
|
|
448
|
-
"Each content section is a labelled region (by its h2, or aria-label for the customer story); the decorative product scene is hidden from assistive tech and inert.",
|
|
449
|
-
"The how-it-works steps are an ordered list; the testimonial is a figure with a blockquote and a figcaption naming the speaker.",
|
|
450
|
-
"The footer is a real footer landmark with a labeled footer nav, visible link text, and a labeled email field.",
|
|
451
|
-
"TextReveal preserves the full headline as one accessible label and honors reduced-motion policy; Reveal shows the value cards immediately when motion is off.",
|
|
452
|
-
"Status dots and the status Badge are paired with visible text labels so color is never the only signal."
|
|
453
|
-
]
|
|
454
|
-
};
|
|
455
|
-
var pending = true;
|
|
456
|
-
var streamed = "Yes, appends are already safe: your layout effect measures before paint. The jump is on the prepend path, which reads scrollHeight after the new rows";
|
|
457
|
-
var turns = [
|
|
458
|
-
{
|
|
459
|
-
id: "t_01",
|
|
460
|
-
mine: true,
|
|
461
|
-
body: "Our chat scroll jumps every time older messages load. What are we doing wrong?"
|
|
462
|
-
},
|
|
463
|
-
{
|
|
464
|
-
id: "t_02",
|
|
465
|
-
mine: false,
|
|
466
|
-
step: "Read message-scroller.md",
|
|
467
|
-
body: "Almost always the anchor. If you preserve the scroll position by measuring the container, prepending history moves everything under the reader."
|
|
468
|
-
},
|
|
469
|
-
{
|
|
470
|
-
id: "t_03",
|
|
471
|
-
mine: false,
|
|
472
|
-
body: "Anchor on the oldest rendered message instead and add back however far it moved. Appends leave it alone, so the same rule covers both."
|
|
473
|
-
}
|
|
474
|
-
];
|
|
475
|
-
function ChatPanel() {
|
|
476
|
-
const assistant = /* @__PURE__ */ jsx(
|
|
477
|
-
Avatar,
|
|
478
|
-
{
|
|
479
|
-
variant: "art",
|
|
480
|
-
size: "sm",
|
|
481
|
-
alt: "Assistant",
|
|
482
|
-
fallback: /* @__PURE__ */ jsx(Icon, { name: "sparkles", className: "text-base" })
|
|
483
|
-
}
|
|
484
|
-
);
|
|
485
|
-
return /* @__PURE__ */ jsx(Page, { as: "main", max: "feed", gap: 0, className: "py-6", children: /* @__PURE__ */ jsxs(
|
|
486
|
-
Card,
|
|
487
|
-
{
|
|
488
|
-
render: /* @__PURE__ */ jsx("section", { "aria-labelledby": "chat-title" }),
|
|
489
|
-
className: "flex h-[calc(100dvh_-_--spacing(12))] flex-col overflow-hidden",
|
|
490
|
-
children: [
|
|
491
|
-
/* @__PURE__ */ jsxs(CardHeader, { className: "border-b border-border pb-(--card-p)", children: [
|
|
492
|
-
/* @__PURE__ */ jsx(CardTitle, { children: /* @__PURE__ */ jsx("h1", { id: "chat-title", children: "Scroll behaviour" }) }),
|
|
493
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Assistant \xB7 answers from your team's docs" }),
|
|
494
|
-
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", children: [
|
|
495
|
-
/* @__PURE__ */ jsx(Icon, { name: "edit" }),
|
|
496
|
-
"New chat"
|
|
497
|
-
] }) })
|
|
498
|
-
] }),
|
|
499
|
-
/* @__PURE__ */ jsxs(MessageScroller, { className: "min-h-0 flex-1 p-(--card-p)", children: [
|
|
500
|
-
/* @__PURE__ */ jsx(Marker, { variant: "separator", children: "Today" }),
|
|
501
|
-
turns.map((turn, i) => {
|
|
502
|
-
const continues = i > 0 && turns[i - 1].mine === turn.mine;
|
|
503
|
-
return /* @__PURE__ */ jsx(
|
|
504
|
-
Message,
|
|
505
|
-
{
|
|
506
|
-
messageId: turn.id,
|
|
507
|
-
align: turn.mine ? "end" : "start",
|
|
508
|
-
grouped: continues,
|
|
509
|
-
avatar: turn.mine ? void 0 : assistant,
|
|
510
|
-
header: turn.step ? /* @__PURE__ */ jsx(Marker, { variant: "note", icon: "book", className: "justify-start", children: turn.step }) : void 0,
|
|
511
|
-
children: /* @__PURE__ */ jsx(
|
|
512
|
-
Bubble,
|
|
513
|
-
{
|
|
514
|
-
variant: turn.mine ? "primary" : "muted",
|
|
515
|
-
align: turn.mine ? "end" : "start",
|
|
516
|
-
tail: !continues,
|
|
517
|
-
children: turn.body
|
|
518
|
-
}
|
|
519
|
-
)
|
|
520
|
-
},
|
|
521
|
-
turn.id
|
|
522
|
-
);
|
|
523
|
-
}),
|
|
524
|
-
/* @__PURE__ */ jsx(
|
|
525
|
-
Message,
|
|
526
|
-
{
|
|
527
|
-
messageId: "t_04",
|
|
528
|
-
grouped: true,
|
|
529
|
-
avatar: assistant,
|
|
530
|
-
footer: /* @__PURE__ */ jsxs(Toolbar, { "aria-label": "Answer actions", children: [
|
|
531
|
-
/* @__PURE__ */ jsx(SimpleTooltip, { content: "Copy answer", children: /* @__PURE__ */ jsx(ToolbarButton, { size: "icon-sm", "aria-label": "Copy answer", children: /* @__PURE__ */ jsx(Icon, { name: "copy" }) }) }),
|
|
532
|
-
/* @__PURE__ */ jsx(SimpleTooltip, { content: "Regenerate", children: /* @__PURE__ */ jsx(ToolbarButton, { size: "icon-sm", "aria-label": "Regenerate answer", children: /* @__PURE__ */ jsx(Icon, { name: "refresh" }) }) })
|
|
533
|
-
] }),
|
|
534
|
-
children: /* @__PURE__ */ jsxs(Bubble, { variant: "plain", className: "max-w-full", children: [
|
|
535
|
-
/* @__PURE__ */ jsx("p", { className: "text-body-md text-foreground", children: "Here is the shape of it:" }),
|
|
536
|
-
/* @__PURE__ */ jsx(
|
|
537
|
-
CodeBlock,
|
|
538
|
-
{
|
|
539
|
-
code: "const shift = anchor.offsetTop - lastTop\nif (shift !== 0) viewport.scrollTop += shift",
|
|
540
|
-
filename: "preserve.ts"
|
|
541
|
-
}
|
|
542
|
-
)
|
|
543
|
-
] })
|
|
544
|
-
}
|
|
545
|
-
),
|
|
546
|
-
/* @__PURE__ */ jsxs(Message, { messageId: "t_05", align: "end", children: [
|
|
547
|
-
/* @__PURE__ */ jsx(Bubble, { variant: "primary", align: "end", children: "Here's ours. Does the same fix cover new messages arriving?" }),
|
|
548
|
-
/* @__PURE__ */ jsx(
|
|
549
|
-
Attachment,
|
|
550
|
-
{
|
|
551
|
-
name: "scroller.tsx",
|
|
552
|
-
meta: "4.2 KB \xB7 TSX",
|
|
553
|
-
onOpen: () => void 0
|
|
554
|
-
}
|
|
555
|
-
)
|
|
556
|
-
] }),
|
|
557
|
-
/* @__PURE__ */ jsx(
|
|
558
|
-
Message,
|
|
559
|
-
{
|
|
560
|
-
messageId: "t_06",
|
|
561
|
-
avatar: assistant,
|
|
562
|
-
header: /* @__PURE__ */ jsx(Marker, { variant: "note", icon: "file", className: "justify-start", children: "Read scroller.tsx" }),
|
|
563
|
-
children: /* @__PURE__ */ jsx(Bubble, { variant: "plain", children: /* @__PURE__ */ jsx(StreamingText, { text: streamed, streaming: pending }) })
|
|
564
|
-
}
|
|
565
|
-
)
|
|
566
|
-
] }),
|
|
567
|
-
/* @__PURE__ */ jsx(CardFooter, { className: "border-t border-border pt-(--card-p)", children: /* @__PURE__ */ jsx(
|
|
568
|
-
ChatComposer,
|
|
569
|
-
{
|
|
570
|
-
className: "flex-1",
|
|
571
|
-
placeholder: "Ask a follow-up\u2026",
|
|
572
|
-
busy: pending,
|
|
573
|
-
onStop: () => void 0,
|
|
574
|
-
stopLabel: "Stop answering",
|
|
575
|
-
onAttach: () => void 0,
|
|
576
|
-
footer: "Enter to send \xB7 Shift+Enter for a new line",
|
|
577
|
-
actions: /* @__PURE__ */ jsxs(
|
|
578
|
-
SettingsMenu,
|
|
579
|
-
{
|
|
580
|
-
variant: "subtle",
|
|
581
|
-
size: "xs",
|
|
582
|
-
label: "Change model and effort",
|
|
583
|
-
summary: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
584
|
-
/* @__PURE__ */ jsx("span", { className: "text-foreground", children: "5.6 Sol" }),
|
|
585
|
-
" ",
|
|
586
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "High" })
|
|
587
|
-
] }),
|
|
588
|
-
children: [
|
|
589
|
-
/* @__PURE__ */ jsx(SettingsRow, { label: "Model", value: "5.6 Sol", children: /* @__PURE__ */ jsxs(MenuRadioGroup, { defaultValue: "sol", children: [
|
|
590
|
-
/* @__PURE__ */ jsx(MenuRadioItem, { value: "sol", children: "5.6 Sol" }),
|
|
591
|
-
/* @__PURE__ */ jsx(MenuRadioItem, { value: "lite", children: "5.6 Lite" })
|
|
592
|
-
] }) }),
|
|
593
|
-
/* @__PURE__ */ jsx(SettingsRow, { label: "Effort", value: "High", children: /* @__PURE__ */ jsxs(MenuRadioGroup, { defaultValue: "high", children: [
|
|
594
|
-
/* @__PURE__ */ jsx(MenuRadioItem, { value: "low", children: "Low" }),
|
|
595
|
-
/* @__PURE__ */ jsx(MenuRadioItem, { value: "standard", children: "Standard" }),
|
|
596
|
-
/* @__PURE__ */ jsx(MenuRadioItem, { value: "high", children: "High" })
|
|
597
|
-
] }) }),
|
|
598
|
-
/* @__PURE__ */ jsx(SettingsRow, { label: "Speed", value: "Standard", children: /* @__PURE__ */ jsxs(MenuRadioGroup, { defaultValue: "standard", children: [
|
|
599
|
-
/* @__PURE__ */ jsx(MenuRadioItem, { value: "standard", children: "Standard" }),
|
|
600
|
-
/* @__PURE__ */ jsx(MenuRadioItem, { value: "priority", children: "Priority" })
|
|
601
|
-
] }) })
|
|
602
|
-
]
|
|
603
|
-
}
|
|
604
|
-
)
|
|
605
|
-
}
|
|
606
|
-
) })
|
|
607
|
-
]
|
|
608
|
-
}
|
|
609
|
-
) });
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// src/patterns/chat-panel.meta.ts
|
|
613
|
-
var chatPanelMeta = {
|
|
614
|
-
name: "ChatPanel",
|
|
615
|
-
summary: "A full-height conversation surface: titled header, scrolling transcript, and a composer pinned to the bottom \u2014 the assembled shape of an AI assistant, a support thread, or a DM.",
|
|
616
|
-
intent: "Reach for this for any conversation that owns its screen \u2014 an assistant, a support inbox thread, a DM. Not for a chat docked beside other work (compose the same three parts inside a Sheet or a PanelGroup instead), and not for a comment thread under an article, which is a FeedPage concern.",
|
|
617
|
-
notes: [
|
|
618
|
-
"The panel is a fixed-height column, not a growing page. `h-[calc(100dvh_-_--spacing(12))]` on the Card (the viewport minus the Page's py-6, in spacing units so density moves both together \u2014 a fixed `3rem` drifts the moment density moves the padding) and `flex-1 min-h-0` on the MessageScroller are what make the transcript scroll instead of the document \u2014 the single most common thing to get wrong here, and it fails by pushing the composer off the bottom of the screen.",
|
|
619
|
-
"The panel is a Card, not a hand-built bordered section: CardHeader (title, description, CardAction), the MessageScroller and a CardFooter all inset by the card's own `--card-p`, so the three regions share one edge at any card density.",
|
|
620
|
-
"`dvh` rather than `vh`: on mobile the URL bar collapses, and a `vh`-sized panel puts the composer under it exactly when someone is trying to type.",
|
|
621
|
-
"Region cap is `feed` (640px) \u2014 a conversation is a reading column, and the Bubble measure cap is tuned to the same assumption.",
|
|
622
|
-
"The composer sits outside the scroll region, in a bordered CardFooter, so it stays put while the transcript moves.",
|
|
623
|
-
"Grouping is derived from the data (`turns[i - 1].mine === turn.mine`) rather than hand-set, and the tail is dropped on continuations \u2014 that pairing is what makes a run read as one block.",
|
|
624
|
-
'What the assistant did before answering \u2014 a doc it read, a tool it ran \u2014 is a `Marker variant="note"` in that message\'s `header`, so it reads as part of the turn rather than as an event between turns.',
|
|
625
|
-
'The assistant\'s long-form answer uses `variant="plain"` and continues the same grouped run. A tinted box around a code block reads as a quote rather than as a turn. A plain Bubble sizes to its content with no measure cap, so it takes `max-w-full` here: bounded by the column, a wide code line scrolls inside the CodeBlock instead of pushing the transcript sideways on a phone.',
|
|
626
|
-
"A settled answer carries its actions (copy, regenerate) in the Message `footer` as a `Toolbar` of icon `ToolbarButton`s with tooltips \u2014 one tab stop, arrow keys between them. The answer still streaming has none: there is nothing to copy or regenerate until it settles, and Stop lives in the composer.",
|
|
627
|
-
"One turn, one Message: the reader's question and the file it refers to stack inside a single end-aligned Message rather than being sent as two.",
|
|
628
|
-
'Assistant turns share one Avatar with a glyph fallback and `alt="Assistant"` \u2014 initials like "AI" read as a person, and a fallback-only avatar is otherwise unnamed.',
|
|
629
|
-
"The answer in flight is a `StreamingText` in a `plain` Bubble, and ONE `pending` flag drives both it and the composer's `busy`. They are the same wait reported at the two ends of the panel; letting them drift gives you a composer still saying 'sending' under an answer that already finished.",
|
|
630
|
-
"A spinner `Marker` (or `ThinkingOrb`) belongs BEFORE the first token and is replaced once tokens land, not kept alongside. Two indicators for one wait read as two things happening \u2014 which is also why `StreamingText` takes `caret={false}` when something adjacent already reports it.",
|
|
631
|
-
'MessageScroller needs no configuring for this: it re-follows on "a message grew" (ResizeObserver) as well as "a message arrived", so the newest token stays pinned while a reader who has scrolled up is left alone.',
|
|
632
|
-
"For a new thread, swap the transcript's children for an `<EmptyState>` inside the same MessageScroller \u2014 the panel, header and composer are unchanged, which is the point of keeping them separate."
|
|
633
|
-
],
|
|
634
|
-
a11y: [
|
|
635
|
-
"The panel header's title is the page's <h1> (an h1 inside CardTitle, which carries the type); the transcript is a scroll region with native overflow, so keyboard scrolling works unchanged.",
|
|
636
|
-
"The answer in flight carries `aria-busy` rather than sitting in a live region. That is deliberate: a live region around streamed text announces every token, which is unusable. Announce once when the turn settles, from whatever owns the turn \u2014 the incomplete state is still exposed, just not narrated.",
|
|
637
|
-
'The composer is a real <form> whose Enter handling skips IME composition, and every icon-only control in the panel carries an accessible label \u2014 the answer actions also carry a tooltip, and the stop control is named for what it stops (`stopLabel="Stop answering"`).'
|
|
638
|
-
]
|
|
639
|
-
};
|
|
640
|
-
var STATUS_TONE = { live: "success", scheduled: "info", "sold-out": "edition", closed: "neutral" };
|
|
641
|
-
var STATUS_LABEL = { live: "Live", scheduled: "Scheduled", "sold-out": "Sold out", closed: "Closed" };
|
|
642
|
-
var MIN = 60 * 1e3;
|
|
643
|
-
var HOUR = 60 * MIN;
|
|
644
|
-
var DAY = 24 * HOUR;
|
|
645
|
-
var usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 });
|
|
646
|
-
var usdCents = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
|
|
647
|
-
var shortDate = (offset) => new Date(Date.now() + offset).toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
648
|
-
var stats = [
|
|
649
|
-
{ label: "Revenue", value: "$18,240", delta: "+8.2%", up: true, note: "vs. $16,857" },
|
|
650
|
-
{ label: "Collects", value: "1,284", delta: "+3.1%", up: true, note: "182 first-time" },
|
|
651
|
-
{ label: "Collectors", value: "312", delta: "+12.4%", up: true, note: "Best month yet" },
|
|
652
|
-
{ label: "Sell-through", value: "64%", delta: "\u22121.8%", up: false, note: "6 open editions" }
|
|
653
|
-
];
|
|
654
|
-
var editions = [
|
|
655
|
-
{ id: "ed_01", title: "Gold Rush", status: "live", sold: 184, supply: 250, price: 48, released: -6 * DAY },
|
|
656
|
-
{ id: "ed_02", title: "Quiet Harbor", status: "sold-out", sold: 100, supply: 100, price: 120, released: -19 * DAY },
|
|
657
|
-
{ id: "ed_03", title: "Dusk, no. 4", status: "live", sold: 61, supply: 150, price: 64, released: -2 * DAY },
|
|
658
|
-
{ id: "ed_04", title: "Tidewater", status: "live", sold: 212, supply: 300, price: 36, released: -27 * DAY },
|
|
659
|
-
{ id: "ed_05", title: "Salt Study", status: "closed", sold: 38, supply: 75, price: 90, released: -44 * DAY },
|
|
660
|
-
{ id: "ed_06", title: "Paper Moon", status: "sold-out", sold: 50, supply: 50, price: 220, released: -58 * DAY },
|
|
661
|
-
{ id: "ed_07", title: "Night Signal", status: "scheduled", sold: 0, supply: 200, price: 56, released: 3 * DAY },
|
|
662
|
-
{ id: "ed_08", title: "Low Tide, Blue", status: "closed", sold: 72, supply: 120, price: 42, released: -71 * DAY },
|
|
663
|
-
{ id: "ed_09", title: "Ember Field", status: "sold-out", sold: 80, supply: 80, price: 140, released: -86 * DAY }
|
|
664
|
-
];
|
|
665
|
-
var editionCount = 24;
|
|
666
|
-
var payout = {
|
|
667
|
-
amount: 8214.6,
|
|
668
|
-
date: 5 * DAY,
|
|
669
|
-
lines: [
|
|
670
|
-
{ label: "Sales", value: 9127.33 },
|
|
671
|
-
{ label: "Platform fee (10%)", value: -912.73 },
|
|
672
|
-
{ label: "Deposits to", value: "Checking \u2022\u20224821" }
|
|
673
|
-
]
|
|
674
|
-
};
|
|
675
|
-
var collectors = [
|
|
676
|
-
{ id: "co_01", name: "Nina Simone", handle: "@nina", initials: "NS", pieces: 14, spent: 1460 },
|
|
677
|
-
{ id: "co_02", name: "Grace Hopper", handle: "@grace", initials: "GH", pieces: 11, spent: 1128 },
|
|
678
|
-
{ id: "co_03", name: "Frida Kahlo", handle: "@frida", initials: "FK", pieces: 9, spent: 942 },
|
|
679
|
-
{ id: "co_04", name: "Mary Shelley", handle: "@mary", initials: "MS", pieces: 6, spent: 588 }
|
|
680
|
-
];
|
|
681
|
-
var upcoming = [
|
|
682
|
-
{ id: "up_01", title: "Night Signal", detail: "Edition of 200 \xB7 $56", at: 3 * DAY },
|
|
683
|
-
{ id: "up_02", title: "Tidewater, second printing", detail: "Edition of 100 \xB7 $40", at: 12 * DAY },
|
|
684
|
-
{ id: "up_03", title: "Dusk, no. 5", detail: "Edition of 150 \xB7 $64", at: 26 * DAY },
|
|
685
|
-
{ id: "up_04", title: "Quiet Harbor, artist proofs", detail: "Edition of 10 \xB7 $480", at: 34 * DAY }
|
|
686
|
-
];
|
|
687
|
-
var activity = [
|
|
688
|
-
{ id: "ac_01", who: "Nina Simone", initials: "NS", what: "collected Gold Rush \xB7 185 of 250", when: 2 * MIN },
|
|
689
|
-
{ id: "ac_02", who: "Grace Hopper", initials: "GH", what: "collected Dusk, no. 4 \xB7 61 of 150", when: 26 * MIN },
|
|
690
|
-
{ id: "ac_03", who: "Frida Kahlo", initials: "FK", what: "tipped $20 on Tidewater", when: 1 * HOUR },
|
|
691
|
-
{ id: "ac_04", who: "Mary Shelley", initials: "MS", what: "started following you", when: 3 * HOUR },
|
|
692
|
-
{ id: "ac_05", who: "Nina Simone", initials: "NS", what: "set a reminder for Night Signal", when: 6 * HOUR }
|
|
693
|
-
];
|
|
694
|
-
function DashboardPage() {
|
|
695
|
-
return /* @__PURE__ */ jsx(
|
|
696
|
-
AppShell,
|
|
697
|
-
{
|
|
698
|
-
header: /* @__PURE__ */ jsx(
|
|
699
|
-
AppHeader,
|
|
700
|
-
{
|
|
701
|
-
title: /* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", children: [
|
|
702
|
-
/* @__PURE__ */ jsx(Icon, { name: "gem", variant: "solid", className: "text-xl" }),
|
|
703
|
-
/* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" })
|
|
704
|
-
] }),
|
|
705
|
-
actions: /* @__PURE__ */ jsx(Avatar, { variant: "art", size: "sm", fallback: "AL", alt: "Ada Lovelace" })
|
|
706
|
-
}
|
|
707
|
-
),
|
|
708
|
-
bottomNav: /* @__PURE__ */ jsxs(BottomNav, { "aria-label": "Primary", children: [
|
|
709
|
-
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "grid" }), label: "Overview", showLabel: true, active: true, render: /* @__PURE__ */ jsx("a", { href: "/" }) }),
|
|
710
|
-
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }), label: "Editions", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/editions" }) }),
|
|
711
|
-
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "user" }), label: "Collectors", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/collectors" }) }),
|
|
712
|
-
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "bell" }), label: "Activity", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/activity" }) }),
|
|
713
|
-
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "gear" }), label: "Settings", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/settings" }) })
|
|
714
|
-
] }),
|
|
715
|
-
sidebar: /* @__PURE__ */ jsxs(Sidebar, { children: [
|
|
716
|
-
/* @__PURE__ */ jsx(SidebarHeader, { children: /* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", className: "px-3", children: [
|
|
717
|
-
/* @__PURE__ */ jsx(Icon, { name: "gem", variant: "solid", className: "text-xl" }),
|
|
718
|
-
/* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" })
|
|
719
|
-
] }) }),
|
|
720
|
-
/* @__PURE__ */ jsxs(SidebarNav, { children: [
|
|
721
|
-
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "grid" }), label: "Overview", active: true, render: /* @__PURE__ */ jsx("a", { href: "/" }) }),
|
|
722
|
-
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }), label: "Editions", render: /* @__PURE__ */ jsx("a", { href: "/editions" }) }),
|
|
723
|
-
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "user" }), label: "Collectors", render: /* @__PURE__ */ jsx("a", { href: "/collectors" }) }),
|
|
724
|
-
/* @__PURE__ */ jsx(
|
|
725
|
-
SidebarItem,
|
|
726
|
-
{
|
|
727
|
-
icon: /* @__PURE__ */ jsx(Icon, { name: "bell" }),
|
|
728
|
-
label: "Activity",
|
|
729
|
-
badge: /* @__PURE__ */ jsx(NotificationBadge, { count: 4, tone: "neutral" }),
|
|
730
|
-
render: /* @__PURE__ */ jsx("a", { href: "/activity" })
|
|
731
|
-
}
|
|
732
|
-
),
|
|
733
|
-
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "gear" }), label: "Settings", render: /* @__PURE__ */ jsx("a", { href: "/settings" }) })
|
|
734
|
-
] }),
|
|
735
|
-
/* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsx(
|
|
736
|
-
Entity,
|
|
737
|
-
{
|
|
738
|
-
className: "[--entity-py:0]",
|
|
739
|
-
leading: /* @__PURE__ */ jsx(Avatar, { variant: "art", size: "sm", fallback: "AL", alt: "" }),
|
|
740
|
-
title: "Ada Lovelace",
|
|
741
|
-
subtitle: "@ada",
|
|
742
|
-
action: /* @__PURE__ */ jsxs(OverflowMenu, { label: "Account", size: "icon-sm", children: [
|
|
743
|
-
/* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/@ada" }), children: "View profile" }),
|
|
744
|
-
/* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/settings" }), children: "Settings" }),
|
|
745
|
-
/* @__PURE__ */ jsx(MenuSeparator, {}),
|
|
746
|
-
/* @__PURE__ */ jsx(MenuItem, { children: "Sign out" })
|
|
747
|
-
] })
|
|
748
|
-
}
|
|
749
|
-
) })
|
|
750
|
-
] }),
|
|
751
|
-
children: /* @__PURE__ */ jsxs(Page, { max: "wide", gap: 4, className: "py-6", children: [
|
|
752
|
-
/* @__PURE__ */ jsx(
|
|
753
|
-
PageHeader,
|
|
754
|
-
{
|
|
755
|
-
title: "Overview",
|
|
756
|
-
description: "Sales and collector activity for the last 30 days.",
|
|
757
|
-
actions: /* @__PURE__ */ jsxs(Row, { gap: 2, children: [
|
|
758
|
-
/* @__PURE__ */ jsxs(Button, { variant: "outline", children: [
|
|
759
|
-
/* @__PURE__ */ jsx(Icon, { name: "download" }),
|
|
760
|
-
"Export"
|
|
761
|
-
] }),
|
|
762
|
-
/* @__PURE__ */ jsxs(Button, { children: [
|
|
763
|
-
/* @__PURE__ */ jsx(Icon, { name: "plus" }),
|
|
764
|
-
"New edition"
|
|
765
|
-
] })
|
|
766
|
-
] })
|
|
767
|
-
}
|
|
768
|
-
),
|
|
769
|
-
/* @__PURE__ */ jsx(Grid, { cols: { base: 2, xl: 4 }, gap: 3, children: stats.map((stat) => /* @__PURE__ */ jsxs(Card, { children: [
|
|
770
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
771
|
-
/* @__PURE__ */ jsx(CardDescription, { children: stat.label }),
|
|
772
|
-
/* @__PURE__ */ jsx(CardTitle, { className: "text-mono-xl", children: stat.value })
|
|
773
|
-
] }),
|
|
774
|
-
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Row, { gap: 1, wrap: true, children: [
|
|
775
|
-
/* @__PURE__ */ jsx(
|
|
776
|
-
Badge,
|
|
777
|
-
{
|
|
778
|
-
tone: stat.up ? "success" : "warning",
|
|
779
|
-
size: "sm",
|
|
780
|
-
icon: /* @__PURE__ */ jsx(Icon, { name: stat.up ? "arrow-up" : "arrow-down" }),
|
|
781
|
-
children: stat.delta
|
|
782
|
-
}
|
|
783
|
-
),
|
|
784
|
-
/* @__PURE__ */ jsx("span", { className: "text-caption text-muted-foreground", children: stat.note })
|
|
785
|
-
] }) })
|
|
786
|
-
] }, stat.label)) }),
|
|
787
|
-
/* @__PURE__ */ jsxs(Grid, { cols: { base: 1, xl: 3 }, gap: 3, children: [
|
|
788
|
-
/* @__PURE__ */ jsxs(Card, { className: "flex flex-col xl:col-span-2", children: [
|
|
789
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
790
|
-
/* @__PURE__ */ jsx(CardTitle, { children: "Editions" }),
|
|
791
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Sales across your releases, newest first." })
|
|
792
|
-
] }),
|
|
793
|
-
/* @__PURE__ */ jsx(CardContent, { flush: true, className: "mt-4 flex-1", children: /* @__PURE__ */ jsx(
|
|
794
|
-
DataTable,
|
|
795
|
-
{
|
|
796
|
-
data: editions,
|
|
797
|
-
rowKey: (row) => row.id,
|
|
798
|
-
defaultSort: { key: "released", direction: "desc" },
|
|
799
|
-
columns: [
|
|
800
|
-
{ key: "title", header: "Edition", sortable: true, className: "text-label-md whitespace-nowrap" },
|
|
801
|
-
{
|
|
802
|
-
key: "status",
|
|
803
|
-
header: "Status",
|
|
804
|
-
cell: (row) => /* @__PURE__ */ jsx(Badge, { appearance: "plain", tone: STATUS_TONE[row.status], dot: true, children: STATUS_LABEL[row.status] })
|
|
805
|
-
},
|
|
806
|
-
{
|
|
807
|
-
key: "sold",
|
|
808
|
-
header: "Sold",
|
|
809
|
-
sortable: true,
|
|
810
|
-
sortValue: (row) => row.sold / row.supply,
|
|
811
|
-
cell: (row) => /* @__PURE__ */ jsxs(Row, { gap: 3, align: "center", className: "min-w-40", children: [
|
|
812
|
-
/* @__PURE__ */ jsx(
|
|
813
|
-
Meter,
|
|
814
|
-
{
|
|
815
|
-
value: row.sold,
|
|
816
|
-
max: row.supply,
|
|
817
|
-
size: "sm",
|
|
818
|
-
tone: row.sold === row.supply ? "success" : "primary",
|
|
819
|
-
"aria-label": `${row.title} sold`,
|
|
820
|
-
getAriaValueText: (_, v) => `${v} of ${row.supply} sold`,
|
|
821
|
-
className: "flex-1"
|
|
822
|
-
}
|
|
823
|
-
),
|
|
824
|
-
/* @__PURE__ */ jsxs("span", { className: "text-mono-caption whitespace-nowrap text-muted-foreground", children: [
|
|
825
|
-
row.sold,
|
|
826
|
-
"/",
|
|
827
|
-
row.supply
|
|
828
|
-
] })
|
|
829
|
-
] })
|
|
830
|
-
},
|
|
831
|
-
{
|
|
832
|
-
key: "revenue",
|
|
833
|
-
header: "Revenue",
|
|
834
|
-
align: "end",
|
|
835
|
-
sortable: true,
|
|
836
|
-
sortValue: (row) => row.sold * row.price,
|
|
837
|
-
cell: (row) => row.sold > 0 ? /* @__PURE__ */ jsx("span", { className: "text-mono-sm", children: usd.format(row.sold * row.price) }) : /* @__PURE__ */ jsx("span", { className: "text-mono-sm text-muted-foreground", children: "\u2014" })
|
|
838
|
-
},
|
|
839
|
-
{
|
|
840
|
-
key: "released",
|
|
841
|
-
header: "Release",
|
|
842
|
-
align: "end",
|
|
843
|
-
sortable: true,
|
|
844
|
-
cell: (row) => /* @__PURE__ */ jsx("span", { className: "text-body-sm whitespace-nowrap text-muted-foreground", children: shortDate(row.released) })
|
|
845
|
-
}
|
|
846
|
-
]
|
|
847
|
-
}
|
|
848
|
-
) }),
|
|
849
|
-
/* @__PURE__ */ jsxs(CardFooter, { className: "justify-between", children: [
|
|
850
|
-
/* @__PURE__ */ jsxs("span", { className: "text-caption text-muted-foreground", children: [
|
|
851
|
-
"Showing ",
|
|
852
|
-
editions.length,
|
|
853
|
-
" of ",
|
|
854
|
-
editionCount,
|
|
855
|
-
" editions"
|
|
856
|
-
] }),
|
|
857
|
-
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/editions" }), children: [
|
|
858
|
-
"View all editions",
|
|
859
|
-
/* @__PURE__ */ jsx(Icon, { name: "arrow-right" })
|
|
860
|
-
] })
|
|
861
|
-
] })
|
|
862
|
-
] }),
|
|
863
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
864
|
-
/* @__PURE__ */ jsxs(Card, { children: [
|
|
865
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
866
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Next payout" }),
|
|
867
|
-
/* @__PURE__ */ jsx(CardTitle, { className: "text-mono-xl", children: usdCents.format(payout.amount) }),
|
|
868
|
-
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Badge, { tone: "info", size: "sm", icon: /* @__PURE__ */ jsx(Icon, { name: "calendar" }), children: shortDate(payout.date) }) })
|
|
869
|
-
] }),
|
|
870
|
-
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("dl", { className: "sable-list-dl text-body-sm", children: payout.lines.map((line) => /* @__PURE__ */ jsxs("div", { className: "contents", children: [
|
|
871
|
-
/* @__PURE__ */ jsx("dt", { children: line.label }),
|
|
872
|
-
/* @__PURE__ */ jsx("dd", { className: typeof line.value === "number" ? "text-end text-mono-sm" : "text-end", children: typeof line.value === "number" ? usdCents.format(line.value) : line.value })
|
|
873
|
-
] }, line.label)) }) })
|
|
874
|
-
] }),
|
|
875
|
-
/* @__PURE__ */ jsxs(Card, { children: [
|
|
876
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
877
|
-
/* @__PURE__ */ jsx(CardTitle, { children: "Top collectors" }),
|
|
878
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "By spend this month." }),
|
|
879
|
-
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/collectors" }), children: "View all" }) })
|
|
880
|
-
] }),
|
|
881
|
-
/* @__PURE__ */ jsx(CardList, { children: collectors.map((person) => /* @__PURE__ */ jsx(
|
|
882
|
-
Entity,
|
|
883
|
-
{
|
|
884
|
-
leading: /* @__PURE__ */ jsx(Avatar, { variant: "art", size: "sm", fallback: person.initials, alt: "" }),
|
|
885
|
-
title: person.name,
|
|
886
|
-
subtitle: `${person.handle} \xB7 ${person.pieces} pieces`,
|
|
887
|
-
action: /* @__PURE__ */ jsx("span", { className: "text-mono-sm", children: usd.format(person.spent) })
|
|
888
|
-
},
|
|
889
|
-
person.id
|
|
890
|
-
)) })
|
|
891
|
-
] })
|
|
892
|
-
] })
|
|
893
|
-
] }),
|
|
894
|
-
/* @__PURE__ */ jsxs(Grid, { cols: { base: 1, xl: 3 }, gap: 3, children: [
|
|
895
|
-
/* @__PURE__ */ jsxs(Card, { className: "flex flex-col", children: [
|
|
896
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
897
|
-
/* @__PURE__ */ jsx(CardTitle, { children: "Upcoming releases" }),
|
|
898
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Scheduled to open to collectors." })
|
|
899
|
-
] }),
|
|
900
|
-
/* @__PURE__ */ jsx(CardList, { children: upcoming.map((release) => /* @__PURE__ */ jsx(
|
|
901
|
-
Entity,
|
|
902
|
-
{
|
|
903
|
-
title: release.title,
|
|
904
|
-
subtitle: release.detail,
|
|
905
|
-
action: /* @__PURE__ */ jsx(RelativeTime, { date: Date.now() + release.at, className: "text-caption text-muted-foreground" })
|
|
906
|
-
},
|
|
907
|
-
release.id
|
|
908
|
-
)) }),
|
|
909
|
-
/* @__PURE__ */ jsx(CardFooter, { className: "mt-auto", children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/editions/new" }), children: [
|
|
910
|
-
/* @__PURE__ */ jsx(Icon, { name: "calendar" }),
|
|
911
|
-
"Schedule a release"
|
|
912
|
-
] }) })
|
|
913
|
-
] }),
|
|
914
|
-
/* @__PURE__ */ jsxs(Card, { className: "xl:col-span-2", children: [
|
|
915
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
916
|
-
/* @__PURE__ */ jsx(CardTitle, { children: "Activity" }),
|
|
917
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Collects, tips, and follows." }),
|
|
918
|
-
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/activity" }), children: "View all" }) })
|
|
919
|
-
] }),
|
|
920
|
-
/* @__PURE__ */ jsx(CardList, { children: activity.map((item) => /* @__PURE__ */ jsx(
|
|
921
|
-
Entity,
|
|
922
|
-
{
|
|
923
|
-
leading: /* @__PURE__ */ jsx(Avatar, { variant: "art", size: "sm", fallback: item.initials, alt: "" }),
|
|
924
|
-
title: item.who,
|
|
925
|
-
subtitle: item.what,
|
|
926
|
-
action: /* @__PURE__ */ jsx(RelativeTime, { date: Date.now() - item.when, className: "text-caption text-muted-foreground" })
|
|
927
|
-
},
|
|
928
|
-
item.id
|
|
929
|
-
)) })
|
|
930
|
-
] })
|
|
931
|
-
] })
|
|
932
|
-
] })
|
|
933
|
-
}
|
|
934
|
-
);
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
// src/patterns/dashboard-page.meta.ts
|
|
938
|
-
var dashboardPageMeta = {
|
|
939
|
-
name: "DashboardPage",
|
|
940
|
-
summary: "App-shell creator dashboard: a branded Sidebar with an account footer (AppHeader + BottomNav below lg), a KPI row, an edge-to-edge editions table with sell-through Meters, and supporting payout, collector, release and activity cards \u2014 the default shape for an operator or analytics home route.",
|
|
941
|
-
intent: "Reach for this when a route's job is monitoring or managing many records (sales, orders, editions, posts) with a few headline numbers. Not for reading-focused routes (use FeedPage) or single-record routes (use ListDetailPage's detail side).",
|
|
942
|
-
notes: [
|
|
943
|
-
"Region cap is `wide` (1280px): dashboards hold tables and multi-column grids, never a reading column.",
|
|
944
|
-
"Hierarchy runs headline \u2192 records \u2192 context: KPI row, then the primary table beside its two closest companions (payout, top collectors), then the lower-priority lists (upcoming, activity). Every row of the grid is filled so the route ends on content, not empty canvas.",
|
|
945
|
-
"Stat cards: CardHeader carries the label (CardDescription) and the value; KPI values are `text-mono-xl` \u2014 the metric mono tier, tabular figures \u2014 never a heading tier. The trend Badge sits with its comparison note in CardContent, so the value keeps the full card width when the row folds to 2\xD72 on a phone.",
|
|
946
|
-
"Stat cards use an explicit cols map (2 \u2192 4) \u2014 a 4-stat row must fold to 2\xD72, never strand an orphan the way auto-fit can.",
|
|
947
|
-
"The table runs flush to the card edges with `<CardContent flush>`, which hands the card's padding to the table (`--table-edge-px`), so the first and last columns line up with the CardHeader at any card density \u2014 never hand-set pl-6/pr-6. Numeric columns align end in mono; Table scrolls horizontally inside the card on narrow screens.",
|
|
948
|
-
'Sold / supply is a `Meter` (how full a known range is), not `Progress` (task progress). Status is `Badge appearance="plain" dot`, with the app\'s own states mapped to tones once at the top of the file.',
|
|
949
|
-
"Lists are Entity rows in CardList, inset to the card padding and divided by hairlines \u2014 never CardContent with hand-tuned px. A card that shares a grid row with a taller one becomes `flex flex-col` and pins its CardFooter with `mt-auto`.",
|
|
950
|
-
"Brand and account live in the Sidebar: SidebarHeader holds the mark + wordmark, SidebarFooter holds the signed-in Entity with an OverflowMenu. Sidebar rebinds the page ink tokens to its own, so the Entity and OverflowMenu read correctly even when a theme paints the rail dark on a light page.",
|
|
951
|
-
"The page has exactly one heading-1 (PageHeader); card titles are Title tier, not headings.",
|
|
952
|
-
"Chrome (Sidebar) is outside the content grid; the Page region caps only content.",
|
|
953
|
-
"Sidebar hides below lg, so the shell's header carries an AppHeader (brand + account) and its footer a BottomNav with the same destinations \u2014 never strand mobile users without navigation."
|
|
954
|
-
],
|
|
955
|
-
a11y: [
|
|
956
|
-
'AppShell provides the skip link and <main id="main-content"> landmark.',
|
|
957
|
-
"The active SidebarItem and BottomNavItem carry aria-current via `active`.",
|
|
958
|
-
"DataTable headers are real <th>; sortable columns are announced.",
|
|
959
|
-
'Each sell-through Meter has an aria-label naming the edition and aria-valuetext reading "184 of 250 sold".',
|
|
960
|
-
"Avatars beside a visible name use an empty alt so the name is not read twice."
|
|
961
|
-
]
|
|
962
|
-
};
|
|
963
|
-
var posts = [
|
|
964
|
-
{
|
|
965
|
-
id: "p_01",
|
|
966
|
-
author: "Ada Lovelace",
|
|
967
|
-
handle: "@ada",
|
|
968
|
-
initials: "AL",
|
|
969
|
-
time: "2h",
|
|
970
|
-
following: true,
|
|
971
|
-
body: "Gold Rush is live. 100 signed prints, each paired with the on-chain original. Thank you for waiting.",
|
|
972
|
-
media: { src: "/media/edition-cover.svg", alt: "Gold Rush, a gold-leaf study in overlapping arcs" },
|
|
973
|
-
drop: { title: "Gold Rush", price: "$120", collected: 62, supply: 100 },
|
|
974
|
-
likes: 128,
|
|
975
|
-
comments: 24
|
|
976
|
-
},
|
|
977
|
-
{
|
|
978
|
-
id: "p_02",
|
|
979
|
-
author: "Grace Hopper",
|
|
980
|
-
handle: "@grace",
|
|
981
|
-
initials: "GH",
|
|
982
|
-
time: "5h",
|
|
983
|
-
following: true,
|
|
984
|
-
body: "Process shot from this week. Compilers and copper ink \u2014 the plotter ran for eleven hours on this one.",
|
|
985
|
-
media: { src: "/media/process-shot.svg", alt: "Work-in-progress plotter drawing in copper ink" },
|
|
986
|
-
likes: 86,
|
|
987
|
-
comments: 9
|
|
988
|
-
},
|
|
989
|
-
{
|
|
990
|
-
id: "p_03",
|
|
991
|
-
author: "Nina Simone",
|
|
992
|
-
handle: "@nina",
|
|
993
|
-
initials: "NS",
|
|
994
|
-
time: "8h",
|
|
995
|
-
following: false,
|
|
996
|
-
body: "Collectors of Dusk, no. 3 \u2014 your unlockable score is now in your library. Headphones on for this one.",
|
|
997
|
-
likes: 214,
|
|
998
|
-
comments: 31
|
|
999
|
-
}
|
|
1000
|
-
];
|
|
1001
|
-
var suggestions = [
|
|
1002
|
-
{ id: "u_01", name: "Mary Shelley", handle: "@mary", initials: "MS" },
|
|
1003
|
-
{ id: "u_02", name: "Nina Simone", handle: "@nina", initials: "NS" }
|
|
1004
|
-
];
|
|
1005
|
-
var upcoming2 = [
|
|
1006
|
-
{ id: "d_01", title: "Quiet Harbor", artist: "Mary Shelley", initials: "MS", date: "Sat, Oct 12" },
|
|
1007
|
-
{ id: "d_02", title: "Dusk, no. 4", artist: "Nina Simone", initials: "NS", date: "Tue, Oct 15" }
|
|
1008
|
-
];
|
|
1009
|
-
var renderPost = (post) => /* @__PURE__ */ jsx("article", { children: /* @__PURE__ */ jsxs(Card, { children: [
|
|
1010
|
-
/* @__PURE__ */ jsx(CardList, { children: /* @__PURE__ */ jsx(
|
|
1011
|
-
Entity,
|
|
1012
|
-
{
|
|
1013
|
-
leading: /* @__PURE__ */ jsx(Avatar, { variant: "art", fallback: post.initials, alt: post.author }),
|
|
1014
|
-
title: post.author,
|
|
1015
|
-
subtitle: `${post.handle} \xB7 ${post.time}`,
|
|
1016
|
-
action: /* @__PURE__ */ jsxs(OverflowMenu, { label: `Post options for ${post.author}`, size: "icon-sm", children: [
|
|
1017
|
-
/* @__PURE__ */ jsx(MenuItem, { children: "Copy link" }),
|
|
1018
|
-
/* @__PURE__ */ jsxs(MenuItem, { children: [
|
|
1019
|
-
"Mute ",
|
|
1020
|
-
post.handle
|
|
1021
|
-
] }),
|
|
1022
|
-
/* @__PURE__ */ jsx(MenuSeparator, {}),
|
|
1023
|
-
/* @__PURE__ */ jsx(MenuItem, { variant: "destructive", children: "Report post" })
|
|
1024
|
-
] })
|
|
1025
|
-
}
|
|
1026
|
-
) }),
|
|
1027
|
-
/* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-4 pt-0", children: [
|
|
1028
|
-
/* @__PURE__ */ jsx("p", { className: "text-body-md text-foreground", children: post.body }),
|
|
1029
|
-
post.media ? /* @__PURE__ */ jsxs(AspectBox, { ratio: 16 / 9, className: "overflow-hidden rounded-concentric-lg/6 bg-muted", children: [
|
|
1030
|
-
/* @__PURE__ */ jsx("img", { src: post.media.src, alt: post.media.alt, className: "size-full object-cover" }),
|
|
1031
|
-
post.drop ? /* @__PURE__ */ jsx("div", { className: "absolute start-3 top-3", children: /* @__PURE__ */ jsx(Badge, { appearance: "overlay", size: "sm", dot: true, children: "Live drop" }) }) : null
|
|
1032
|
-
] }) : null,
|
|
1033
|
-
post.drop ? /* @__PURE__ */ jsxs(Stack, { gap: 1.5, children: [
|
|
1034
|
-
/* @__PURE__ */ jsxs(Row, { gap: 2, align: "center", justify: "between", children: [
|
|
1035
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
1036
|
-
/* @__PURE__ */ jsx("p", { className: "text-title-md text-foreground", children: post.drop.title }),
|
|
1037
|
-
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: `${post.drop.price} \xB7 Edition of ${post.drop.supply}` })
|
|
1038
|
-
] }),
|
|
1039
|
-
/* @__PURE__ */ jsx(Button, { children: `Collect for ${post.drop.price}` })
|
|
1040
|
-
] }),
|
|
1041
|
-
/* @__PURE__ */ jsxs(
|
|
1042
|
-
Meter,
|
|
1043
|
-
{
|
|
1044
|
-
value: post.drop.collected,
|
|
1045
|
-
max: post.drop.supply,
|
|
1046
|
-
size: "sm",
|
|
1047
|
-
getAriaValueText: (_, value) => `${value} of ${post.drop?.supply} collected`,
|
|
1048
|
-
children: [
|
|
1049
|
-
/* @__PURE__ */ jsxs(Row, { gap: 2, align: "center", justify: "between", className: "mb-2", children: [
|
|
1050
|
-
/* @__PURE__ */ jsx(MeterLabel, { children: `${post.drop.collected} collected` }),
|
|
1051
|
-
/* @__PURE__ */ jsx(MeterValue, { children: (_, value) => `${(post.drop?.supply ?? 0) - value} left` })
|
|
1052
|
-
] }),
|
|
1053
|
-
/* @__PURE__ */ jsx(MeterTrack, {})
|
|
1054
|
-
]
|
|
1055
|
-
}
|
|
1056
|
-
)
|
|
1057
|
-
] }) : null,
|
|
1058
|
-
/* @__PURE__ */ jsxs(Row, { gap: 0.5, className: "-ms-3", children: [
|
|
1059
|
-
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", "aria-label": `Like, ${post.likes} likes`, children: [
|
|
1060
|
-
/* @__PURE__ */ jsx(Icon, { name: "heart" }),
|
|
1061
|
-
post.likes
|
|
1062
|
-
] }),
|
|
1063
|
-
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", "aria-label": `Comment, ${post.comments} comments`, children: [
|
|
1064
|
-
/* @__PURE__ */ jsx(Icon, { name: "comment" }),
|
|
1065
|
-
post.comments
|
|
1066
|
-
] }),
|
|
1067
|
-
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": "Share", children: /* @__PURE__ */ jsx(Icon, { name: "share" }) })
|
|
1068
|
-
] })
|
|
1069
|
-
] })
|
|
1070
|
-
] }) }, post.id);
|
|
1071
|
-
function FeedPage() {
|
|
1072
|
-
return /* @__PURE__ */ jsx(Page, { as: "main", max: "wide", gap: 4, className: "py-6", children: /* @__PURE__ */ jsxs(Row, { gap: 4, align: "start", justify: "center", children: [
|
|
1073
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 3, className: "min-w-0 max-w-(--region-feed) flex-1", children: [
|
|
1074
|
-
/* @__PURE__ */ jsx(PageHeader, { title: "Home" }),
|
|
1075
|
-
/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Row, { gap: 1.5, align: "start", children: [
|
|
1076
|
-
/* @__PURE__ */ jsx(Avatar, { variant: "art", fallback: "AL", alt: "Ada Lovelace" }),
|
|
1077
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 1.5, className: "min-w-0 flex-1", children: [
|
|
1078
|
-
/* @__PURE__ */ jsx(
|
|
1079
|
-
Textarea,
|
|
1080
|
-
{
|
|
1081
|
-
autoResize: true,
|
|
1082
|
-
rows: 2,
|
|
1083
|
-
"aria-label": "New post",
|
|
1084
|
-
placeholder: "Share a process shot or an upcoming drop"
|
|
1085
|
-
}
|
|
1086
|
-
),
|
|
1087
|
-
/* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", justify: "between", children: [
|
|
1088
|
-
/* @__PURE__ */ jsxs(Row, { gap: 0.5, children: [
|
|
1089
|
-
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Add images", children: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }) }),
|
|
1090
|
-
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Attach an edition", children: /* @__PURE__ */ jsx(Icon, { name: "gem" }) })
|
|
1091
|
-
] }),
|
|
1092
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", children: "Post" })
|
|
1093
|
-
] })
|
|
1094
|
-
] })
|
|
1095
|
-
] }) }) }),
|
|
1096
|
-
/* @__PURE__ */ jsxs(Tabs, { defaultValue: "for-you", children: [
|
|
1097
|
-
/* @__PURE__ */ jsxs(TabsList, { className: "flex", children: [
|
|
1098
|
-
/* @__PURE__ */ jsx(TabsTrigger, { value: "for-you", children: "For you" }),
|
|
1099
|
-
/* @__PURE__ */ jsx(TabsTrigger, { value: "following", children: "Following" })
|
|
1100
|
-
] }),
|
|
1101
|
-
/* @__PURE__ */ jsx(TabsPanel, { value: "for-you", children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1102
|
-
posts.map(renderPost),
|
|
1103
|
-
/* @__PURE__ */ jsx(Card, { "aria-busy": "true", children: /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-4", children: [
|
|
1104
|
-
/* @__PURE__ */ jsxs(Row, { gap: 1.5, align: "center", children: [
|
|
1105
|
-
/* @__PURE__ */ jsx(Skeleton, { className: "size-10 rounded-full" }),
|
|
1106
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 1, children: [
|
|
1107
|
-
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-32" }),
|
|
1108
|
-
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-20" })
|
|
1109
|
-
] })
|
|
1110
|
-
] }),
|
|
1111
|
-
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-full" }),
|
|
1112
|
-
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-2/3" })
|
|
1113
|
-
] }) }),
|
|
1114
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", loading: true, loadingLabel: "Loading posts", children: "Load more posts" })
|
|
1115
|
-
] }) }),
|
|
1116
|
-
/* @__PURE__ */ jsx(TabsPanel, { value: "following", children: /* @__PURE__ */ jsx(Stack, { gap: 3, children: posts.filter((post) => post.following).map(renderPost) }) })
|
|
1117
|
-
] })
|
|
1118
|
-
] }),
|
|
1119
|
-
/* @__PURE__ */ jsx("aside", { "aria-label": "Suggestions", className: "sticky top-6 hidden w-80 shrink-0 lg:block", children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1120
|
-
/* @__PURE__ */ jsxs(Card, { children: [
|
|
1121
|
-
/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), children: "Who to follow" }) }),
|
|
1122
|
-
/* @__PURE__ */ jsx(CardList, { children: suggestions.map((person) => /* @__PURE__ */ jsx(
|
|
1123
|
-
Entity,
|
|
1124
|
-
{
|
|
1125
|
-
leading: /* @__PURE__ */ jsx(Avatar, { variant: "art", fallback: person.initials, alt: person.name }),
|
|
1126
|
-
title: person.name,
|
|
1127
|
-
subtitle: person.handle,
|
|
1128
|
-
action: /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", "aria-label": `Follow ${person.name}`, children: "Follow" })
|
|
1129
|
-
},
|
|
1130
|
-
person.id
|
|
1131
|
-
)) })
|
|
1132
|
-
] }),
|
|
1133
|
-
/* @__PURE__ */ jsxs(Card, { children: [
|
|
1134
|
-
/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), children: "Upcoming drops" }) }),
|
|
1135
|
-
/* @__PURE__ */ jsx(CardList, { children: upcoming2.map((drop) => /* @__PURE__ */ jsx(
|
|
1136
|
-
Entity,
|
|
1137
|
-
{
|
|
1138
|
-
leading: /* @__PURE__ */ jsx(Avatar, { variant: "art", fallback: drop.initials, alt: drop.artist }),
|
|
1139
|
-
title: drop.title,
|
|
1140
|
-
subtitle: `${drop.artist} \xB7 ${drop.date}`,
|
|
1141
|
-
action: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": `Remind me about ${drop.title}`, children: /* @__PURE__ */ jsx(Icon, { name: "bell" }) })
|
|
1142
|
-
},
|
|
1143
|
-
drop.id
|
|
1144
|
-
)) })
|
|
1145
|
-
] })
|
|
1146
|
-
] }) })
|
|
1147
|
-
] }) });
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
// src/patterns/feed-page.meta.ts
|
|
1151
|
-
var feedPageMeta = {
|
|
1152
|
-
name: "FeedPage",
|
|
1153
|
-
summary: "Social reading feed with a side rail: a feed-width main column (composer, For you / Following Tabs, one Card per post, an edition drop with price, supply Meter and Collect, a Skeleton for the next page) beside a sticky rail of who-to-follow and upcoming drops that drops away below lg.",
|
|
1154
|
-
intent: "Reach for this for timelines, activity streams, and any reverse-chronological reading surface where people also act on what they read. Not for record management (DashboardPage) or side-by-side browsing (ListDetailPage).",
|
|
1155
|
-
notes: [
|
|
1156
|
-
"The main column is capped at `--region-feed` (640px) inside a `wide` page, so posts keep a reading measure while the rail fills the desktop width. The rail is a fixed `w-80` aside, `sticky` at lg and `hidden` below it \u2014 its content is supplementary, so nothing is lost on mobile.",
|
|
1157
|
-
"Feed scope is `Tabs` (underline, stretched to the column with `flex`): peer views of the same feed. The composer sits above them because posting applies to both.",
|
|
1158
|
-
"The composer is a Card holding `Textarea autoResize` plus ghost icon Buttons and a secondary `Post` \u2014 not ChatComposer, which submits on Enter and is for conversations.",
|
|
1159
|
-
"Each post is a Card: an Entity byline in a CardList (with an OverflowMenu for post actions), body copy, then media in an AspectBox at the asset's own ratio, inset with `rounded-concentric-lg/6` so its corners follow the card's in every theme.",
|
|
1160
|
-
'An edition drop adds a `Badge appearance="overlay"` on the artwork, title and price, the one primary Button on the page (`Collect`), and a `Meter` for supply \u2014 collected against a known maximum is a level, not task progress.',
|
|
1161
|
-
"Actions are ghost sm Buttons (icon + count) in a Row pulled out by the button's own padding (`-ms-3`) so the first icon sits on the text edge.",
|
|
1162
|
-
"Loading the next page shows a Skeleton post in the shape the post will land in, above the explicit load-more Button in its `loading` state \u2014 predictable for keyboard and AT users, unlike infinite scroll."
|
|
1163
|
-
],
|
|
1164
|
-
a11y: [
|
|
1165
|
-
"Each post is an <article>, so AT users can jump between posts; the rail is a labelled <aside> landmark.",
|
|
1166
|
-
"Rail card titles render as h2 (CardTitle `render={<h2 />}`) under the page's one h1.",
|
|
1167
|
-
"Icon-only buttons and every Follow / Remind button carry aria-labels naming their target; counts stay in the accessible name.",
|
|
1168
|
-
'The supply Meter is role=meter with a getAriaValueText reading ("62 of 100 collected").',
|
|
1169
|
-
'The Skeleton card is aria-busy; the loading Button keeps its label ("Loading posts").'
|
|
1170
|
-
]
|
|
1171
|
-
};
|
|
1172
|
-
var navLinks = [
|
|
1173
|
-
{ label: "Docs", href: "/docs" },
|
|
1174
|
-
{ label: "Components", href: "/components" },
|
|
1175
|
-
{ label: "Patterns", href: "/patterns" },
|
|
1176
|
-
{ label: "Themes", href: "/themes" }
|
|
1177
|
-
];
|
|
1178
|
-
var deploys = [
|
|
1179
|
-
{
|
|
1180
|
-
initials: "MO",
|
|
1181
|
-
name: "Maya Ortiz",
|
|
1182
|
-
title: "Add usage-based billing",
|
|
1183
|
-
meta: "main \xB7 a3f9c21 \xB7 2m ago",
|
|
1184
|
-
status: "Building",
|
|
1185
|
-
tone: "info",
|
|
1186
|
-
pulse: true
|
|
1187
|
-
},
|
|
1188
|
-
{
|
|
1189
|
-
initials: "SR",
|
|
1190
|
-
name: "Sam Reyes",
|
|
1191
|
-
title: "Fix search ranking for long queries",
|
|
1192
|
-
meta: "main \xB7 81be04d \xB7 18m ago",
|
|
1193
|
-
status: "Ready",
|
|
1194
|
-
tone: "success",
|
|
1195
|
-
pulse: false
|
|
1196
|
-
},
|
|
1197
|
-
{
|
|
1198
|
-
initials: "JL",
|
|
1199
|
-
name: "Jun Lee",
|
|
1200
|
-
title: "Rotate webhook signing keys",
|
|
1201
|
-
meta: "main \xB7 5c7e9aa \xB7 1h ago",
|
|
1202
|
-
status: "Ready",
|
|
1203
|
-
tone: "success",
|
|
1204
|
-
pulse: false
|
|
1205
|
-
}
|
|
1206
|
-
];
|
|
1207
|
-
var features = [
|
|
1208
|
-
{
|
|
1209
|
-
icon: "component",
|
|
1210
|
-
title: "Behavior built in",
|
|
1211
|
-
body: "Focus management, keyboard navigation, and ARIA come from Base UI primitives under every control."
|
|
1212
|
-
},
|
|
1213
|
-
{
|
|
1214
|
-
icon: "eyedropper",
|
|
1215
|
-
title: "Semantic tokens end to end",
|
|
1216
|
-
body: "Re-theme the whole product, light and dark, from one preset without touching a component."
|
|
1217
|
-
},
|
|
1218
|
-
{
|
|
1219
|
-
icon: "layout",
|
|
1220
|
-
title: "Pages, not just parts",
|
|
1221
|
-
body: "Landing, dashboard, settings, auth, and list-detail shells to copy and adapt."
|
|
1222
|
-
},
|
|
1223
|
-
{
|
|
1224
|
-
icon: "book",
|
|
1225
|
-
title: "A contract agents can read",
|
|
1226
|
-
body: "registry.json, llms.txt, and a CLI hand agents the exact props, examples, and rules."
|
|
1227
|
-
},
|
|
1228
|
-
{
|
|
1229
|
-
icon: "circle-check",
|
|
1230
|
-
title: "Lint that knows the design",
|
|
1231
|
-
body: "sable-lint flags raw colors, hand-built controls, and motion that ignores reduced motion."
|
|
1232
|
-
},
|
|
1233
|
-
{
|
|
1234
|
-
icon: "play",
|
|
1235
|
-
title: "Motion with an off switch",
|
|
1236
|
-
body: "Named recipes run on one scale variable, so reduced motion stops every transition at once."
|
|
1237
|
-
}
|
|
1238
|
-
];
|
|
1239
|
-
var steps2 = [
|
|
1240
|
-
{
|
|
1241
|
-
title: "Install the package",
|
|
1242
|
-
body: "One dependency. React 19 and Tailwind v4 are the only peers."
|
|
1243
|
-
},
|
|
1244
|
-
{
|
|
1245
|
-
title: "Import the styles",
|
|
1246
|
-
body: "Tailwind first, then Sable's tokens, then a source glob over the package."
|
|
1247
|
-
},
|
|
1248
|
-
{
|
|
1249
|
-
title: "Compose your screens",
|
|
1250
|
-
body: "Build from components and layout primitives. Your theme does the rest."
|
|
1251
|
-
}
|
|
1252
|
-
];
|
|
1253
|
-
var stylesCode = `@import "tailwindcss";
|
|
1254
|
-
@import "@sableui/react/styles.css";
|
|
1255
|
-
@source "../node_modules/@sableui/react/dist/**/*.{js,mjs}";`;
|
|
1256
|
-
var composeCode = `import { Button, Card, CardContent, CardHeader, CardTitle } from "@sableui/react"
|
|
1257
|
-
|
|
1258
|
-
export function DeployCard() {
|
|
1259
|
-
return (
|
|
1260
|
-
<Card>
|
|
1261
|
-
<CardHeader>
|
|
1262
|
-
<CardTitle>Production</CardTitle>
|
|
1263
|
-
</CardHeader>
|
|
1264
|
-
<CardContent>
|
|
1265
|
-
<Button>Deploy</Button>
|
|
1266
|
-
</CardContent>
|
|
1267
|
-
</Card>
|
|
1268
|
-
)
|
|
1269
|
-
}`;
|
|
1270
|
-
var terminalCode = `$ npx sable search "confirm before deleting"
|
|
1271
|
-
component DestructiveActionModal
|
|
1272
|
-
component Dialog
|
|
1273
|
-
|
|
1274
|
-
$ npx sable component DestructiveActionModal --props
|
|
1275
|
-
props, a11y contract, composed examples
|
|
1276
|
-
|
|
1277
|
-
$ npx sable lint src
|
|
1278
|
-
0 findings in 42 files`;
|
|
1279
|
-
var agentPoints = [
|
|
1280
|
-
"Search ranks components, patterns, tokens, and docs for a plain-language need.",
|
|
1281
|
-
"Every component ships its props, accessibility contract, and composed examples.",
|
|
1282
|
-
"The same lint runs in the editor, in CI, and inside the agent's own loop."
|
|
1283
|
-
];
|
|
1284
|
-
var footerGroups2 = [
|
|
1285
|
-
{
|
|
1286
|
-
title: "Product",
|
|
1287
|
-
links: [
|
|
1288
|
-
{ label: "Components", href: "/components" },
|
|
1289
|
-
{ label: "Patterns", href: "/patterns" },
|
|
1290
|
-
{ label: "Themes", href: "/themes" },
|
|
1291
|
-
{ label: "Canvas", href: "/canvas" }
|
|
1292
|
-
]
|
|
1293
|
-
},
|
|
1294
|
-
{
|
|
1295
|
-
title: "Resources",
|
|
1296
|
-
links: [
|
|
1297
|
-
{ label: "Documentation", href: "/docs" },
|
|
1298
|
-
{ label: "Changelog", href: "/changelog" },
|
|
1299
|
-
{ label: "llms.txt", href: "/llms.txt" },
|
|
1300
|
-
{ label: "registry.json", href: "/registry.json" }
|
|
1301
|
-
]
|
|
1302
|
-
},
|
|
1303
|
-
{
|
|
1304
|
-
title: "Community",
|
|
1305
|
-
links: [
|
|
1306
|
-
{ label: "GitHub", href: "/github" },
|
|
1307
|
-
{ label: "npm", href: "/npm" },
|
|
1308
|
-
{ label: "Discussions", href: "/discussions" }
|
|
1309
|
-
]
|
|
1310
|
-
}
|
|
1311
|
-
];
|
|
1312
|
-
function LandingPage() {
|
|
1313
|
-
return /* @__PURE__ */ jsxs("div", { className: "min-h-dvh bg-background text-foreground", children: [
|
|
1314
|
-
/* @__PURE__ */ jsx("header", { className: "border-b border-border", children: /* @__PURE__ */ jsxs(Region, { as: "nav", "aria-label": "Main", max: "wide", className: "grid h-16 grid-cols-[1fr_auto_1fr] items-center", children: [
|
|
1315
|
-
/* @__PURE__ */ jsx("a", { href: "/", className: "justify-self-start text-title-lg motion-interactive hover:text-muted-foreground", children: "Sable" }),
|
|
1316
|
-
/* @__PURE__ */ jsx("div", { className: "justify-self-center max-md:hidden", children: /* @__PURE__ */ jsx(Row, { gap: 1, children: navLinks.map((link) => /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: link.href }), children: link.label }, link.label)) }) }),
|
|
1317
|
-
/* @__PURE__ */ jsxs(Row, { gap: 1, className: "col-start-3 justify-self-end", children: [
|
|
1318
|
-
/* @__PURE__ */ jsx("div", { className: "max-sm:hidden", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/github" }), children: "GitHub" }) }),
|
|
1319
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/docs/getting-started" }), children: "Get started" })
|
|
1320
|
-
] })
|
|
1321
|
-
] }) }),
|
|
1322
|
-
/* @__PURE__ */ jsxs("main", { children: [
|
|
1323
|
-
/* @__PURE__ */ jsx(Region, { as: "section", max: "wide", className: "pb-16 pt-16 sm:pt-24", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, align: "center", children: [
|
|
1324
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 4, align: "center", className: "text-center", children: [
|
|
1325
|
-
/* @__PURE__ */ jsx(Badge, { appearance: "outline", size: "sm", icon: /* @__PURE__ */ jsx(Icon, { name: "sparkles" }), children: "Open source \xB7 MIT licensed" }),
|
|
1326
|
-
/* @__PURE__ */ jsx("h1", { className: "max-w-4xl text-balance text-display-lg sm:text-display-2xl", children: "Product UI your agents can't get wrong" }),
|
|
1327
|
-
/* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-pretty text-body-lg text-muted-foreground", children: "Sable is a themeable React component library with enforced tokens, typography, and motion, documented for coding agents as carefully as for people." }),
|
|
1328
|
-
/* @__PURE__ */ jsxs(Row, { gap: 2, justify: "center", wrap: true, className: "pt-2", children: [
|
|
1329
|
-
/* @__PURE__ */ jsx(Button, { size: "cta", render: /* @__PURE__ */ jsx("a", { href: "/docs/getting-started" }), children: "Get started" }),
|
|
1330
|
-
/* @__PURE__ */ jsx(Button, { size: "cta", variant: "outline", render: /* @__PURE__ */ jsx("a", { href: "/components" }), children: "Browse components" })
|
|
1331
|
-
] })
|
|
1332
|
-
] }),
|
|
1333
|
-
/* @__PURE__ */ jsx("div", { className: "w-full max-w-sm", children: /* @__PURE__ */ jsx(CodeBlock, { prompt: "$", code: "pnpm add @sableui/react" }) })
|
|
1334
|
-
] }) }),
|
|
1335
|
-
/* @__PURE__ */ jsx(Region, { as: "section", "aria-label": "Product preview", max: "wide", className: "pb-20 sm:pb-28", children: /* @__PURE__ */ jsxs("figure", { children: [
|
|
1336
|
-
/* @__PURE__ */ jsx(Browser, { url: "atlas.example/deploys", className: "shadow-xl", children: /* @__PURE__ */ jsxs("div", { inert: true, className: "grid gap-4 p-4 sm:p-6 lg:grid-cols-[minmax(0,1fr)_20rem]", children: [
|
|
1337
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 4, className: "min-w-0", children: [
|
|
1338
|
-
/* @__PURE__ */ jsx(
|
|
1339
|
-
PageHeader,
|
|
1340
|
-
{
|
|
1341
|
-
titleAs: "h2",
|
|
1342
|
-
title: "Deployments",
|
|
1343
|
-
description: "atlas-web \xB7 production",
|
|
1344
|
-
actions: /* @__PURE__ */ jsxs(Row, { gap: 2, children: [
|
|
1345
|
-
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: "View logs" }),
|
|
1346
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", iconStart: /* @__PURE__ */ jsx(Icon, { name: "upload" }), children: "Deploy" })
|
|
1347
|
-
] })
|
|
1348
|
-
}
|
|
1349
|
-
),
|
|
1350
|
-
/* @__PURE__ */ jsxs(Grid, { cols: { base: 2, md: 3 }, gap: 3, children: [
|
|
1351
|
-
/* @__PURE__ */ jsxs(Card, { density: "compact", children: [
|
|
1352
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
1353
|
-
/* @__PURE__ */ jsx(CardTitle, { children: "Uptime" }),
|
|
1354
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Last 30 days" })
|
|
1355
|
-
] }),
|
|
1356
|
-
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("p", { className: "text-heading-3", children: "99.98%" }) })
|
|
1357
|
-
] }),
|
|
1358
|
-
/* @__PURE__ */ jsxs(Card, { density: "compact", children: [
|
|
1359
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
1360
|
-
/* @__PURE__ */ jsx(CardTitle, { children: "p95 latency" }),
|
|
1361
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Edge, all regions" })
|
|
1362
|
-
] }),
|
|
1363
|
-
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("p", { className: "text-heading-3", children: "182 ms" }) })
|
|
1364
|
-
] }),
|
|
1365
|
-
/* @__PURE__ */ jsxs(Card, { density: "compact", className: "max-md:hidden", children: [
|
|
1366
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
1367
|
-
/* @__PURE__ */ jsx(CardTitle, { children: "Build minutes" }),
|
|
1368
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Team plan" })
|
|
1369
|
-
] }),
|
|
1370
|
-
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Meter, { value: 64, tone: "standard", children: [
|
|
1371
|
-
/* @__PURE__ */ jsxs(Row, { justify: "between", wrap: true, className: "mb-2", children: [
|
|
1372
|
-
/* @__PURE__ */ jsx(MeterLabel, { children: "Used" }),
|
|
1373
|
-
/* @__PURE__ */ jsx(MeterValue, { children: (_, value) => `${(value * 30).toLocaleString("en-US")} of 3,000` })
|
|
1374
|
-
] }),
|
|
1375
|
-
/* @__PURE__ */ jsx(MeterTrack, {})
|
|
1376
|
-
] }) })
|
|
1377
|
-
] })
|
|
1378
|
-
] }),
|
|
1379
|
-
/* @__PURE__ */ jsxs(Card, { children: [
|
|
1380
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
1381
|
-
/* @__PURE__ */ jsx(CardTitle, { children: "Recent deploys" }),
|
|
1382
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Production branch" }),
|
|
1383
|
-
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Badge, { appearance: "plain", tone: "success", dot: true, children: "Healthy" }) })
|
|
1384
|
-
] }),
|
|
1385
|
-
/* @__PURE__ */ jsx(CardList, { children: deploys.map((deploy) => /* @__PURE__ */ jsx(
|
|
1386
|
-
Entity,
|
|
1387
|
-
{
|
|
1388
|
-
leading: /* @__PURE__ */ jsx(Avatar, { variant: "art", size: "sm", fallback: deploy.initials, alt: deploy.name }),
|
|
1389
|
-
title: deploy.title,
|
|
1390
|
-
subtitle: deploy.meta,
|
|
1391
|
-
action: /* @__PURE__ */ jsx(Badge, { appearance: "plain", tone: deploy.tone, dot: true, pulse: deploy.pulse, children: deploy.status })
|
|
1392
|
-
},
|
|
1393
|
-
deploy.meta
|
|
1394
|
-
)) })
|
|
1395
|
-
] })
|
|
1396
|
-
] }),
|
|
1397
|
-
/* @__PURE__ */ jsxs(Card, { className: "max-lg:hidden", children: [
|
|
1398
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
1399
|
-
/* @__PURE__ */ jsx(CardTitle, { children: "Release assistant" }),
|
|
1400
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Summaries from today's deploys" })
|
|
1401
|
-
] }),
|
|
1402
|
-
/* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-4", children: [
|
|
1403
|
-
/* @__PURE__ */ jsx(Message, { align: "end", children: /* @__PURE__ */ jsx(Bubble, { variant: "primary", align: "end", children: "What shipped to production today?" }) }),
|
|
1404
|
-
/* @__PURE__ */ jsx(Message, { children: /* @__PURE__ */ jsx(Bubble, { children: "Three deploys. Search ranking is fixed, webhook keys are rotated, and usage-based billing is building now." }) }),
|
|
1405
|
-
/* @__PURE__ */ jsx(ChatComposer, { placeholder: "Ask about a deploy" })
|
|
1406
|
-
] })
|
|
1407
|
-
] })
|
|
1408
|
-
] }) }),
|
|
1409
|
-
/* @__PURE__ */ jsx("figcaption", { className: "mt-4 text-center text-body-sm text-muted-foreground", children: "Every surface in this preview is a Sable component, styled by the active theme." })
|
|
1410
|
-
] }) }),
|
|
1411
|
-
/* @__PURE__ */ jsx("section", { "aria-labelledby": "landing-features", className: "border-y border-border bg-muted/40 py-20 sm:py-28", children: /* @__PURE__ */ jsx(Region, { max: "wide", children: /* @__PURE__ */ jsxs(Stack, { gap: 10, children: [
|
|
1412
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 3, className: "max-w-2xl", children: [
|
|
1413
|
-
/* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Why Sable" }),
|
|
1414
|
-
/* @__PURE__ */ jsx("h2", { id: "landing-features", className: "text-balance text-heading-1", children: "A design system with the rules written down" }),
|
|
1415
|
-
/* @__PURE__ */ jsx("p", { className: "text-body-lg text-muted-foreground", children: "The decisions a senior designer would make are encoded once, so every screen your team or your agent builds starts from them." })
|
|
1416
|
-
] }),
|
|
1417
|
-
/* @__PURE__ */ jsx(Reveal, { children: /* @__PURE__ */ jsx(Grid, { cols: { base: 1, sm: 2, lg: 3 }, gap: 4, children: features.map((feature) => /* @__PURE__ */ jsx(Card, { render: /* @__PURE__ */ jsx("article", {}), className: "h-full", children: /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-3", children: [
|
|
1418
|
-
/* @__PURE__ */ jsx("span", { className: "grid size-10 place-items-center rounded-md sable-well", children: /* @__PURE__ */ jsx(Icon, { name: feature.icon, className: "text-xl" }) }),
|
|
1419
|
-
/* @__PURE__ */ jsx("h3", { className: "pt-2 text-title-lg", children: feature.title }),
|
|
1420
|
-
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: feature.body })
|
|
1421
|
-
] }) }, feature.title)) }) })
|
|
1422
|
-
] }) }) }),
|
|
1423
|
-
/* @__PURE__ */ jsx(Region, { as: "section", "aria-labelledby": "landing-setup", max: "wide", className: "py-20 sm:py-28", children: /* @__PURE__ */ jsxs("div", { className: "grid items-start gap-10 lg:grid-cols-[minmax(0,5fr)_minmax(0,7fr)] lg:gap-16", children: [
|
|
1424
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
|
|
1425
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1426
|
-
/* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Setup" }),
|
|
1427
|
-
/* @__PURE__ */ jsx("h2", { id: "landing-setup", className: "text-balance text-heading-1", children: "From install to first screen in three steps" })
|
|
1428
|
-
] }),
|
|
1429
|
-
/* @__PURE__ */ jsx("ol", { className: "flex flex-col gap-6", children: steps2.map((step, index) => /* @__PURE__ */ jsxs("li", { className: "grid grid-cols-[auto_1fr] gap-4", children: [
|
|
1430
|
-
/* @__PURE__ */ jsxs("span", { className: "pt-0.5 text-mono-md text-muted-foreground", children: [
|
|
1431
|
-
"0",
|
|
1432
|
-
index + 1
|
|
1433
|
-
] }),
|
|
1434
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
1435
|
-
/* @__PURE__ */ jsx("h3", { className: "text-title-lg", children: step.title }),
|
|
1436
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-body-md text-muted-foreground", children: step.body })
|
|
1437
|
-
] })
|
|
1438
|
-
] }, step.title)) })
|
|
1439
|
-
] }),
|
|
1440
|
-
/* @__PURE__ */ jsx(Reveal, { className: "min-w-0", children: /* @__PURE__ */ jsxs(Tabs, { defaultValue: "compose", children: [
|
|
1441
|
-
/* @__PURE__ */ jsxs(TabsList, { children: [
|
|
1442
|
-
/* @__PURE__ */ jsx(TabsTrigger, { value: "install", children: "Install" }),
|
|
1443
|
-
/* @__PURE__ */ jsx(TabsTrigger, { value: "styles", children: "Styles" }),
|
|
1444
|
-
/* @__PURE__ */ jsx(TabsTrigger, { value: "compose", children: "Compose" })
|
|
1445
|
-
] }),
|
|
1446
|
-
/* @__PURE__ */ jsx(TabsPanel, { value: "install", className: "pt-4", children: /* @__PURE__ */ jsx(CodeBlock, { prompt: "$", code: "pnpm add @sableui/react" }) }),
|
|
1447
|
-
/* @__PURE__ */ jsx(TabsPanel, { value: "styles", className: "pt-4", children: /* @__PURE__ */ jsx(CodeBlock, { filename: "app.css", code: stylesCode }) }),
|
|
1448
|
-
/* @__PURE__ */ jsx(TabsPanel, { value: "compose", className: "pt-4", children: /* @__PURE__ */ jsx(CodeBlock, { filename: "deploy-card.tsx", code: composeCode }) })
|
|
1449
|
-
] }) })
|
|
1450
|
-
] }) }),
|
|
1451
|
-
/* @__PURE__ */ jsx("section", { "aria-labelledby": "landing-agents", className: "border-t border-border py-20 sm:py-28", children: /* @__PURE__ */ jsx(Region, { max: "wide", children: /* @__PURE__ */ jsxs("div", { className: "grid items-center gap-10 lg:grid-cols-2 lg:gap-16", children: [
|
|
1452
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 5, children: [
|
|
1453
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1454
|
-
/* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Built for agents" }),
|
|
1455
|
-
/* @__PURE__ */ jsx("h2", { id: "landing-agents", className: "text-balance text-heading-1", children: "Your agent reads the same contract you do" }),
|
|
1456
|
-
/* @__PURE__ */ jsxs("p", { className: "text-body-lg text-muted-foreground", children: [
|
|
1457
|
-
"Point Claude Code, Codex, or Cursor at the installed package. The",
|
|
1458
|
-
" ",
|
|
1459
|
-
/* @__PURE__ */ jsx(Code, { children: "sable" }),
|
|
1460
|
-
" CLI answers what to use, how to use it, and whether the result holds up."
|
|
1461
|
-
] })
|
|
1462
|
-
] }),
|
|
1463
|
-
/* @__PURE__ */ jsx("ul", { className: "sable-list", children: agentPoints.map((point) => /* @__PURE__ */ jsx("li", { children: point }, point)) })
|
|
1464
|
-
] }),
|
|
1465
|
-
/* @__PURE__ */ jsx(Reveal, { className: "min-w-0", children: /* @__PURE__ */ jsx(CodeBlock, { filename: "Terminal", code: terminalCode, highlight: false }) })
|
|
1466
|
-
] }) }) }),
|
|
1467
|
-
/* @__PURE__ */ jsx(Region, { as: "section", "aria-labelledby": "landing-cta", max: "wide", className: "pb-20 sm:pb-28", children: /* @__PURE__ */ jsx(Reveal, { children: /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Stack, { gap: 4, align: "center", className: "py-8 text-center sm:py-12", children: [
|
|
1468
|
-
/* @__PURE__ */ jsx("h2", { id: "landing-cta", className: "max-w-2xl text-balance text-heading-1", children: "Build the product, not another component library" }),
|
|
1469
|
-
/* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-body-lg text-muted-foreground", children: "Install Sable, pick a preset, and ship the first screen today." }),
|
|
1470
|
-
/* @__PURE__ */ jsxs(Row, { gap: 2, justify: "center", wrap: true, className: "pt-2", children: [
|
|
1471
|
-
/* @__PURE__ */ jsx(Button, { size: "cta", render: /* @__PURE__ */ jsx("a", { href: "/docs/getting-started" }), children: "Get started" }),
|
|
1472
|
-
/* @__PURE__ */ jsx(Button, { size: "cta", variant: "outline", render: /* @__PURE__ */ jsx("a", { href: "/github" }), children: "Star on GitHub" })
|
|
1473
|
-
] })
|
|
1474
|
-
] }) }) }) }) })
|
|
1475
|
-
] }),
|
|
1476
|
-
/* @__PURE__ */ jsx("footer", { className: "border-t border-border", children: /* @__PURE__ */ jsxs(Region, { max: "wide", className: "py-12", children: [
|
|
1477
|
-
/* @__PURE__ */ jsxs("div", { className: "grid gap-10 md:grid-cols-[minmax(0,2fr)_minmax(0,3fr)]", children: [
|
|
1478
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 3, className: "max-w-xs", children: [
|
|
1479
|
-
/* @__PURE__ */ jsx("a", { href: "/", className: "w-fit text-title-lg motion-interactive hover:text-muted-foreground", children: "Sable" }),
|
|
1480
|
-
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: "React components, patterns, and tokens for products built with AI agents." })
|
|
1481
|
-
] }),
|
|
1482
|
-
/* @__PURE__ */ jsx("nav", { "aria-label": "Footer", className: "grid grid-cols-2 gap-8 sm:grid-cols-3", children: footerGroups2.map((group) => /* @__PURE__ */ jsxs("div", { children: [
|
|
1483
|
-
/* @__PURE__ */ jsx("h2", { className: "text-label-md text-muted-foreground", children: group.title }),
|
|
1484
|
-
/* @__PURE__ */ jsx("ul", { className: "mt-3 flex flex-col gap-1", children: group.links.map((link) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
1485
|
-
"a",
|
|
1486
|
-
{
|
|
1487
|
-
href: link.href,
|
|
1488
|
-
className: "flex min-h-9 items-center text-body-sm motion-interactive hover:text-muted-foreground",
|
|
1489
|
-
children: link.label
|
|
1490
|
-
}
|
|
1491
|
-
) }, link.label)) })
|
|
1492
|
-
] }, group.title)) })
|
|
1493
|
-
] }),
|
|
1494
|
-
/* @__PURE__ */ jsx("div", { className: "mt-12 border-t border-border pt-6", children: /* @__PURE__ */ jsx("p", { className: "text-caption text-muted-foreground", children: "\xA9 2026 Sable. Released under the MIT license." }) })
|
|
1495
|
-
] }) })
|
|
1496
|
-
] });
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
// src/patterns/landing-page.meta.ts
|
|
1500
|
-
var landingPageMeta = {
|
|
1501
|
-
name: "LandingPage",
|
|
1502
|
-
summary: "Product / open-source landing page: a centered hero with an eyebrow Badge, one display headline, lede, two CTAs and a copyable install command; a Browser-framed product preview composed from real components; a six-up feature grid; a setup section with tabbed code; an agent-workflow section with terminal output; a CTA card; and a link-column footer.",
|
|
1503
|
-
intent: "Reach for this for the marketing route of a developer product, library, or tool: the page where the product shows itself working and the reader installs or signs up. For a branded, editorial marketing site use BrandLandingPage. Not for product UI (display type and one-per-view rules forbid it inside the app).",
|
|
1504
|
-
notes: [
|
|
1505
|
-
"Display type is the marketing voice: exactly one display headline per page (`text-display-lg` on phones, `text-display-2xl` from `sm`). Every section below uses `text-heading-1` with a `text-overline` kicker.",
|
|
1506
|
-
'The eyebrow is a small outline Badge (`size="sm"`): the badge\'s own size, never a type class forced onto its label. Hierarchy above the fold is eyebrow, display, lede, CTAs, install command.',
|
|
1507
|
-
"The lede is body-lg capped at the prose measure. The headline may be wide; reading text may not.",
|
|
1508
|
-
'Two CTAs max per row: one primary, one quiet outline. The install line is a `CodeBlock` with `prompt="$"`, so it truncates on one line and copy takes only the command.',
|
|
1509
|
-
"Show the product instead of describing it. The preview is real components (PageHeader, metric Cards, a Meter, a CardList of Entity rows, Message and Bubble, ChatComposer) inside `Browser`, wrapped in an `inert` div so the picture's controls never take focus. Secondary columns hide on small screens rather than stacking into a long mock.",
|
|
1510
|
-
"Features are Cards in a fixed `Grid cols={{ base: 1, sm: 2, lg: 3 }}`, so six items always land as full rows (3 x 2, 2 x 3, 1 x 6) without an orphan.",
|
|
1511
|
-
"Multi-line code uses `CodeBlock` with a filename; switch related snippets with `Tabs`, defaulting to the most complete one. Terminal output passes `highlight={false}` so a code highlighter does not colour prose.",
|
|
1512
|
-
"Reveal wraps below-the-fold blocks only (features, code, CTA). It no-ops under reduced motion via the motion policy.",
|
|
1513
|
-
"Hide nav groups on narrow screens by wrapping them in a `max-md:hidden` div: a `Row` or `Button` sets its own display, which a `hidden` class on the same element does not reliably override."
|
|
1514
|
-
],
|
|
1515
|
-
a11y: [
|
|
1516
|
-
"The hero headline is the page h1; section titles are h2s and feature and step titles are h3s. Footer column labels are h2s inside the footer nav.",
|
|
1517
|
-
"The product preview is a `figure` with a visible figcaption. Its content is `inert`, which removes it from the tab order and the accessibility tree, so its headings and buttons do not pollute the outline.",
|
|
1518
|
-
"Reveal renders content immediately (no animation) when prefers-reduced-motion is set.",
|
|
1519
|
-
"CTA buttons are real links with visible text, with no icon-only CTAs. Footer links keep a 36px minimum row height for touch."
|
|
1520
|
-
]
|
|
1521
|
-
};
|
|
1522
|
-
var roles = [
|
|
1523
|
-
{ value: "owner", label: "Owner" },
|
|
1524
|
-
{ value: "admin", label: "Admin" },
|
|
1525
|
-
{ value: "editor", label: "Editor" },
|
|
1526
|
-
{ value: "viewer", label: "Viewer" }
|
|
1527
|
-
];
|
|
1528
|
-
var roleLabel = {
|
|
1529
|
-
owner: "Owner",
|
|
1530
|
-
admin: "Admin",
|
|
1531
|
-
editor: "Editor",
|
|
1532
|
-
viewer: "Viewer"
|
|
1533
|
-
};
|
|
1534
|
-
var roleSummary = {
|
|
1535
|
-
owner: "Full access, including payouts and billing. Transfer ownership in studio settings.",
|
|
1536
|
-
admin: "Manages members, editions and payouts.",
|
|
1537
|
-
editor: "Drafts, prices and releases editions.",
|
|
1538
|
-
viewer: "Sees editions and sales, but can't change them."
|
|
1539
|
-
};
|
|
1540
|
-
var seats = 10;
|
|
1541
|
-
var initialMembers = [
|
|
1542
|
-
{
|
|
1543
|
-
id: "u_01",
|
|
1544
|
-
name: "Ada Lovelace",
|
|
1545
|
-
handle: "@ada",
|
|
1546
|
-
email: "ada@atelier.art",
|
|
1547
|
-
initials: "AL",
|
|
1548
|
-
role: "owner",
|
|
1549
|
-
pending: false,
|
|
1550
|
-
joined: "Mar 4, 2024",
|
|
1551
|
-
lastActive: "Online now",
|
|
1552
|
-
editions: 12,
|
|
1553
|
-
activity: [
|
|
1554
|
-
{ id: "a1", icon: "gem", text: "Released Analytical Engine No. 3 \u2014 edition of 50", when: "2 hours ago" },
|
|
1555
|
-
{ id: "a2", icon: "mail", text: "Invited louise@bourgeois.studio as Editor", when: "2 days ago" }
|
|
1556
|
-
]
|
|
1557
|
-
},
|
|
1558
|
-
{
|
|
1559
|
-
id: "u_02",
|
|
1560
|
-
name: "Grace Hopper",
|
|
1561
|
-
handle: "@grace",
|
|
1562
|
-
email: "grace@atelier.art",
|
|
1563
|
-
initials: "GH",
|
|
1564
|
-
role: "admin",
|
|
1565
|
-
pending: false,
|
|
1566
|
-
joined: "Mar 9, 2024",
|
|
1567
|
-
lastActive: "20 minutes ago",
|
|
1568
|
-
editions: 4,
|
|
1569
|
-
activity: [
|
|
1570
|
-
{ id: "a1", icon: "user", text: "Changed Frida Kahlo's role to Viewer", when: "Yesterday" },
|
|
1571
|
-
{ id: "a2", icon: "edit", text: "Updated pricing on First Compiler \u2014 edition of 10", when: "3 days ago" },
|
|
1572
|
-
{ id: "a3", icon: "gem", text: "Released First Compiler \u2014 edition of 10", when: "Sep 28" }
|
|
1573
|
-
]
|
|
1574
|
-
},
|
|
1575
|
-
{
|
|
1576
|
-
id: "u_03",
|
|
1577
|
-
name: "Mary Shelley",
|
|
1578
|
-
handle: "@mary",
|
|
1579
|
-
email: "mary@atelier.art",
|
|
1580
|
-
initials: "MS",
|
|
1581
|
-
role: "editor",
|
|
1582
|
-
pending: false,
|
|
1583
|
-
joined: "Jun 18, 2024",
|
|
1584
|
-
lastActive: "3 hours ago",
|
|
1585
|
-
editions: 7,
|
|
1586
|
-
activity: [
|
|
1587
|
-
{ id: "a1", icon: "calendar", text: "Scheduled Frankenstein Studies for Oct 14", when: "3 hours ago" },
|
|
1588
|
-
{ id: "a2", icon: "gem", text: "Released The Last Man \u2014 edition of 25", when: "Sep 21" }
|
|
1589
|
-
]
|
|
1590
|
-
},
|
|
1591
|
-
{
|
|
1592
|
-
id: "u_04",
|
|
1593
|
-
name: "Nina Simone",
|
|
1594
|
-
handle: "@nina",
|
|
1595
|
-
email: "nina@atelier.art",
|
|
1596
|
-
initials: "NS",
|
|
1597
|
-
role: "editor",
|
|
1598
|
-
pending: false,
|
|
1599
|
-
joined: "Jan 12, 2025",
|
|
1600
|
-
lastActive: "Yesterday",
|
|
1601
|
-
editions: 3,
|
|
1602
|
-
activity: [
|
|
1603
|
-
{ id: "a1", icon: "upload", text: "Uploaded artwork for Little Girl Blue", when: "Yesterday" },
|
|
1604
|
-
{ id: "a2", icon: "gem", text: "Released Feeling Good \u2014 edition of 100", when: "Aug 30" }
|
|
1605
|
-
]
|
|
1606
|
-
},
|
|
1607
|
-
{
|
|
1608
|
-
id: "u_05",
|
|
1609
|
-
name: "Frida Kahlo",
|
|
1610
|
-
handle: "@frida",
|
|
1611
|
-
email: "frida@atelier.art",
|
|
1612
|
-
initials: "FK",
|
|
1613
|
-
role: "viewer",
|
|
1614
|
-
pending: false,
|
|
1615
|
-
joined: "Jul 2, 2025",
|
|
1616
|
-
lastActive: "4 days ago",
|
|
1617
|
-
editions: 0,
|
|
1618
|
-
activity: [{ id: "a1", icon: "comment", text: "Commented on the Las Dos Fridas draft", when: "4 days ago" }]
|
|
1619
|
-
},
|
|
1620
|
-
{
|
|
1621
|
-
id: "i_01",
|
|
1622
|
-
name: "louise@bourgeois.studio",
|
|
1623
|
-
email: "louise@bourgeois.studio",
|
|
1624
|
-
initials: "",
|
|
1625
|
-
role: "editor",
|
|
1626
|
-
pending: true,
|
|
1627
|
-
invitedBy: "Ada Lovelace",
|
|
1628
|
-
sent: "2 days ago",
|
|
1629
|
-
expires: "In 5 days",
|
|
1630
|
-
activity: []
|
|
1631
|
-
}
|
|
1632
|
-
];
|
|
1633
|
-
function ListDetailPage() {
|
|
1634
|
-
const [members, setMembers] = useState(initialMembers);
|
|
1635
|
-
const [selectedId, setSelectedId] = useState("u_02");
|
|
1636
|
-
const [query, setQuery] = useState("");
|
|
1637
|
-
const [showDetail, setShowDetail] = useState(false);
|
|
1638
|
-
const [confirming, setConfirming] = useState(false);
|
|
1639
|
-
const listRef = useRef(null);
|
|
1640
|
-
const backRef = useRef(null);
|
|
1641
|
-
const searchRef = useRef(null);
|
|
1642
|
-
const needle = query.trim().toLowerCase();
|
|
1643
|
-
const visible = members.filter(
|
|
1644
|
-
(m) => [m.name, m.email, m.handle ?? "", roleLabel[m.role]].some((v) => v.toLowerCase().includes(needle))
|
|
1645
|
-
);
|
|
1646
|
-
const selected = members.find((m) => m.id === selectedId) ?? null;
|
|
1647
|
-
const active = members.filter((m) => !m.pending).length;
|
|
1648
|
-
const pending2 = members.length - active;
|
|
1649
|
-
useEffect(() => {
|
|
1650
|
-
if (showDetail && backRef.current?.offsetParent) backRef.current.focus();
|
|
1651
|
-
}, [showDetail, selectedId]);
|
|
1652
|
-
function select(id) {
|
|
1653
|
-
setSelectedId(id);
|
|
1654
|
-
setShowDetail(true);
|
|
1655
|
-
}
|
|
1656
|
-
function focusRow(id) {
|
|
1657
|
-
requestAnimationFrame(() => {
|
|
1658
|
-
listRef.current?.querySelector(`[data-member-id="${id}"] button`)?.focus();
|
|
1659
|
-
});
|
|
1660
|
-
}
|
|
1661
|
-
function backToList() {
|
|
1662
|
-
setShowDetail(false);
|
|
1663
|
-
focusRow(selectedId ?? void 0);
|
|
1664
|
-
}
|
|
1665
|
-
function setRole(id, role) {
|
|
1666
|
-
setMembers((list) => list.map((m) => m.id === id ? { ...m, role } : m));
|
|
1667
|
-
}
|
|
1668
|
-
function resend(id) {
|
|
1669
|
-
setMembers(
|
|
1670
|
-
(list) => list.map((m) => m.id === id ? { ...m, sent: "Just now", expires: "In 7 days", resent: true } : m)
|
|
1671
|
-
);
|
|
1672
|
-
}
|
|
1673
|
-
function remove(id) {
|
|
1674
|
-
const index = members.findIndex((m) => m.id === id);
|
|
1675
|
-
const rest = members.filter((m) => m.id !== id);
|
|
1676
|
-
setMembers(rest);
|
|
1677
|
-
const next = rest[Math.min(index, rest.length - 1)]?.id;
|
|
1678
|
-
setSelectedId(next ?? null);
|
|
1679
|
-
setShowDetail(false);
|
|
1680
|
-
focusRow(next);
|
|
1681
|
-
}
|
|
1682
|
-
return /* @__PURE__ */ jsxs(Page, { as: "main", max: "wide", gap: 4, className: "py-6", children: [
|
|
1683
|
-
/* @__PURE__ */ jsx(
|
|
1684
|
-
PageHeader,
|
|
1685
|
-
{
|
|
1686
|
-
title: "Members",
|
|
1687
|
-
description: `${active} members \xB7 ${pending2} pending ${pending2 === 1 ? "invite" : "invites"} \xB7 ${members.length} of ${seats} seats used`,
|
|
1688
|
-
actions: /* @__PURE__ */ jsx(Button, { iconStart: /* @__PURE__ */ jsx(Icon, { name: "plus" }), children: "Invite member" })
|
|
1689
|
-
}
|
|
1690
|
-
),
|
|
1691
|
-
/* @__PURE__ */ jsxs(Columns, { count: { base: 1, lg: 12 }, children: [
|
|
1692
|
-
/* @__PURE__ */ jsx(Col, { span: { base: 1, lg: 5 }, className: showDetail ? "max-lg:hidden" : void 0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1693
|
-
/* @__PURE__ */ jsx(
|
|
1694
|
-
SearchInput,
|
|
1695
|
-
{
|
|
1696
|
-
ref: searchRef,
|
|
1697
|
-
value: query,
|
|
1698
|
-
onValueChange: setQuery,
|
|
1699
|
-
placeholder: "Search name, email or role",
|
|
1700
|
-
"aria-label": "Search members"
|
|
1701
|
-
}
|
|
1702
|
-
),
|
|
1703
|
-
/* @__PURE__ */ jsx(Card, { ref: listRef, className: "sable-surface", children: visible.length > 0 ? /* @__PURE__ */ jsx(CardList, { "aria-label": "Members", children: visible.map((m) => {
|
|
1704
|
-
const isSelected = m.id === selectedId;
|
|
1705
|
-
return /* @__PURE__ */ jsx(
|
|
1706
|
-
Entity,
|
|
1707
|
-
{
|
|
1708
|
-
"data-member-id": m.id,
|
|
1709
|
-
interactive: true,
|
|
1710
|
-
selected: isSelected,
|
|
1711
|
-
onSelect: () => select(m.id),
|
|
1712
|
-
leading: /* @__PURE__ */ jsx(
|
|
1713
|
-
Avatar,
|
|
1714
|
-
{
|
|
1715
|
-
variant: "art",
|
|
1716
|
-
seed: m.name,
|
|
1717
|
-
fallback: m.pending ? /* @__PURE__ */ jsx(Icon, { name: "mail", className: "size-5" }) : m.initials,
|
|
1718
|
-
status: m.lastActive === "Online now" ? /* @__PURE__ */ jsx(StatusDot, { tone: "success" }) : void 0,
|
|
1719
|
-
"aria-hidden": true
|
|
1720
|
-
}
|
|
1721
|
-
),
|
|
1722
|
-
title: m.name,
|
|
1723
|
-
subtitle: m.pending ? `${roleLabel[m.role]} \xB7 invite sent ${m.sent?.toLowerCase()}` : `${roleLabel[m.role]} \xB7 ${m.handle}`,
|
|
1724
|
-
action: m.pending ? /* @__PURE__ */ jsx(Badge, { tone: "info", children: "Invited" }) : void 0
|
|
1725
|
-
},
|
|
1726
|
-
m.id
|
|
1727
|
-
);
|
|
1728
|
-
}) }) : /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(
|
|
1729
|
-
EmptyState,
|
|
1730
|
-
{
|
|
1731
|
-
icon: /* @__PURE__ */ jsx(Icon, { name: "search" }),
|
|
1732
|
-
title: "No members found",
|
|
1733
|
-
description: `Nobody matches \u201C${query.trim()}\u201D. Try a name, email or role.`,
|
|
1734
|
-
action: /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", onClick: () => {
|
|
1735
|
-
setQuery("");
|
|
1736
|
-
searchRef.current?.focus();
|
|
1737
|
-
}, children: "Clear search" })
|
|
1738
|
-
}
|
|
1739
|
-
) }) })
|
|
1740
|
-
] }) }),
|
|
1741
|
-
/* @__PURE__ */ jsx(Col, { span: { base: 1, lg: 7 }, className: showDetail ? void 0 : "max-lg:hidden", children: /* @__PURE__ */ jsxs(Stack, { role: "region", "aria-label": "Member details", gap: 3, className: "lg:sticky lg:top-6", children: [
|
|
1742
|
-
/* @__PURE__ */ jsx(
|
|
1743
|
-
Button,
|
|
1744
|
-
{
|
|
1745
|
-
ref: backRef,
|
|
1746
|
-
variant: "ghost",
|
|
1747
|
-
size: "sm",
|
|
1748
|
-
iconStart: /* @__PURE__ */ jsx(Icon, { name: "chevron-left" }),
|
|
1749
|
-
onClick: backToList,
|
|
1750
|
-
className: "self-start lg:hidden",
|
|
1751
|
-
children: "All members"
|
|
1752
|
-
}
|
|
1753
|
-
),
|
|
1754
|
-
selected === null ? /* @__PURE__ */ jsx(
|
|
1755
|
-
EmptyState,
|
|
1756
|
-
{
|
|
1757
|
-
surface: "card",
|
|
1758
|
-
icon: /* @__PURE__ */ jsx(Icon, { name: "user" }),
|
|
1759
|
-
title: "No member selected",
|
|
1760
|
-
description: "Choose someone from the list to see their role and activity."
|
|
1761
|
-
}
|
|
1762
|
-
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1763
|
-
/* @__PURE__ */ jsxs(Card, { children: [
|
|
1764
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
1765
|
-
/* @__PURE__ */ jsxs(Row, { gap: 2, children: [
|
|
1766
|
-
/* @__PURE__ */ jsx(
|
|
1767
|
-
Avatar,
|
|
1768
|
-
{
|
|
1769
|
-
variant: "art",
|
|
1770
|
-
size: "lg",
|
|
1771
|
-
fallback: selected.pending ? /* @__PURE__ */ jsx(Icon, { name: "mail", className: "size-6" }) : selected.initials,
|
|
1772
|
-
status: selected.lastActive === "Online now" ? /* @__PURE__ */ jsx(StatusDot, { tone: "success" }) : void 0,
|
|
1773
|
-
alt: selected.pending ? "Pending invite" : selected.name
|
|
1774
|
-
}
|
|
1775
|
-
),
|
|
1776
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 0, className: "min-w-0", children: [
|
|
1777
|
-
/* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), className: "truncate", children: selected.name }),
|
|
1778
|
-
/* @__PURE__ */ jsx(CardDescription, { className: "truncate", children: selected.pending ? "Hasn't joined yet" : `${selected.handle} \xB7 ${selected.email}` })
|
|
1779
|
-
] })
|
|
1780
|
-
] }),
|
|
1781
|
-
selected.pending ? /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Badge, { tone: "info", children: "Invited" }) }) : null
|
|
1782
|
-
] }),
|
|
1783
|
-
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Stack, { gap: 5, children: [
|
|
1784
|
-
/* @__PURE__ */ jsx(
|
|
1785
|
-
Field,
|
|
1786
|
-
{
|
|
1787
|
-
label: selected.pending ? "Role on joining" : "Role",
|
|
1788
|
-
description: roleSummary[selected.role],
|
|
1789
|
-
children: /* @__PURE__ */ jsxs(
|
|
1790
|
-
Select,
|
|
1791
|
-
{
|
|
1792
|
-
items: roles,
|
|
1793
|
-
value: selected.role,
|
|
1794
|
-
onValueChange: (value) => setRole(selected.id, value),
|
|
1795
|
-
disabled: selected.role === "owner",
|
|
1796
|
-
children: [
|
|
1797
|
-
/* @__PURE__ */ jsx(SelectTrigger, { className: "max-w-72" }),
|
|
1798
|
-
/* @__PURE__ */ jsx(SelectContent, { children: roles.filter((r) => r.value !== "owner" || selected.role === "owner").map((r) => /* @__PURE__ */ jsx(SelectItem, { value: r.value, children: r.label }, r.value)) })
|
|
1799
|
-
]
|
|
1800
|
-
}
|
|
1801
|
-
)
|
|
1802
|
-
}
|
|
1803
|
-
),
|
|
1804
|
-
/* @__PURE__ */ jsx("div", { className: "@container", children: selected.pending ? /* @__PURE__ */ jsxs("dl", { className: "sable-list-dl sable-list-dl-2", children: [
|
|
1805
|
-
/* @__PURE__ */ jsx("dt", { children: "Sent" }),
|
|
1806
|
-
/* @__PURE__ */ jsx("dd", { children: selected.sent }),
|
|
1807
|
-
/* @__PURE__ */ jsx("dt", { children: "Expires" }),
|
|
1808
|
-
/* @__PURE__ */ jsx("dd", { children: selected.expires }),
|
|
1809
|
-
/* @__PURE__ */ jsx("dt", { children: "Invited by" }),
|
|
1810
|
-
/* @__PURE__ */ jsx("dd", { children: selected.invitedBy })
|
|
1811
|
-
] }) : /* @__PURE__ */ jsxs("dl", { className: "sable-list-dl sable-list-dl-2", children: [
|
|
1812
|
-
/* @__PURE__ */ jsx("dt", { children: "Email" }),
|
|
1813
|
-
/* @__PURE__ */ jsx("dd", { children: selected.email }),
|
|
1814
|
-
/* @__PURE__ */ jsx("dt", { children: "Joined" }),
|
|
1815
|
-
/* @__PURE__ */ jsx("dd", { children: selected.joined }),
|
|
1816
|
-
/* @__PURE__ */ jsx("dt", { children: "Last active" }),
|
|
1817
|
-
/* @__PURE__ */ jsx("dd", { className: selected.lastActive === "Online now" ? "text-success" : void 0, children: selected.lastActive }),
|
|
1818
|
-
/* @__PURE__ */ jsx("dt", { children: "Editions released" }),
|
|
1819
|
-
/* @__PURE__ */ jsx("dd", { children: selected.editions })
|
|
1820
|
-
] }) })
|
|
1821
|
-
] }) }),
|
|
1822
|
-
selected.role !== "owner" ? /* @__PURE__ */ jsx(CardFooter, { children: /* @__PURE__ */ jsxs(Row, { gap: 2, wrap: true, children: [
|
|
1823
|
-
selected.pending ? /* @__PURE__ */ jsx(
|
|
1824
|
-
Button,
|
|
1825
|
-
{
|
|
1826
|
-
variant: "secondary",
|
|
1827
|
-
size: "sm",
|
|
1828
|
-
iconStart: /* @__PURE__ */ jsx(Icon, { name: selected.resent ? "check" : "send" }),
|
|
1829
|
-
disabled: selected.resent,
|
|
1830
|
-
onClick: () => resend(selected.id),
|
|
1831
|
-
children: selected.resent ? "Invite resent" : "Resend invite"
|
|
1832
|
-
}
|
|
1833
|
-
) : null,
|
|
1834
|
-
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setConfirming(true), children: selected.pending ? "Revoke invite" : "Remove from studio" })
|
|
1835
|
-
] }) }) : null
|
|
1836
|
-
] }),
|
|
1837
|
-
selected.activity.length > 0 ? /* @__PURE__ */ jsxs(Card, { children: [
|
|
1838
|
-
/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h3", {}), children: "Recent activity" }) }),
|
|
1839
|
-
/* @__PURE__ */ jsx(CardList, { children: selected.activity.map((a) => /* @__PURE__ */ jsx(
|
|
1840
|
-
Entity,
|
|
1841
|
-
{
|
|
1842
|
-
leading: /* @__PURE__ */ jsx(Icon, { name: a.icon, className: "text-muted-foreground" }),
|
|
1843
|
-
title: a.text,
|
|
1844
|
-
subtitle: a.when
|
|
1845
|
-
},
|
|
1846
|
-
a.id
|
|
1847
|
-
)) })
|
|
1848
|
-
] }) : null,
|
|
1849
|
-
/* @__PURE__ */ jsx(
|
|
1850
|
-
DestructiveActionModal,
|
|
1851
|
-
{
|
|
1852
|
-
open: confirming,
|
|
1853
|
-
onOpenChange: setConfirming,
|
|
1854
|
-
title: selected.pending ? "Revoke this invite?" : `Remove ${selected.name}?`,
|
|
1855
|
-
description: selected.pending ? `${selected.email} won't be able to join with the link they were sent.` : "They lose access to the studio right away. Editions they released stay on sale.",
|
|
1856
|
-
confirmLabel: selected.pending ? "Revoke invite" : "Remove member",
|
|
1857
|
-
onConfirm: () => remove(selected.id)
|
|
1858
|
-
}
|
|
1859
|
-
)
|
|
1860
|
-
] })
|
|
1861
|
-
] }) })
|
|
1862
|
-
] })
|
|
1863
|
-
] });
|
|
1864
|
-
}
|
|
1865
|
-
|
|
1866
|
-
// src/patterns/list-detail-page.meta.ts
|
|
1867
|
-
var listDetailPageMeta = {
|
|
1868
|
-
name: "ListDetailPage",
|
|
1869
|
-
summary: "Searchable list beside the selected record's detail: interactive Entity rows in a CardList (5 of 12 columns) and a sticky detail pane (7) with an editable field, a label/value list, record actions with a destructive confirmation, and recent activity. Below lg it becomes a drill-in.",
|
|
1870
|
-
intent: "Reach for this when a route lists records AND works on one at a time (members + member, inbox + message, orders + order). For pure browsing with no selection, a single-column FeedPage or a DashboardPage table is simpler.",
|
|
1871
|
-
notes: [
|
|
1872
|
-
"Region cap is `wide`; Columns 12 \u2192 Col 5 (list) + Col 7 (detail). Below lg the list and detail share one column as a drill-in: selecting a row shows the detail with an `All members` back button (lg:hidden), and the back button returns to the list. The panes swap with `max-lg:hidden`, so desktop always shows both.",
|
|
1873
|
-
"Rows are interactive Entity inside one Card + CardList. Selection is Entity `selected`; `sable-surface` on the list card makes its selected step 12% ink off the card, so a selected row stays distinct from its muted avatar.",
|
|
1874
|
-
"Search filters the list in place (name, email, role). No matches swap the CardList for an EmptyState with a Clear search action; the selected detail stays open.",
|
|
1875
|
-
"A pending invite lives in the same list: a mail glyph in the Avatar, an `Invited` Badge, and an invite-specific detail (role on joining, sent, expires, invited by; Resend and Revoke actions).",
|
|
1876
|
-
"The role Select edits the record in place; its Field description states what the role can do. The owner's role is read-only and the owner has no remove action.",
|
|
1877
|
-
'Read-only facts are a native `<dl className="sable-list-dl sable-list-dl-2">` inside an `@container`, so pairs go two-up only when the pane is wide enough.',
|
|
1878
|
-
"Remove / revoke run through DestructiveActionModal; on confirm the next row is selected. Page-level counts (members, pending, seats) live in the PageHeader description and update with the list.",
|
|
1879
|
-
"The detail pane is sticky (lg:top-6) so it stays in view while a long list scrolls."
|
|
1880
|
-
],
|
|
1881
|
-
a11y: [
|
|
1882
|
-
"Each row is a real button (Entity `interactive` + `onSelect`): Tab moves through rows, Enter/Space selects. The selected row is Entity `selected`, which paints the selected fill and sets aria-current; list avatars are aria-hidden so initials are not read before the name.",
|
|
1883
|
-
"The detail pane is a labelled region (`Member details`) whose title is a level-2 heading.",
|
|
1884
|
-
"On a phone, drilling in moves focus to the back button; going back returns focus to the row that was open. After a remove, focus lands on the newly selected row; after Clear search, on the search field."
|
|
1885
|
-
]
|
|
1886
|
-
};
|
|
1887
|
-
var sections = [
|
|
1888
|
-
{ id: "profile", label: "Profile" },
|
|
1889
|
-
{ id: "notifications", label: "Notifications" },
|
|
1890
|
-
{ id: "payouts", label: "Payouts" },
|
|
1891
|
-
{ id: "account", label: "Account" }
|
|
1892
|
-
];
|
|
1893
|
-
var timezones = [
|
|
1894
|
-
{ value: "europe-london", label: "London (GMT+1)" },
|
|
1895
|
-
{ value: "europe-paris", label: "Paris (GMT+2)" },
|
|
1896
|
-
{ value: "america-new-york", label: "New York (GMT\u22124)" },
|
|
1897
|
-
{ value: "america-los-angeles", label: "Los Angeles (GMT\u22127)" },
|
|
1898
|
-
{ value: "asia-tokyo", label: "Tokyo (GMT+9)" }
|
|
1899
|
-
];
|
|
1900
|
-
var notifications = [
|
|
1901
|
-
{ name: "notifyCollects", title: "New collects", description: "When someone collects your work", on: true },
|
|
1902
|
-
{ name: "notifySoldOut", title: "Sold out", description: "When an edition sells its last copy", on: true },
|
|
1903
|
-
{ name: "notifyFollowers", title: "New followers", description: "When a collector follows you", on: false },
|
|
1904
|
-
{ name: "notifyDigest", title: "Weekly digest", description: "Sales and collectors, every Monday", on: true },
|
|
1905
|
-
{ name: "notifyProduct", title: "Product updates", description: "New Atelier features", on: false }
|
|
1906
|
-
];
|
|
1907
|
-
var payouts = [
|
|
1908
|
-
{ label: "Next payout", value: "$1,284.60", note: "Friday, Oct 16" },
|
|
1909
|
-
{ label: "Pending", value: "$412.00", note: "Clears in 7 days" },
|
|
1910
|
-
{ label: "Paid this year", value: "$18,920.00", note: "Across 214 collects" }
|
|
1911
|
-
];
|
|
1912
|
-
function SettingsPage() {
|
|
1913
|
-
const [activeSection, setActiveSection] = useState("profile");
|
|
1914
|
-
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
1915
|
-
return /* @__PURE__ */ jsxs(
|
|
1916
|
-
AppShell,
|
|
1917
|
-
{
|
|
1918
|
-
header: /* @__PURE__ */ jsx(
|
|
1919
|
-
AppHeader,
|
|
1920
|
-
{
|
|
1921
|
-
title: /* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", children: [
|
|
1922
|
-
/* @__PURE__ */ jsx(Icon, { name: "gem", variant: "solid", className: "text-xl" }),
|
|
1923
|
-
/* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" })
|
|
1924
|
-
] }),
|
|
1925
|
-
actions: /* @__PURE__ */ jsx(Avatar, { variant: "art", size: "sm", fallback: "AL", alt: "Ada Lovelace" })
|
|
1926
|
-
}
|
|
1927
|
-
),
|
|
1928
|
-
bottomNav: /* @__PURE__ */ jsxs(BottomNav, { "aria-label": "Primary", children: [
|
|
1929
|
-
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "grid" }), label: "Overview", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/" }) }),
|
|
1930
|
-
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }), label: "Editions", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/editions" }) }),
|
|
1931
|
-
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "user" }), label: "Collectors", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/collectors" }) }),
|
|
1932
|
-
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "bell" }), label: "Activity", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/activity" }) }),
|
|
1933
|
-
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "gear" }), label: "Settings", showLabel: true, active: true, render: /* @__PURE__ */ jsx("a", { href: "/settings" }) })
|
|
1934
|
-
] }),
|
|
1935
|
-
sidebar: /* @__PURE__ */ jsxs(Sidebar, { children: [
|
|
1936
|
-
/* @__PURE__ */ jsx(SidebarHeader, { children: /* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", className: "px-3", children: [
|
|
1937
|
-
/* @__PURE__ */ jsx(Icon, { name: "gem", variant: "solid", className: "text-xl" }),
|
|
1938
|
-
/* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" })
|
|
1939
|
-
] }) }),
|
|
1940
|
-
/* @__PURE__ */ jsxs(SidebarNav, { children: [
|
|
1941
|
-
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "grid" }), label: "Overview", render: /* @__PURE__ */ jsx("a", { href: "/" }) }),
|
|
1942
|
-
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }), label: "Editions", render: /* @__PURE__ */ jsx("a", { href: "/editions" }) }),
|
|
1943
|
-
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "user" }), label: "Collectors", render: /* @__PURE__ */ jsx("a", { href: "/collectors" }) }),
|
|
1944
|
-
/* @__PURE__ */ jsx(
|
|
1945
|
-
SidebarItem,
|
|
1946
|
-
{
|
|
1947
|
-
icon: /* @__PURE__ */ jsx(Icon, { name: "bell" }),
|
|
1948
|
-
label: "Activity",
|
|
1949
|
-
badge: /* @__PURE__ */ jsx(NotificationBadge, { count: 4, tone: "neutral" }),
|
|
1950
|
-
render: /* @__PURE__ */ jsx("a", { href: "/activity" })
|
|
1951
|
-
}
|
|
1952
|
-
),
|
|
1953
|
-
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "gear" }), label: "Settings", active: true, render: /* @__PURE__ */ jsx("a", { href: "/settings" }) })
|
|
1954
|
-
] }),
|
|
1955
|
-
/* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsx(
|
|
1956
|
-
Entity,
|
|
1957
|
-
{
|
|
1958
|
-
className: "[--entity-py:0]",
|
|
1959
|
-
leading: /* @__PURE__ */ jsx(Avatar, { variant: "art", size: "sm", fallback: "AL", alt: "" }),
|
|
1960
|
-
title: "Ada Lovelace",
|
|
1961
|
-
subtitle: "@ada",
|
|
1962
|
-
action: /* @__PURE__ */ jsxs(OverflowMenu, { label: "Account", size: "icon-sm", children: [
|
|
1963
|
-
/* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/@ada" }), children: "View profile" }),
|
|
1964
|
-
/* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/settings" }), children: "Settings" }),
|
|
1965
|
-
/* @__PURE__ */ jsx(MenuSeparator, {}),
|
|
1966
|
-
/* @__PURE__ */ jsx(MenuItem, { children: "Sign out" })
|
|
1967
|
-
] })
|
|
1968
|
-
}
|
|
1969
|
-
) })
|
|
1970
|
-
] }),
|
|
1971
|
-
children: [
|
|
1972
|
-
/* @__PURE__ */ jsxs(Page, { max: "wide", gap: 4, className: "py-6", children: [
|
|
1973
|
-
/* @__PURE__ */ jsx(
|
|
1974
|
-
PageHeader,
|
|
1975
|
-
{
|
|
1976
|
-
title: "Settings",
|
|
1977
|
-
description: "Manage your profile, notifications, and payouts."
|
|
1978
|
-
}
|
|
1979
|
-
),
|
|
1980
|
-
/* @__PURE__ */ jsxs(Columns, { gap: 4, children: [
|
|
1981
|
-
/* @__PURE__ */ jsx(Col, { span: { base: 12, lg: 3 }, className: "hidden lg:block", children: /* @__PURE__ */ jsx(SideNav, { "aria-label": "Settings sections", className: "sticky top-6", children: sections.map((section) => /* @__PURE__ */ jsx(
|
|
1982
|
-
SideNavItem,
|
|
1983
|
-
{
|
|
1984
|
-
label: section.label,
|
|
1985
|
-
active: activeSection === section.id,
|
|
1986
|
-
onClick: () => setActiveSection(section.id),
|
|
1987
|
-
render: /* @__PURE__ */ jsx("a", { href: `#${section.id}` })
|
|
1988
|
-
},
|
|
1989
|
-
section.id
|
|
1990
|
-
)) }) }),
|
|
1991
|
-
/* @__PURE__ */ jsx(Col, { span: { base: 12, lg: 9 }, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1992
|
-
/* @__PURE__ */ jsxs(Card, { id: "profile", className: "scroll-mt-6", children: [
|
|
1993
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
1994
|
-
/* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), children: "Profile" }),
|
|
1995
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "How collectors see you on Atelier." })
|
|
1996
|
-
] }),
|
|
1997
|
-
/* @__PURE__ */ jsxs(Form, { onSubmit: (event) => event.preventDefault(), className: "gap-0", children: [
|
|
1998
|
-
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1999
|
-
/* @__PURE__ */ jsxs(Row, { gap: 2, children: [
|
|
2000
|
-
/* @__PURE__ */ jsx(Avatar, { variant: "art", size: "lg", fallback: "AL", alt: "Ada Lovelace" }),
|
|
2001
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 0.5, align: "start", children: [
|
|
2002
|
-
/* @__PURE__ */ jsxs(Row, { gap: 1, children: [
|
|
2003
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", children: "Upload photo" }),
|
|
2004
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", children: "Remove" })
|
|
2005
|
-
] }),
|
|
2006
|
-
/* @__PURE__ */ jsx("p", { className: "text-caption text-muted-foreground", children: "Square PNG or JPG, at least 400 px." })
|
|
2007
|
-
] })
|
|
2008
|
-
] }),
|
|
2009
|
-
/* @__PURE__ */ jsxs(Grid, { cols: { base: 1, sm: 2 }, gap: 3, children: [
|
|
2010
|
-
/* @__PURE__ */ jsx(Field, { label: "Display name", name: "displayName", required: true, children: /* @__PURE__ */ jsx(Input, { defaultValue: "Ada Lovelace", autoComplete: "name" }) }),
|
|
2011
|
-
/* @__PURE__ */ jsx(Field, { label: "Handle", name: "handle", description: "atelier.com/@ada", required: true, children: /* @__PURE__ */ jsx(Input, { defaultValue: "ada", autoComplete: "username", spellCheck: false }) })
|
|
2012
|
-
] }),
|
|
2013
|
-
/* @__PURE__ */ jsx(Field, { label: "Bio", name: "bio", description: "Shown on your profile and edition pages.", children: /* @__PURE__ */ jsx(
|
|
2014
|
-
Textarea,
|
|
2015
|
-
{
|
|
2016
|
-
rows: 3,
|
|
2017
|
-
defaultValue: "Generative artist working in plotted ink and code. New editions of 25 to 100 every month."
|
|
2018
|
-
}
|
|
2019
|
-
) }),
|
|
2020
|
-
/* @__PURE__ */ jsx(Grid, { cols: { base: 1, sm: 2 }, gap: 3, children: /* @__PURE__ */ jsx(Field, { label: "Timezone", name: "timezone", description: "Release times use this zone.", children: /* @__PURE__ */ jsxs(Select, { defaultValue: "europe-london", items: timezones, children: [
|
|
2021
|
-
/* @__PURE__ */ jsx(SelectTrigger, {}),
|
|
2022
|
-
/* @__PURE__ */ jsx(SelectContent, { children: timezones.map((zone) => /* @__PURE__ */ jsx(SelectItem, { value: zone.value, children: zone.label }, zone.value)) })
|
|
2023
|
-
] }) }) })
|
|
2024
|
-
] }) }),
|
|
2025
|
-
/* @__PURE__ */ jsx(CardFooter, { className: "justify-end", children: /* @__PURE__ */ jsx(Button, { type: "submit", children: "Save profile" }) })
|
|
2026
|
-
] })
|
|
2027
|
-
] }),
|
|
2028
|
-
/* @__PURE__ */ jsxs(Card, { id: "notifications", className: "scroll-mt-6", children: [
|
|
2029
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
2030
|
-
/* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), children: "Notifications" }),
|
|
2031
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Email updates about your editions. Changes save automatically." })
|
|
2032
|
-
] }),
|
|
2033
|
-
/* @__PURE__ */ jsx(CardList, { children: notifications.map((item) => /* @__PURE__ */ jsx(
|
|
2034
|
-
Entity,
|
|
2035
|
-
{
|
|
2036
|
-
title: item.title,
|
|
2037
|
-
subtitle: item.description,
|
|
2038
|
-
action: /* @__PURE__ */ jsx(
|
|
2039
|
-
Switch,
|
|
2040
|
-
{
|
|
2041
|
-
name: item.name,
|
|
2042
|
-
defaultChecked: item.on,
|
|
2043
|
-
"aria-label": item.title
|
|
2044
|
-
}
|
|
2045
|
-
)
|
|
2046
|
-
},
|
|
2047
|
-
item.name
|
|
2048
|
-
)) })
|
|
2049
|
-
] }),
|
|
2050
|
-
/* @__PURE__ */ jsxs(Card, { id: "payouts", className: "scroll-mt-6", children: [
|
|
2051
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
2052
|
-
/* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), children: "Payouts" }),
|
|
2053
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Paid in USD every Friday to Chase checking \u2022\u20224821." }),
|
|
2054
|
-
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/settings/payouts" }), children: "Manage payouts" }) })
|
|
2055
|
-
] }),
|
|
2056
|
-
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(Grid, { cols: { base: 1, sm: 3 }, gap: 3, children: payouts.map((stat) => /* @__PURE__ */ jsxs(Stack, { gap: 0.5, children: [
|
|
2057
|
-
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: stat.label }),
|
|
2058
|
-
/* @__PURE__ */ jsx("p", { className: "text-mono-lg text-foreground", children: stat.value }),
|
|
2059
|
-
/* @__PURE__ */ jsx("p", { className: "text-caption text-muted-foreground", children: stat.note })
|
|
2060
|
-
] }, stat.label)) }) })
|
|
2061
|
-
] }),
|
|
2062
|
-
/* @__PURE__ */ jsxs(Card, { id: "account", className: "scroll-mt-6", children: [
|
|
2063
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
2064
|
-
/* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), children: "Account" }),
|
|
2065
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Sign-in details and your data." })
|
|
2066
|
-
] }),
|
|
2067
|
-
/* @__PURE__ */ jsxs(CardList, { children: [
|
|
2068
|
-
/* @__PURE__ */ jsx(
|
|
2069
|
-
Entity,
|
|
2070
|
-
{
|
|
2071
|
-
leading: /* @__PURE__ */ jsx(Icon, { name: "mail", className: "text-lg text-muted-foreground" }),
|
|
2072
|
-
title: "Email",
|
|
2073
|
-
subtitle: "ada@atelier.art",
|
|
2074
|
-
action: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: "Change" })
|
|
2075
|
-
}
|
|
2076
|
-
),
|
|
2077
|
-
/* @__PURE__ */ jsx(
|
|
2078
|
-
Entity,
|
|
2079
|
-
{
|
|
2080
|
-
leading: /* @__PURE__ */ jsx(Icon, { name: "download", className: "text-lg text-muted-foreground" }),
|
|
2081
|
-
title: "Export data",
|
|
2082
|
-
subtitle: "Editions, sales, and collectors",
|
|
2083
|
-
action: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: "Export" })
|
|
2084
|
-
}
|
|
2085
|
-
)
|
|
2086
|
-
] })
|
|
2087
|
-
] }),
|
|
2088
|
-
/* @__PURE__ */ jsxs(Card, { children: [
|
|
2089
|
-
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
2090
|
-
/* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), children: "Delete account" }),
|
|
2091
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Permanently remove your profile, editions, and sales history. Collectors keep the editions they own." })
|
|
2092
|
-
] }),
|
|
2093
|
-
/* @__PURE__ */ jsx(CardFooter, { className: "justify-end", children: /* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: () => setDeleteOpen(true), children: "Delete account" }) })
|
|
2094
|
-
] })
|
|
2095
|
-
] }) })
|
|
2096
|
-
] })
|
|
2097
|
-
] }),
|
|
2098
|
-
/* @__PURE__ */ jsx(
|
|
2099
|
-
DestructiveActionModal,
|
|
2100
|
-
{
|
|
2101
|
-
open: deleteOpen,
|
|
2102
|
-
onOpenChange: setDeleteOpen,
|
|
2103
|
-
title: "Delete your account?",
|
|
2104
|
-
description: "This permanently removes your profile, 12 editions, and sales history. Pending payouts are still sent. This can't be undone.",
|
|
2105
|
-
confirmLabel: "Delete account",
|
|
2106
|
-
confirmingLabel: "Deleting\u2026",
|
|
2107
|
-
confirmText: "ada",
|
|
2108
|
-
onConfirm: () => setDeleteOpen(false)
|
|
2109
|
-
}
|
|
2110
|
-
)
|
|
2111
|
-
]
|
|
2112
|
-
}
|
|
2113
|
-
);
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
// src/patterns/settings-page.meta.ts
|
|
2117
|
-
var settingsPageMeta = {
|
|
2118
|
-
name: "SettingsPage",
|
|
2119
|
-
summary: "Account settings inside the app shell: a sticky SideNav section rail beside a column of section Cards \u2014 a Profile form with the page's one explicit save, Card + CardList + Entity rows with Switches that apply instantly, a read-only payouts summary, account rows, and a delete card confirmed through DestructiveActionModal.",
|
|
2120
|
-
intent: "Reach for this for account/profile/workspace settings and any 'edit a handful of grouped preferences' route. Not for a single short form (use AuthPage's centered form shape) or bulk record editing (use DashboardPage + DataTable).",
|
|
2121
|
-
notes: [
|
|
2122
|
-
"Region cap is `wide`, split on a 12-column track: a 3-column SideNav rail and a 9-column section column. The rail fills the space a lone content-width column would leave empty and gives the page its table of contents.",
|
|
2123
|
-
"The rail is `SideNav` (the documented settings sub-nav), sticky, with in-page anchor items; it hides below lg, where the sections simply stack and the shell's BottomNav carries app navigation.",
|
|
2124
|
-
"One save model, stated in the copy: drafted text (the Profile form) commits through its single footer save; toggles apply the moment they change, and the Notifications description says so. Never pair a Switch with a Save button.",
|
|
2125
|
-
"Settings rows are Card + CardList + Entity with the Switch in `action` \u2014 the row owns its title and one-line description; keep descriptions short, because Entity truncates its subtitle.",
|
|
2126
|
-
"Each section is a Card whose CardTitle holds an h2, so the page has a real heading outline under PageHeader's h1; Cards carry the anchor id and `scroll-mt-*` so anchor jumps clear the top edge.",
|
|
2127
|
-
"Field wires label/description/error a11y automatically; never hand-roll <label> + <input> pairs. Pair fields side by side with Grid cols={{ base: 1, sm: 2 }}.",
|
|
2128
|
-
"The destructive action is last and isolated in its own Card, and it never acts directly: it opens DestructiveActionModal with type-to-confirm on the handle.",
|
|
2129
|
-
"Lives in the same AppShell as DashboardPage: Sidebar from lg up, AppHeader + BottomNav below it, with Settings marked active in both."
|
|
2130
|
-
],
|
|
2131
|
-
a11y: [
|
|
2132
|
-
'AppShell provides the skip link and <main id="main-content"> landmark; the active SidebarItem and BottomNavItem carry aria-current.',
|
|
2133
|
-
'The section rail is a labelled <nav> ("Settings sections") distinct from the primary navigation; its active item carries aria-current.',
|
|
2134
|
-
"Every section has an h2, so screen-reader heading navigation mirrors the rail.",
|
|
2135
|
-
"Each Switch takes its row title as aria-label, since the Entity title is not a <label>.",
|
|
2136
|
-
"Field auto-wires htmlFor/id, aria-describedby (description + error), aria-invalid, and required.",
|
|
2137
|
-
"DestructiveActionModal is an alertdialog: focus opens on Cancel, the backdrop does not dismiss, and the confirm stays disabled until the handle is typed."
|
|
2138
|
-
]
|
|
2139
|
-
};
|
|
2140
|
-
|
|
2141
|
-
export { AuthPage, BrandLandingPage, ChatPanel, DashboardPage, FeedPage, LandingPage, ListDetailPage, SettingsPage, authPageMeta, brandLandingPageMeta, chatPanelMeta, dashboardPageMeta, feedPageMeta, landingPageMeta, listDetailPageMeta, settingsPageMeta };
|