@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/scripts/lint-rules.mjs
CHANGED
|
@@ -65,7 +65,7 @@ function* openingTags(text, tagPattern) {
|
|
|
65
65
|
else if (ch === ">" && depth === 0) break
|
|
66
66
|
}
|
|
67
67
|
if (i >= text.length) continue // unterminated — malformed source, not our problem
|
|
68
|
-
yield { tag: m[1], attrs: text.slice(m.index + m[0].length, i), index: m.index }
|
|
68
|
+
yield { tag: m[1], attrs: text.slice(m.index + m[0].length, i), index: m.index, end: i }
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -95,7 +95,12 @@ function classNameOf(attrs) {
|
|
|
95
95
|
return ""
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Every class token in a className expression, with its variant prefixes (`hover:`, `md:`) split off.
|
|
100
|
+
* `variants` are the top-level prefixes only: `[@media(hover:none)]:opacity-100`
|
|
101
|
+
* has the ONE variant `[@media(hover:none)]` — the `hover:` inside the brackets
|
|
102
|
+
* is a media feature, not a variant.
|
|
103
|
+
*/
|
|
99
104
|
function classTokens(expr) {
|
|
100
105
|
const out = []
|
|
101
106
|
for (const s of expr.matchAll(/(["'`])((?:(?!\1).)*)\1/gs)) {
|
|
@@ -103,15 +108,286 @@ function classTokens(expr) {
|
|
|
103
108
|
if (!raw || raw.includes("${")) continue
|
|
104
109
|
// The base utility is what follows the last `:` outside brackets.
|
|
105
110
|
let depth = 0
|
|
106
|
-
let
|
|
111
|
+
let from = 0
|
|
112
|
+
const variants = []
|
|
107
113
|
for (let k = 0; k < raw.length; k++) {
|
|
108
114
|
if (raw[k] === "[" || raw[k] === "(") depth++
|
|
109
115
|
else if (raw[k] === "]" || raw[k] === ")") depth--
|
|
110
|
-
else if (raw[k] === ":" && depth === 0)
|
|
116
|
+
else if (raw[k] === ":" && depth === 0) {
|
|
117
|
+
variants.push(raw.slice(from, k))
|
|
118
|
+
from = k + 1
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
out.push({ raw, base: raw.slice(from).replace(/^!/, ""), variants })
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return out
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** A variant that styles a pointer state: hover, active, focus-visible — and their group-/peer- (optionally /named) forms. */
|
|
128
|
+
const POINTER_STATE_VARIANT = /^(?:(?:group|peer)-)?(?:hover|active|focus-visible)(?:\/[\w-]+)?$/
|
|
129
|
+
|
|
130
|
+
/** Utilities that set a property no transition can interpolate: display, visibility, text-decoration-line. */
|
|
131
|
+
const DISCRETE_UTILITY = /^(?:hidden|block|inline|inline-block|flex|inline-flex|grid|inline-grid|contents|table|invisible|visible|collapse|underline|overline|line-through|no-underline)$/
|
|
132
|
+
|
|
133
|
+
/* ── Assembly lookalikes: the shared reading of an element and its content. ────
|
|
134
|
+
* lookalike-control sees a control by its clothes. The four lookalike-* rules
|
|
135
|
+
* below see the bigger pieces an app assembles from Sable's utilities and tokens
|
|
136
|
+
* — a card grid, a link tile, an avatar chip, a cover frame — which every token
|
|
137
|
+
* rule passes, because every class on them is legal. An e2e build shipped all
|
|
138
|
+
* four through lint, a 100/100 grade and a visual review; a source audit found
|
|
139
|
+
* them. The helpers here are what those rules and grade's surface inventory read. */
|
|
140
|
+
|
|
141
|
+
/** Breakpoint variants that Grid's `cols` map takes (Tailwind's defaults). */
|
|
142
|
+
const BREAKPOINT_VARIANT = /^(?:sm|md|lg|xl|2xl)$/
|
|
143
|
+
|
|
144
|
+
/** Children that make a grid a form layout, not a collection: fields and their parts. */
|
|
145
|
+
const FORM_PART = /^(?:input|select|textarea|label|fieldset|legend|button|option)$|Field|Input|Label|Select|Textarea|Checkbox|Switch|Radio|Combobox|Slider|Button|Fieldset|Form/
|
|
146
|
+
|
|
147
|
+
/** A corner radius that makes a box a surface: any step but none or full. */
|
|
148
|
+
const SURFACE_RADIUS = /^rounded(?:-(?:xs|sm|md|lg|xl|[2-4]xl|\[[^\]]+\]|\([^)]+\)|concentric[\w-]*))?$/
|
|
149
|
+
|
|
150
|
+
/** A fixed square-ish measure: `size-12`, `size-[52px]`, `size-(--x)`. Percentages and keywords are not fixed. */
|
|
151
|
+
const FIXED_MEASURE = /^(?:\d+(?:\.\d+)?|\[[^\]%]+\]|\([^)%]+\))$/
|
|
152
|
+
|
|
153
|
+
/** Media an avatar or a cover frame holds. */
|
|
154
|
+
const MEDIA_CHILD = /<(?:img|picture|video|GradientArt|Image)\b/
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The text with the literal part of every template literal that holds MARKUP
|
|
158
|
+
* blanked (length and newlines kept, so offsets and line numbers hold). A docs
|
|
159
|
+
* page carries JSX as data — a code sample in a backtick string — and a lookalike
|
|
160
|
+
* rule must not report the sample. A className template (`grid ${cols}`) carries
|
|
161
|
+
* no `<tag`, so it is kept and its classes still count.
|
|
162
|
+
*/
|
|
163
|
+
function maskMarkupTemplates(text) {
|
|
164
|
+
const out = text.split("")
|
|
165
|
+
const stack = [] // { from, segs } = inside a template literal; number = brace depth of a ${ } inside one
|
|
166
|
+
let quote = null
|
|
167
|
+
const close = (t) => {
|
|
168
|
+
if (!t.segs.some(([a, b]) => /<[A-Za-z]/.test(text.slice(a, b)))) return
|
|
169
|
+
for (const [a, b] of t.segs) for (let k = a; k < b; k++) if (out[k] !== "\n") out[k] = " "
|
|
170
|
+
}
|
|
171
|
+
for (let i = 0; i < text.length; i++) {
|
|
172
|
+
const c = text[i]
|
|
173
|
+
const top = stack[stack.length - 1]
|
|
174
|
+
if (top && typeof top === "object") {
|
|
175
|
+
if (c === "\\") i++
|
|
176
|
+
else if (c === "`") {
|
|
177
|
+
top.segs.push([top.from, i])
|
|
178
|
+
stack.pop()
|
|
179
|
+
close(top)
|
|
180
|
+
} else if (c === "$" && text[i + 1] === "{") {
|
|
181
|
+
top.segs.push([top.from, i])
|
|
182
|
+
stack.push(0)
|
|
183
|
+
i++
|
|
184
|
+
}
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
if (quote) {
|
|
188
|
+
// JSX text is full of apostrophes; a quote never spans a line in code.
|
|
189
|
+
if (c === "\\") i++
|
|
190
|
+
else if (c === quote || c === "\n") quote = null
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
193
|
+
if (c === "'" || c === '"') quote = c
|
|
194
|
+
else if (c === "`") stack.push({ from: i + 1, segs: [] })
|
|
195
|
+
else if (typeof top === "number") {
|
|
196
|
+
if (c === "{") stack[stack.length - 1]++
|
|
197
|
+
else if (c === "}") {
|
|
198
|
+
if (top === 0) {
|
|
199
|
+
stack.pop()
|
|
200
|
+
stack[stack.length - 1].from = i + 1
|
|
201
|
+
} else stack[stack.length - 1]--
|
|
111
202
|
}
|
|
112
|
-
out.push({ raw, base: raw.slice(cut).replace(/^!/, "") })
|
|
113
203
|
}
|
|
114
204
|
}
|
|
205
|
+
return out.join("")
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Index of the `}` closing the `{` at `i`. Strings are skipped; a quote ends at a line break (JSX text). */
|
|
209
|
+
function skipBraces(text, i) {
|
|
210
|
+
let depth = 0
|
|
211
|
+
let quote = null
|
|
212
|
+
for (; i < text.length; i++) {
|
|
213
|
+
const c = text[i]
|
|
214
|
+
if (quote) {
|
|
215
|
+
if (c === "\\") i++
|
|
216
|
+
else if (c === quote || (c === "\n" && quote !== "`")) quote = null
|
|
217
|
+
continue
|
|
218
|
+
}
|
|
219
|
+
if (c === '"' || c === "'" || c === "`") quote = c
|
|
220
|
+
else if (c === "{") depth++
|
|
221
|
+
else if (c === "}" && --depth === 0) return i
|
|
222
|
+
}
|
|
223
|
+
return text.length
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Index of the `>` ending the tag that opens at `i`, as openingTags finds it. */
|
|
227
|
+
function tagEnd(text, i) {
|
|
228
|
+
let depth = 0
|
|
229
|
+
let quote = null
|
|
230
|
+
for (i++; i < text.length; i++) {
|
|
231
|
+
const c = text[i]
|
|
232
|
+
if (quote) {
|
|
233
|
+
if (c === "\\") i++
|
|
234
|
+
else if (c === quote) quote = null
|
|
235
|
+
continue
|
|
236
|
+
}
|
|
237
|
+
if (c === '"' || c === "'" || c === "`") quote = c
|
|
238
|
+
else if (c === "{") depth++
|
|
239
|
+
else if (c === "}") depth--
|
|
240
|
+
else if (c === ">" && depth === 0) return i
|
|
241
|
+
}
|
|
242
|
+
return text.length
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* What an element directly holds: the tag names of its child elements, its
|
|
247
|
+
* child `{…}` expressions, its loose text, and its whole inner source. Lexical,
|
|
248
|
+
* like everything here: it pairs `<x>` with `</x>` by depth and skips `{…}`
|
|
249
|
+
* whole, so a comparison inside an expression cannot open a tag.
|
|
250
|
+
*/
|
|
251
|
+
function elementContent(text, open) {
|
|
252
|
+
const out = { tags: [], exprs: [], text: "", inner: "" }
|
|
253
|
+
if (/\/\s*$/.test(open.attrs)) return out // self-closing
|
|
254
|
+
const start = open.end + 1
|
|
255
|
+
const limit = Math.min(text.length, start + 40000)
|
|
256
|
+
let depth = 0
|
|
257
|
+
let i = start
|
|
258
|
+
while (i < limit) {
|
|
259
|
+
const c = text[i]
|
|
260
|
+
if (c === "{") {
|
|
261
|
+
const j = skipBraces(text, i)
|
|
262
|
+
if (depth === 0) out.exprs.push(text.slice(i + 1, j))
|
|
263
|
+
i = j + 1
|
|
264
|
+
continue
|
|
265
|
+
}
|
|
266
|
+
if (c === "<" && text[i + 1] === "/") {
|
|
267
|
+
if (depth === 0) break
|
|
268
|
+
depth--
|
|
269
|
+
const j = text.indexOf(">", i)
|
|
270
|
+
i = j === -1 ? limit : j + 1
|
|
271
|
+
continue
|
|
272
|
+
}
|
|
273
|
+
if (c === "<" && /[A-Za-z>]/.test(text[i + 1] ?? "")) {
|
|
274
|
+
const name = /^<([A-Za-z][\w.]*)?/.exec(text.slice(i, i + 80))?.[1]
|
|
275
|
+
const j = tagEnd(text, i)
|
|
276
|
+
if (depth === 0 && name) out.tags.push(name)
|
|
277
|
+
if (text[j - 1] !== "/") depth++
|
|
278
|
+
i = j + 1
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
if (depth === 0) out.text += c
|
|
282
|
+
i++
|
|
283
|
+
}
|
|
284
|
+
out.inner = text.slice(start, i)
|
|
285
|
+
return out
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Is the tag at `index` the element handed to a component's `render` prop?
|
|
290
|
+
* `<Card render={<a href="/x" className="…" />}>` already renders the Sable
|
|
291
|
+
* component; its element's classes are not an app-built surface.
|
|
292
|
+
*/
|
|
293
|
+
function insideRender(text, index) {
|
|
294
|
+
// The nearest `render={` before the tag whose brace is still open at it —
|
|
295
|
+
// which also covers a render function, `render={({ className, ...props }) => <a …`.
|
|
296
|
+
const before = text.slice(Math.max(0, index - 600), index)
|
|
297
|
+
const opens = [...before.matchAll(/\brender\s*=\s*\{/g)]
|
|
298
|
+
const last = opens[opens.length - 1]
|
|
299
|
+
if (!last) return false
|
|
300
|
+
let depth = 0
|
|
301
|
+
for (let k = last.index + last[0].length - 1; k < before.length; k++) {
|
|
302
|
+
if (before[k] === "{") depth++
|
|
303
|
+
else if (before[k] === "}") depth--
|
|
304
|
+
}
|
|
305
|
+
return depth > 0
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Unprefixed class bases (no `md:`, `hover:`…) of an element, plus every token. */
|
|
309
|
+
function classesOf(attrs) {
|
|
310
|
+
const tokens = classTokens(classNameOf(attrs))
|
|
311
|
+
const plain = tokens.filter((t) => !t.variants.length).map((t) => t.base)
|
|
312
|
+
return { tokens, plain, has: (re) => plain.some((b) => re.test(b)), find: (re) => plain.find((b) => re.test(b)) }
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** A fixed size on a round box: `size-12`, or equal `h-*` and `w-*`. Returns the measure or null. */
|
|
316
|
+
function fixedSize(c) {
|
|
317
|
+
const size = c.find(/^size-/)?.slice(5)
|
|
318
|
+
if (size && FIXED_MEASURE.test(size)) return size
|
|
319
|
+
const h = c.find(/^h-/)?.slice(2)
|
|
320
|
+
const w = c.find(/^w-/)?.slice(2)
|
|
321
|
+
return h && h === w && FIXED_MEASURE.test(h) ? h : null
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Padding big enough for a card: p-3 and up (or py-3 and up), or an arbitrary/var padding. */
|
|
325
|
+
function cardPadding(c) {
|
|
326
|
+
return c.plain.some((b) => {
|
|
327
|
+
const m = /^p[y]?-(?:(\d+(?:\.\d+)?)|\[[^\]]+\]|\([^)]+\))$/.exec(b)
|
|
328
|
+
return m && (m[1] == null || Number(m[1]) >= 3)
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** Grid's `gap` prop for a Tailwind `gap-N` (rhythm units are 2 spacing steps). */
|
|
333
|
+
function gridGap(c) {
|
|
334
|
+
const n = /^gap-(\d+(?:\.\d+)?)$/.exec(c.find(/^gap-\d/) ?? "")?.[1]
|
|
335
|
+
return n == null ? "" : ` gap={${Number(n) / 2}}`
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** `aspect-*` → AspectBox's ratio, as source. */
|
|
339
|
+
function aspectRatio(base) {
|
|
340
|
+
if (base === "aspect-square") return "1"
|
|
341
|
+
if (base === "aspect-video") return "16 / 9"
|
|
342
|
+
const m = /^aspect-(?:\[(\d+(?:\.\d+)?)\/(\d+(?:\.\d+)?)\]|(\d+)\/(\d+))$/.exec(base)
|
|
343
|
+
return m ? `${m[1] ?? m[3]} / ${m[2] ?? m[4]}` : "…"
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* The app-owned surfaces in one file: intrinsic elements wearing a surface's
|
|
348
|
+
* or a shape's classes. Not failures — a review list, so an agent's reuse
|
|
349
|
+
* inventory starts from what the source actually holds rather than from memory.
|
|
350
|
+
* `sable grade` collects it and `sable check` prints it.
|
|
351
|
+
*
|
|
352
|
+
* surface bg-card / bg-popover, or border + radius + padding → Card (or the component that owns it)
|
|
353
|
+
* media frame aspect-* + overflow-hidden → AspectBox
|
|
354
|
+
* round rounded-full at a fixed size → Avatar, or StatusDot for a dot
|
|
355
|
+
*/
|
|
356
|
+
export function surfaceInventory(text) {
|
|
357
|
+
const src = maskMarkupTemplates(stripComments(text))
|
|
358
|
+
const out = []
|
|
359
|
+
for (const m of openingTags(src, /^[a-z][a-z0-9]*$/)) {
|
|
360
|
+
if (insideRender(src, m.index)) continue
|
|
361
|
+
const c = classesOf(m.attrs)
|
|
362
|
+
if (!c.plain.length) continue
|
|
363
|
+
let kind = null
|
|
364
|
+
let consider = null
|
|
365
|
+
const size = c.has(/^rounded-full$/) ? fixedSize(c) : null
|
|
366
|
+
if (size) {
|
|
367
|
+
kind = "round"
|
|
368
|
+
const holds = m.tag === "img" || (() => {
|
|
369
|
+
const content = elementContent(src, m)
|
|
370
|
+
return MEDIA_CHILD.test(content.inner) || /^[A-Z]{1,3}$/.test(content.text.trim())
|
|
371
|
+
})()
|
|
372
|
+
consider = holds
|
|
373
|
+
? "Avatar"
|
|
374
|
+
: /^(?:0?\.5|1|1\.5|2|2\.5|3|px)$/.test(size)
|
|
375
|
+
? "StatusDot, if it reports a status"
|
|
376
|
+
: "Avatar if it shows a person; otherwise app-owned (a handle, a swatch)"
|
|
377
|
+
} else if (c.has(/^aspect-(?!auto$)/) && c.has(/^overflow-(?:hidden|clip)$/)) {
|
|
378
|
+
kind = "media frame"
|
|
379
|
+
consider = "AspectBox"
|
|
380
|
+
} else if (
|
|
381
|
+
c.has(/^bg-(?:card|popover)(?:\/|$)/) ||
|
|
382
|
+
(c.has(/^border(?:-\d+)?$/) && c.has(SURFACE_RADIUS) && c.has(/^p[xy]?-/))
|
|
383
|
+
) {
|
|
384
|
+
kind = "surface"
|
|
385
|
+
consider = m.tag === "a" || m.tag === "button" ? `Card render={<${m.tag} />}` : "Card, or the component that owns this surface"
|
|
386
|
+
}
|
|
387
|
+
if (!kind) continue
|
|
388
|
+
const classes = c.plain.join(" ")
|
|
389
|
+
out.push({ index: m.index, tag: m.tag, kind, classes: classes.length > 70 ? `${classes.slice(0, 67)}…` : classes, consider })
|
|
390
|
+
}
|
|
115
391
|
return out
|
|
116
392
|
}
|
|
117
393
|
|
|
@@ -370,15 +646,41 @@ export function makeRules({ semanticTokens, motionTokens, labelContrast = null,
|
|
|
370
646
|
// up with two transitions fighting over the same property.
|
|
371
647
|
for (const m of openingTags(text, /^[a-z][a-z0-9]*$/)) {
|
|
372
648
|
const attrs = m.attrs
|
|
373
|
-
|
|
374
|
-
|
|
649
|
+
// Judged on the parsed variants, not a regex over the text: a
|
|
650
|
+
// capability query like `[@media(hover:none)]:opacity-100` has
|
|
651
|
+
// "hover:" in it and no pointer state at all.
|
|
652
|
+
const states = classTokens(classNameOf(attrs)).filter(({ variants }) =>
|
|
653
|
+
variants.some((v) => POINTER_STATE_VARIANT.test(v)),
|
|
654
|
+
)
|
|
655
|
+
const pointer = states[0]
|
|
656
|
+
if (!pointer) continue
|
|
375
657
|
if (/\bmotion-[a-z-]+/.test(attrs)) continue
|
|
658
|
+
// `reveal-on-hover` declares motion-interactive's own transition
|
|
659
|
+
// list, so for this rule it is a recipe.
|
|
660
|
+
if (/\breveal-on-hover\b/.test(attrs)) continue
|
|
376
661
|
// `transition-colors` is already caught by its own rule and its fix is
|
|
377
662
|
// the same recipe; reporting both on one element is noise.
|
|
378
663
|
if (/\btransition-colors\b/.test(attrs)) continue
|
|
379
664
|
out.push({
|
|
380
665
|
index: m.index,
|
|
381
|
-
match: `<${m.tag} …${
|
|
666
|
+
match: `<${m.tag} …${pointer.raw}`,
|
|
667
|
+
// `motion-interactive` cannot help a state that only flips display
|
|
668
|
+
// or an underline: there is nothing to interpolate. Point at the
|
|
669
|
+
// fix that can.
|
|
670
|
+
...(states.every(({ base }) => DISCRETE_UTILITY.test(base)) && {
|
|
671
|
+
why:
|
|
672
|
+
"An element whose pointer state only flips a discrete property (display, visibility, an underline), which no transition can animate, so `motion-interactive` alone will not help. " +
|
|
673
|
+
"To reveal an action on hover, keep it rendered and put `reveal-on-hover` on it with `group` on its row or tile: it fades with opacity, shows on focus, and stays visible on touch, where a hand-written `opacity-0 group-hover:opacity-100` hides it for good. To cross-fade two icons, `motion-icon-swap`. " +
|
|
674
|
+
"If an instant flip is the intent, disable the rule on that line with the reason.",
|
|
675
|
+
}),
|
|
676
|
+
// A group-hover opacity swap is a hover REVEAL, and adding
|
|
677
|
+
// motion-interactive to it would still leave the action invisible on
|
|
678
|
+
// every touch device. Point at the utility that is right on both.
|
|
679
|
+
...(states.some(({ base, variants }) => /^opacity-/.test(base) && variants.some((v) => /^group-hover\b/.test(v))) && {
|
|
680
|
+
why:
|
|
681
|
+
"A hover reveal (`group-hover:opacity-*`) with no motion — and, as written, an action no touch device can ever see, because touch has no hover. " +
|
|
682
|
+
"Use `reveal-on-hover` on the action with `group` on its row or tile: it is hidden only where `(hover: hover)`, shows on focus-within, keeps an open menu's trigger visible, and fades on the motion tokens.",
|
|
683
|
+
}),
|
|
382
684
|
})
|
|
383
685
|
}
|
|
384
686
|
return out
|
|
@@ -528,6 +830,180 @@ export function makeRules({ semanticTokens, motionTokens, labelContrast = null,
|
|
|
528
830
|
return out
|
|
529
831
|
},
|
|
530
832
|
},
|
|
833
|
+
/* ── Assembly lookalikes. The siblings of lookalike-control for the pieces
|
|
834
|
+
* bigger than a control: a card grid, a link tile, an avatar, a cover frame.
|
|
835
|
+
* One id each, so an exception is disabled for the one shape it covers.
|
|
836
|
+
* CONSUMER-scoped: the package's own components are where these are built.
|
|
837
|
+
* Each one demands a signal beyond the classes that a single legitimate use
|
|
838
|
+
* would not carry — mapped items, a fill AND padding, content in the circle,
|
|
839
|
+
* media in the frame — because a lookalike rule that fires on a form layout
|
|
840
|
+
* or a text link is a rule people learn to disable. */
|
|
841
|
+
{
|
|
842
|
+
id: "lookalike-grid",
|
|
843
|
+
scope: "consumer",
|
|
844
|
+
since: "0.7.0",
|
|
845
|
+
file: true,
|
|
846
|
+
code: true,
|
|
847
|
+
why:
|
|
848
|
+
"A raw CSS grid laid over a collection — `grid` with an auto-fill/auto-fit `minmax()` track, or uniform `grid-cols-N` columns over mapped or repeated items. That is Sable's `Grid`: `cols` takes the same counts per breakpoint (`{ base: 2, md: 4 }`), `minItemWidth` is the auto-fit track with a `min(…, 100%)` guard so a cell never overflows a narrow container (a bare `minmax(12rem,1fr)` does), every track is `minmax(0, 1fr)` so one long string cannot blow a column out, and the gap defaults to the theme's `--grid-gutter` or takes rhythm units (`gap={2}` = `gap-4`). " +
|
|
849
|
+
"Not flagged: a grid with no columns, a form layout (field children, no mapping), a non-uniform track (`grid-cols-[auto_1fr]`), a `ul`/`ol`/`dl` (Grid renders a div), or an element with a widget role.",
|
|
850
|
+
scan(text) {
|
|
851
|
+
const src = maskMarkupTemplates(text)
|
|
852
|
+
const out = []
|
|
853
|
+
for (const m of openingTags(src, /^div$/)) {
|
|
854
|
+
if (insideRender(src, m.index)) continue
|
|
855
|
+
// A widget or a graphic (role="img", "grid", "radiogroup") owns its own
|
|
856
|
+
// semantics; a list role is still a collection.
|
|
857
|
+
if (/\brole\s*=/.test(m.attrs) && !/\brole\s*=\s*\{?\s*["'`]list["'`]/.test(m.attrs)) continue
|
|
858
|
+
const c = classesOf(m.attrs)
|
|
859
|
+
if (!c.has(/^grid$/)) continue
|
|
860
|
+
const tracks = c.tokens.filter((t) => /^grid-cols-/.test(t.base))
|
|
861
|
+
if (!tracks.length) continue
|
|
862
|
+
const auto = tracks
|
|
863
|
+
.map((t) => /^grid-cols-\[repeat\(auto-(?:fill|fit),\s*minmax\((min\([^)]*\)|[^,]+),/.exec(t.base))
|
|
864
|
+
.find(Boolean)
|
|
865
|
+
if (auto) {
|
|
866
|
+
const min = auto[1].replace(/_/g, " ").replace(/^min\(\s*([^,]+),.*$/, "$1").trim()
|
|
867
|
+
out.push({ index: m.index, match: `raw auto-fill grid → <Grid minItemWidth="${min}"${gridGap(c)}>` })
|
|
868
|
+
continue
|
|
869
|
+
}
|
|
870
|
+
// Uniform column counts per breakpoint, or nothing: a track Grid cannot
|
|
871
|
+
// express (`[auto_1fr]`, `subgrid`, a container-query variant) is
|
|
872
|
+
// a layout, not a collection grid.
|
|
873
|
+
const cols = {}
|
|
874
|
+
let uniform = true
|
|
875
|
+
for (const t of tracks) {
|
|
876
|
+
const bp = t.variants.length === 0 ? "base" : t.variants.length === 1 && BREAKPOINT_VARIANT.test(t.variants[0]) ? t.variants[0] : null
|
|
877
|
+
const n = /^grid-cols-(?:(\d+)|\[repeat\((\d+),\s*minmax\(0,\s*1fr\)\)\])$/.exec(t.base)
|
|
878
|
+
if (!bp || !n) {
|
|
879
|
+
uniform = false
|
|
880
|
+
break
|
|
881
|
+
}
|
|
882
|
+
cols[bp] = Number(n[1] ?? n[2])
|
|
883
|
+
}
|
|
884
|
+
if (!uniform) continue
|
|
885
|
+
if (Math.max(...Object.values(cols)) < 2) continue
|
|
886
|
+
const content = elementContent(src, m)
|
|
887
|
+
// Children placed by span are an explicit layout (Columns + Col), not
|
|
888
|
+
// a collection of equal cells.
|
|
889
|
+
if (/\b(?:col|row)-(?:span|start|end)-/.test(content.inner)) continue
|
|
890
|
+
// The child expression itself maps (`{items.map(…)}`); a map nested in
|
|
891
|
+
// one child's markup (`{cond ? <Panel>{a.map(…)}</Panel> : null}`) is
|
|
892
|
+
// that child's list, not this grid's cells.
|
|
893
|
+
const mapped = content.exprs.some((e) => /\.(?:flat)?[mM]ap\s*\(|Array\.from\s*\(/.test(e.split("<")[0]))
|
|
894
|
+
// Three or more of the same component or item element. Bare wrappers
|
|
895
|
+
// (div, span, p) say nothing about a collection, and fields make a form.
|
|
896
|
+
const counts = new Map()
|
|
897
|
+
for (const tag of content.tags) counts.set(tag, (counts.get(tag) ?? 0) + 1)
|
|
898
|
+
const repeated = [...counts].some(([tag, n]) => n >= 3 && !/^(?:div|span|p)$/.test(tag) && !FORM_PART.test(tag))
|
|
899
|
+
if (!mapped && !repeated) continue
|
|
900
|
+
// `grid md:grid-cols-2` is one column until md, as Tailwind's implicit track is.
|
|
901
|
+
if (!("base" in cols)) cols.base = 1
|
|
902
|
+
const order = ["base", "sm", "md", "lg", "xl", "2xl"].filter((b) => b in cols)
|
|
903
|
+
const prop = order.length === 1 ? `{${cols.base}}` : `{{ ${order.map((b) => `${b === "2xl" ? '"2xl"' : b}: ${cols[b]}`).join(", ")} }}`
|
|
904
|
+
out.push({ index: m.index, match: `raw grid over ${mapped ? "mapped" : "repeated"} items → <Grid cols=${prop}${gridGap(c)}>` })
|
|
905
|
+
}
|
|
906
|
+
return out
|
|
907
|
+
},
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
id: "lookalike-card",
|
|
911
|
+
scope: "consumer",
|
|
912
|
+
since: "0.7.0",
|
|
913
|
+
file: true,
|
|
914
|
+
code: true,
|
|
915
|
+
why:
|
|
916
|
+
"A link or button dressed as a card — a radius, a surface (`bg-card` / `bg-popover`, or a border) and card padding on an <a>, <button> or router <Link>. That is `<Card render={<Link to=… />}>` (or `<a href>` / `<button>`): rendered as a link or button, Card adds the hover (a stronger edge and shadow), a full-card focus-visible ring and motion-interactive easing; its padding follows the density control (`--card-p`) and its corner follows the theme radius; children answer the hover with `group-hover/card:`. Hand-built, a tile keeps none of that when the theme or density moves. " +
|
|
917
|
+
"Not flagged: a text link, a nav row (no surface), a pill (`rounded-full`), a button fill (lookalike-control's), or an element inside a `render` prop.",
|
|
918
|
+
scan(text) {
|
|
919
|
+
const src = maskMarkupTemplates(text)
|
|
920
|
+
// A `Link` the file takes from Sable is Sable's.
|
|
921
|
+
const sableLinks = new Set(
|
|
922
|
+
[...src.matchAll(/import\s*\{([^}]*)\}\s*from\s*["']@sableui\/react[^"']*["']/g)].flatMap((m) =>
|
|
923
|
+
m[1].split(",").map((s) => s.trim().split(/\s+as\s+/).pop()),
|
|
924
|
+
),
|
|
925
|
+
)
|
|
926
|
+
const out = []
|
|
927
|
+
for (const m of openingTags(src, /^(?:a|button|Link|NavLink)$/)) {
|
|
928
|
+
if (sableLinks.has(m.tag) || insideRender(src, m.index)) continue
|
|
929
|
+
const c = classesOf(m.attrs)
|
|
930
|
+
if (!c.has(SURFACE_RADIUS)) continue
|
|
931
|
+
const fill = c.find(/^bg-(?:card|popover)(?:\/|$)/) ?? c.find(/^sable-surface$/)
|
|
932
|
+
const edge = c.find(/^border(?:-\d+)?$/)
|
|
933
|
+
if (!fill && !edge) continue
|
|
934
|
+
if (!cardPadding(c)) continue
|
|
935
|
+
if (c.has(/^bg-(?:primary|secondary|destructive)$/)) continue
|
|
936
|
+
const el = m.tag === "a" ? "<a href=… />" : m.tag === "button" ? "<button />" : `<${m.tag} to=… />`
|
|
937
|
+
const radius = c.find(SURFACE_RADIUS)
|
|
938
|
+
out.push({ index: m.index, match: `<${m.tag}> dressed as a card (${[radius, edge, fill].filter(Boolean).join(" ")}) → <Card render={${el}}>` })
|
|
939
|
+
}
|
|
940
|
+
return out
|
|
941
|
+
},
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
id: "lookalike-avatar",
|
|
945
|
+
scope: "consumer",
|
|
946
|
+
since: "0.7.0",
|
|
947
|
+
file: true,
|
|
948
|
+
code: true,
|
|
949
|
+
why:
|
|
950
|
+
"A round, fixed-size box holding a person's or artist's picture — `rounded-full` + `size-*` (or equal `h-*`/`w-*`) around an <img>, GradientArt or initials, or a round fixed-size <img> itself. That is `Avatar`: `src`/`alt` with a fallback that shows while the image loads or when it fails, `variant=\"art\"` for initials over GradientArt seeded by the person (a stable colour each, from the theme), a `size` ladder, an accessible name (role=img), a `status` slot that clears the circle, and AvatarGroup for an overlapping stack. " +
|
|
951
|
+
"Not flagged: a dot or an icon in a circle (no picture or initials), a round box without a fixed size, or AvatarRoot and its parts.",
|
|
952
|
+
scan(text) {
|
|
953
|
+
const src = maskMarkupTemplates(text)
|
|
954
|
+
const out = []
|
|
955
|
+
for (const m of openingTags(src, /^(?:div|span|figure|img)$/)) {
|
|
956
|
+
if (insideRender(src, m.index)) continue
|
|
957
|
+
const c = classesOf(m.attrs)
|
|
958
|
+
if (!c.has(/^rounded-full$/)) continue
|
|
959
|
+
const size = fixedSize(c)
|
|
960
|
+
if (!size) continue
|
|
961
|
+
let holds = null
|
|
962
|
+
if (m.tag === "img") holds = "an image"
|
|
963
|
+
else {
|
|
964
|
+
const content = elementContent(src, m)
|
|
965
|
+
if (/<GradientArt\b/.test(content.inner)) holds = "GradientArt"
|
|
966
|
+
else if (MEDIA_CHILD.test(content.inner)) holds = "an image"
|
|
967
|
+
else if (
|
|
968
|
+
/^[A-Z]{1,3}$/.test(content.text.trim()) ||
|
|
969
|
+
content.exprs.some((e) => /initial|\[0\]|charAt\(0\)|\.(?:slice|substring)\(0,\s*[12]\)/i.test(e))
|
|
970
|
+
)
|
|
971
|
+
holds = "initials"
|
|
972
|
+
}
|
|
973
|
+
if (!holds) continue
|
|
974
|
+
const use = holds === "an image" ? "<Avatar src alt fallback>" : '<Avatar variant="art" fallback="…">'
|
|
975
|
+
out.push({ index: m.index, match: `<${m.tag}> rounded-full size-${size} holding ${holds} → ${use}` })
|
|
976
|
+
}
|
|
977
|
+
return out
|
|
978
|
+
},
|
|
979
|
+
},
|
|
980
|
+
{
|
|
981
|
+
id: "lookalike-media-frame",
|
|
982
|
+
scope: "consumer",
|
|
983
|
+
since: "0.7.0",
|
|
984
|
+
file: true,
|
|
985
|
+
code: true,
|
|
986
|
+
why:
|
|
987
|
+
"A cover or media frame built by hand — an `aspect-*` ratio + `overflow-hidden` wrapped around an <img>, <video>, <picture> or GradientArt. That is `AspectBox`: `ratio` as a number reserves the box before the media arrives (no layout shift while a cover loads or a shader starts), it clips and positions what it holds (`relative w-full overflow-hidden`), and it is the one frame the media components share, so a change to how frames behave reaches every cover. Put the radius on it as a className. For a cover with its title beneath (an album, an episode, a product), `MediaTile` composes this frame with fallback art for a missing or broken image and an action revealed on hover. " +
|
|
988
|
+
"Not flagged: a ratio box with no media in it (a pad, a canvas well), a frame without overflow clipping, an <img> sized by its own aspect class, or a round fixed-size frame (lookalike-avatar's).",
|
|
989
|
+
scan(text) {
|
|
990
|
+
const src = maskMarkupTemplates(text)
|
|
991
|
+
const out = []
|
|
992
|
+
for (const m of openingTags(src, /^(?:div|span|figure|a|li)$/)) {
|
|
993
|
+
if (insideRender(src, m.index)) continue
|
|
994
|
+
const c = classesOf(m.attrs)
|
|
995
|
+
const aspect = c.find(/^aspect-(?!auto$)/)
|
|
996
|
+
if (!aspect || !c.has(/^overflow-(?:hidden|clip)$/)) continue
|
|
997
|
+
// A round, fixed-size frame is an avatar, reported by lookalike-avatar.
|
|
998
|
+
if (c.has(/^rounded-full$/) && fixedSize(c)) continue
|
|
999
|
+
const inner = elementContent(src, m).inner
|
|
1000
|
+
const media = /<GradientArt\b/.test(inner) ? "GradientArt" : MEDIA_CHILD.exec(inner)?.[0].slice(1)
|
|
1001
|
+
if (!media) continue
|
|
1002
|
+
out.push({ index: m.index, match: `<${m.tag}> ${aspect} overflow-hidden around <${media}> → <AspectBox ratio={${aspectRatio(aspect)}}>` })
|
|
1003
|
+
}
|
|
1004
|
+
return out
|
|
1005
|
+
},
|
|
1006
|
+
},
|
|
531
1007
|
{
|
|
532
1008
|
id: "label-contrast",
|
|
533
1009
|
/* CONSUMER-scoped. An app retunes `--primary` and keeps the theme's
|
|
@@ -1060,7 +1536,7 @@ export function lintText(text, rules, ctx) {
|
|
|
1060
1536
|
? rule.find(subject, ctx)
|
|
1061
1537
|
: [...subject.matchAll(rule.regex)].map((m) => ({ match: m[0] }))
|
|
1062
1538
|
for (const h of hits) {
|
|
1063
|
-
out.push({ line: i + 1, ruleId: rule.id, match: h.match, why: rule.why })
|
|
1539
|
+
out.push({ line: i + 1, ruleId: rule.id, match: h.match, why: h.why ?? rule.why })
|
|
1064
1540
|
}
|
|
1065
1541
|
}
|
|
1066
1542
|
})
|
|
@@ -1091,7 +1567,7 @@ export function lintText(text, rules, ctx) {
|
|
|
1091
1567
|
for (const h of rule.scan(rule.code ? strippedText : text, ctx)) {
|
|
1092
1568
|
const i = h.line != null ? h.line - 1 : lineOf(h.index ?? 0)
|
|
1093
1569
|
if (suppress[i]?.has(rule.id)) continue
|
|
1094
|
-
out.push({ line: i + 1, ruleId: rule.id, match: h.match, why: rule.why })
|
|
1570
|
+
out.push({ line: i + 1, ruleId: rule.id, match: h.match, why: h.why ?? rule.why })
|
|
1095
1571
|
}
|
|
1096
1572
|
}
|
|
1097
1573
|
}
|
|
@@ -9,12 +9,39 @@ export const DIMENSIONS = [
|
|
|
9
9
|
]
|
|
10
10
|
export const CHECKS = ["primary-flow", "keyboard", "overflow", "reduced-motion"]
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* One filled example of each artifact shape, so the required fields are in the
|
|
14
|
+
* template rather than discoverable only by submitting a review and reading the
|
|
15
|
+
* errors. Ids and paths say `example`, the hash is a placeholder that matches no
|
|
16
|
+
* file, and the validator rejects them until they are replaced with real
|
|
17
|
+
* captures — a copied template cannot pass on its examples.
|
|
18
|
+
*/
|
|
19
|
+
export const EXAMPLE_ARTIFACTS = [
|
|
20
|
+
{
|
|
21
|
+
id: "example-wide",
|
|
22
|
+
kind: "screenshot",
|
|
23
|
+
path: "captures/example-wide.png",
|
|
24
|
+
sha256: "replace-with-review.artifactHashes-after-inspecting-the-file",
|
|
25
|
+
viewport: { width: 1440, height: 900 },
|
|
26
|
+
state: "dashboard, loaded",
|
|
27
|
+
colorMode: "light",
|
|
28
|
+
motion: "full",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "example-keyboard",
|
|
32
|
+
kind: "report",
|
|
33
|
+
path: "captures/example-keyboard.md",
|
|
34
|
+
sha256: "replace-with-review.artifactHashes-after-inspecting-the-file",
|
|
35
|
+
state: "checkout flow, keyboard only",
|
|
36
|
+
},
|
|
37
|
+
]
|
|
38
|
+
|
|
12
39
|
export function reviewTemplate() {
|
|
13
40
|
return {
|
|
14
41
|
schemaVersion: 1,
|
|
15
42
|
build: { name: "", brief: "", revision: "", sourceHash: "" },
|
|
16
43
|
reviewer: { name: "", method: "agent" },
|
|
17
|
-
artifacts:
|
|
44
|
+
artifacts: EXAMPLE_ARTIFACTS.map((artifact) => structuredClone(artifact)),
|
|
18
45
|
dimensions: DIMENSIONS.map((id) => ({ id, status: "untested", observation: "", evidence: [] })),
|
|
19
46
|
checks: CHECKS.map((id) => ({ id, status: "untested", observation: "", evidence: [] })),
|
|
20
47
|
iterations: [],
|