From 0e1d4924917c8e12f0497168b922e6ce6d3316c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Vila=C3=A7a=20de=20Jesus?= Date: Sun, 25 Oct 2026 01:56:56 -0300 Subject: [PATCH 1/7] chore: start #569 From 537a6dd157060d698beeb6f867a39039a2d69615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Vila=C3=A7a=20de=20Jesus?= Date: Sun, 25 Oct 2026 02:10:52 -0300 Subject: [PATCH 2/7] feat: gallery index phase 1: facets, id ranges, index.json, md twins Closes #569 --- AGENTS.md | 12 + packages/ui/scripts/build-manifest.ts | 77 +- packages/ui/scripts/index-backfill.ts | 137 + packages/ui/scripts/index-build.ts | 156 + packages/ui/scripts/index-schema.ts | 389 + packages/ui/tests/demos-catalog.test.ts | 7 +- packages/ui/tests/demos-index.test.ts | 254 + scripts/generated-paths.sh | 2 +- site/public/api/v1/catalog.json | 15561 ++++++++++++++++ site/public/api/v1/index.json | 655 + site/public/gallery/001.md | 54 + site/public/gallery/002.md | 53 + site/public/gallery/003.md | 53 + site/public/gallery/004.md | 43 + site/public/gallery/005.md | 49 + site/public/gallery/006.md | 47 + site/public/gallery/007.md | 44 + site/public/gallery/008.md | 47 + site/public/gallery/009.md | 46 + site/public/gallery/010.md | 74 + site/public/gallery/011.md | 73 + site/public/gallery/012.md | 40 + site/public/gallery/013.md | 43 + site/public/gallery/014.md | 47 + site/public/gallery/015.md | 80 + site/public/gallery/016.md | 75 + site/public/gallery/017.md | 69 + site/public/gallery/018.md | 82 + site/public/gallery/019.md | 63 + site/public/gallery/020.md | 69 + site/public/gallery/021.md | 54 + site/public/gallery/022.md | 50 + site/public/gallery/023.md | 94 + site/public/gallery/024.md | 80 + site/public/gallery/025.md | 87 + site/public/gallery/026.md | 67 + site/public/gallery/027.md | 58 + site/public/gallery/028.md | 64 + site/public/gallery/029.md | 60 + site/public/gallery/030.md | 47 + site/public/gallery/031.md | 51 + site/public/gallery/032.md | 65 + site/public/gallery/033.md | 57 + site/public/gallery/034.md | 50 + site/public/gallery/035.md | 57 + site/public/gallery/036.md | 49 + site/public/gallery/037.md | 60 + site/public/gallery/038.md | 81 + site/public/gallery/039.md | 65 + site/public/gallery/040.md | 77 + site/public/gallery/041.md | 66 + site/public/gallery/042.md | 60 + site/public/gallery/043.md | 72 + site/public/gallery/044.md | 50 + site/public/gallery/045.md | 75 + site/public/gallery/046.md | 89 + site/public/gallery/047.md | 88 + site/public/gallery/048.md | 72 + site/public/gallery/049.md | 71 + site/public/gallery/050.md | 44 + site/public/gallery/051.md | 42 + site/public/gallery/052.md | 107 + site/public/gallery/053.md | 44 + site/public/gallery/054.md | 59 + site/public/gallery/055.md | 70 + site/public/gallery/056.md | 57 + site/public/gallery/057.md | 59 + site/public/gallery/058.md | 62 + site/public/gallery/059.md | 48 + site/public/gallery/060.md | 104 + site/public/gallery/061.md | 74 + site/public/gallery/062.md | 103 + site/public/gallery/063.md | 128 + site/public/gallery/064.md | 102 + site/public/gallery/065.md | 56 + site/public/gallery/066.md | 60 + site/public/gallery/067.md | 60 + site/public/gallery/068.md | 58 + site/public/gallery/069.md | 68 + site/public/gallery/070.md | 49 + site/public/gallery/071.md | 44 + site/public/gallery/072.md | 71 + site/public/gallery/073.md | 57 + site/public/gallery/074.md | 55 + site/public/gallery/075.md | 54 + site/public/gallery/076.md | 56 + site/public/gallery/077.md | 56 + site/public/gallery/078.md | 64 + site/public/gallery/079.md | 58 + site/public/gallery/080.md | 67 + site/public/gallery/081.md | 93 + site/public/gallery/082.md | 54 + site/public/gallery/083.md | 63 + site/public/gallery/084.md | 68 + site/public/gallery/085.md | 65 + site/public/gallery/086.md | 81 + site/public/gallery/087.md | 69 + site/public/gallery/088.md | 97 + site/public/gallery/089.md | 83 + site/public/gallery/090.md | 82 + site/public/gallery/091.md | 49 + site/public/gallery/092.md | 85 + site/public/gallery/093.md | 62 + site/public/gallery/094.md | 64 + site/public/gallery/095.md | 67 + site/public/gallery/096.md | 72 + site/public/gallery/097.md | 50 + site/public/gallery/098.md | 54 + site/public/gallery/099.md | 46 + site/public/gallery/100.md | 47 + site/public/gallery/101.md | 50 + site/public/gallery/102.md | 46 + site/public/gallery/103.md | 78 + site/public/gallery/104.md | 66 + site/public/gallery/105.md | 52 + site/public/gallery/106.md | 53 + site/public/gallery/107.md | 46 + site/public/gallery/108.md | 46 + site/public/gallery/109.md | 48 + site/public/gallery/110.md | 49 + site/public/gallery/111.md | 61 + site/public/gallery/112.md | 47 + site/public/gallery/113.md | 49 + site/public/gallery/114.md | 46 + site/public/gallery/115.md | 44 + site/public/gallery/116.md | 62 + site/public/gallery/117.md | 45 + site/public/gallery/118.md | 45 + site/public/gallery/119.md | 47 + site/public/gallery/120.md | 47 + site/public/gallery/121.md | 72 + site/public/gallery/122.md | 135 + site/public/gallery/123.md | 79 + site/public/gallery/124.md | 53 + site/public/gallery/125.md | 53 + site/public/gallery/126.md | 64 + site/public/gallery/127.md | 50 + site/public/gallery/128.md | 58 + site/public/gallery/129.md | 41 + site/public/gallery/130.md | 83 + site/public/gallery/131.md | 55 + site/public/gallery/132.md | 51 + site/public/gallery/133.md | 51 + site/public/gallery/134.md | 40 + site/public/gallery/135.md | 41 + site/public/gallery/136.md | 54 + site/public/gallery/137.md | 60 + site/public/gallery/138.md | 66 + site/public/gallery/139.md | 65 + site/public/gallery/140.md | 52 + site/public/gallery/141.md | 56 + site/public/gallery/142.md | 49 + site/public/gallery/143.md | 50 + site/public/gallery/144.md | 54 + site/public/gallery/145.md | 60 + site/public/gallery/146.md | 62 + site/public/gallery/147.md | 58 + site/public/gallery/148.md | 64 + site/public/gallery/149.md | 67 + site/public/gallery/150.md | 67 + site/public/gallery/151.md | 57 + site/public/gallery/152.md | 57 + site/public/gallery/153.md | 60 + site/public/gallery/154.md | 43 + site/public/gallery/155.md | 59 + site/public/gallery/156.md | 85 + site/public/gallery/157.md | 93 + site/public/gallery/158.md | 74 + site/public/gallery/159.md | 54 + site/public/gallery/160.md | 51 + site/public/gallery/161.md | 51 + site/public/gallery/162.md | 65 + site/public/gallery/163.md | 64 + site/public/gallery/164.md | 80 + site/public/gallery/165.md | 128 + site/public/gallery/166.md | 125 + site/public/gallery/167.md | 45 + site/public/gallery/168.md | 48 + site/public/gallery/169.md | 49 + site/public/gallery/170.md | 45 + site/public/gallery/171.md | 46 + site/public/gallery/172.md | 40 + site/public/gallery/173.md | 102 + site/public/gallery/174.md | 64 + site/public/gallery/175.md | 47 + site/public/gallery/176.md | 45 + site/public/gallery/177.md | 47 + site/public/gallery/178.md | 49 + site/public/gallery/179.md | 47 + site/public/gallery/180.md | 48 + site/public/gallery/181.md | 48 + site/public/gallery/182.md | 53 + site/public/gallery/183.md | 95 + site/public/gallery/184.md | 82 + site/public/gallery/185.md | 77 + site/public/gallery/186.md | 63 + site/public/gallery/187.md | 73 + site/public/gallery/188.md | 59 + site/public/gallery/189.md | 52 + site/public/gallery/190.md | 55 + site/public/gallery/191.md | 61 + site/public/gallery/192.md | 69 + site/public/gallery/193.md | 48 + site/public/gallery/194.md | 66 + site/public/gallery/195.md | 54 + site/public/gallery/196.md | 41 + site/public/gallery/197.md | 74 + site/public/gallery/198.md | 56 + site/public/gallery/199.md | 61 + site/public/gallery/200.md | 76 + site/public/gallery/201.md | 51 + site/public/gallery/202.md | 61 + site/public/gallery/203.md | 50 + site/public/gallery/204.md | 69 + site/public/gallery/205.md | 126 + site/public/gallery/206.md | 66 + site/public/gallery/207.md | 53 + site/public/gallery/208.md | 50 + site/public/gallery/209.md | 51 + site/public/gallery/210.md | 55 + site/public/gallery/211.md | 73 + site/public/gallery/212.md | 46 + site/public/gallery/213.md | 51 + site/public/gallery/214.md | 53 + site/public/gallery/215.md | 59 + site/public/gallery/216.md | 63 + site/public/gallery/217.md | 73 + site/public/gallery/218.md | 85 + site/public/gallery/219.md | 78 + site/public/gallery/220.md | 50 + site/public/gallery/221.md | 41 + site/public/gallery/222.md | 41 + site/public/gallery/223.md | 41 + site/public/gallery/224.md | 41 + site/public/gallery/225.md | 62 + site/public/gallery/226.md | 85 + site/public/gallery/227.md | 75 + site/public/gallery/228.md | 81 + site/public/gallery/229.md | 51 + site/public/gallery/230.md | 74 + site/public/gallery/231.md | 42 + site/public/gallery/232.md | 59 + site/public/gallery/233.md | 76 + site/public/gallery/234.md | 82 + site/public/gallery/235.md | 75 + site/public/gallery/236.md | 72 + site/public/gallery/237.md | 62 + site/public/gallery/238.md | 77 + site/public/gallery/239.md | 85 + site/public/gallery/240.md | 49 + site/public/gallery/241.md | 57 + site/public/gallery/242.md | 74 + site/public/gallery/243.md | 49 + site/public/gallery/244.md | 62 + site/public/gallery/245.md | 85 + site/public/gallery/246.md | 64 + site/public/gallery/247.md | 84 + site/public/gallery/248.md | 56 + site/public/gallery/249.md | 50 + site/public/gallery/250.md | 58 + site/public/gallery/251.md | 70 + site/public/gallery/252.md | 66 + site/public/gallery/253.md | 61 + site/public/gallery/254.md | 67 + site/public/gallery/255.md | 57 + site/public/gallery/256.md | 65 + site/public/gallery/300.md | 92 + site/public/gallery/301.md | 121 + site/public/gallery/302.md | 135 + site/public/gallery/303.md | 119 + site/public/gallery/304.md | 140 + site/public/gallery/305.md | 116 + site/public/gallery/306.md | 102 + site/public/gallery/307.md | 79 + site/public/gallery/320.md | 152 + site/public/gallery/321.md | 162 + site/public/gallery/322.md | 195 + site/public/gallery/323.md | 160 + site/public/gallery/324.md | 192 + site/public/gallery/325.md | 170 + site/public/gallery/326.md | 177 + site/public/gallery/340.md | 186 + site/public/gallery/341.md | 134 + site/public/gallery/342.md | 157 + site/public/gallery/343.md | 160 + site/public/gallery/344.md | 205 + site/public/gallery/345.md | 162 + site/public/gallery/346.md | 211 + site/public/gallery/347.md | 155 + site/public/gallery/348.md | 149 + site/public/gallery/360.md | 142 + site/public/gallery/361.md | 127 + site/public/gallery/362.md | 125 + site/public/gallery/363.md | 121 + site/public/gallery/364.md | 148 + site/public/gallery/365.md | 152 + site/public/gallery/366.md | 122 + site/public/gallery/380.md | 151 + site/public/gallery/381.md | 127 + site/public/gallery/382.md | 106 + site/public/gallery/383.md | 211 + site/public/gallery/384.md | 172 + site/public/gallery/385.md | 82 + site/public/gallery/386.md | 142 + site/public/gallery/387.md | 87 + site/public/gallery/388.md | 90 + site/public/gallery/389.md | 67 + site/public/gallery/400.md | 75 + site/public/gallery/401.md | 80 + site/public/gallery/402.md | 96 + site/public/gallery/420.md | 77 + site/public/gallery/421.md | 72 + site/public/gallery/422.md | 92 + site/public/gallery/430.md | 86 + site/public/gallery/431.md | 86 + site/public/gallery/432.md | 88 + site/public/gallery/433.md | 111 + site/public/gallery/460.md | 60 + site/public/gallery/461.md | 66 + site/public/gallery/462.md | 72 + site/public/gallery/470.md | 52 + site/public/gallery/480.md | 96 + site/public/gallery/481.md | 138 + site/public/gallery/482.md | 49 + site/public/gallery/490.md | 54 + site/public/gallery/491.md | 108 + site/public/gallery/492.md | 66 + site/public/gallery/500.md | 70 + site/public/gallery/501.md | 74 + site/public/gallery/502.md | 55 + site/public/gallery/530.md | 118 + site/public/gallery/531.md | 144 + site/public/gallery/540.md | 54 + site/public/gallery/541.md | 51 + site/public/gallery/542.md | 51 + site/public/gallery/543.md | 63 + site/public/gallery/600.md | 91 + site/public/gallery/601.md | 92 + site/public/gallery/610.md | 105 + site/public/gallery/640.md | 55 + site/public/gallery/641.md | 52 + site/public/gallery/642.md | 54 + site/public/gallery/700.md | 69 + site/public/gallery/720.md | 51 + site/public/gallery/721.md | 69 + site/public/gallery/760.md | 92 + site/public/gallery/761.md | 84 + site/public/gallery/780.md | 61 + site/public/gallery/781.md | 71 + site/public/gallery/800.md | 94 + site/public/gallery/840.md | 79 + site/public/gallery/841.md | 50 + site/public/gallery/842.md | 57 + site/public/gallery/843.md | 75 + site/public/gallery/844.md | 66 + site/public/gallery/845.md | 52 + site/public/gallery/860.md | 117 + site/public/gallery/861.md | 76 + site/public/gallery/880.md | 75 + site/public/gallery/881.md | 91 + site/public/gallery/900.md | 53 + site/public/gallery/901.md | 54 + site/public/gallery/902.md | 58 + site/public/gallery/910.md | 74 + site/public/gallery/940.md | 61 + site/public/gallery/941.md | 62 + site/public/gallery/942.md | 91 + site/public/gallery/943.md | 72 + site/public/gallery/944.md | 97 + site/public/gallery/945.md | 57 + site/public/gallery/946.md | 55 + site/public/gallery/947.md | 50 + site/public/gallery/960.md | 60 + site/public/gallery/961.md | 57 + site/public/gallery/962.md | 61 + site/public/gallery/963.md | 65 + site/public/gallery/964.md | 67 + site/public/gallery/965.md | 62 + site/public/gallery/966.md | 63 + site/public/gallery/980.md | 64 + site/public/gallery/981.md | 69 + site/public/gallery/982.md | 57 + site/public/gallery/983.md | 66 + site/public/gallery/984.md | 47 + site/public/gallery/985.md | 50 + site/public/gallery/986.md | 52 + site/public/gallery/987.md | 41 + .../src/demos/charts/bar/Horizontal.meta.json | 23 +- site/src/demos/charts/bar/Stacked.meta.json | 23 +- site/src/demos/charts/bar/Toggle.meta.json | 23 +- site/src/demos/charts/bar/Vertical.meta.json | 23 +- site/src/demos/charts/gauge/Counter.meta.json | 23 +- site/src/demos/charts/gauge/Ring.meta.json | 23 +- site/src/demos/charts/gauge/Simple.meta.json | 23 +- site/src/demos/charts/gauge/Zones.meta.json | 23 +- .../demos/charts/heatmap/Calendar.meta.json | 23 +- .../src/demos/charts/heatmap/Matrix.meta.json | 23 +- .../demos/charts/heatmap/YearToggle.meta.json | 23 +- site/src/demos/charts/line/Area.meta.json | 23 +- site/src/demos/charts/line/Multi.meta.json | 23 +- site/src/demos/charts/line/Simple.meta.json | 23 +- site/src/demos/charts/line/Stream.meta.json | 23 +- .../demos/charts/overview/Dashboard.meta.json | 36 +- site/src/demos/charts/pie/Animated.meta.json | 23 +- .../demos/charts/pie/CenterLabel.meta.json | 23 +- site/src/demos/charts/pie/Donut.meta.json | 23 +- site/src/demos/charts/pie/Rose.meta.json | 23 +- site/src/demos/charts/radar/Compare.meta.json | 23 +- .../demos/charts/radar/Interactive.meta.json | 23 +- site/src/demos/charts/radar/Simple.meta.json | 23 +- site/src/demos/charts/scatter/Basic.meta.json | 23 +- .../src/demos/charts/scatter/Bubble.meta.json | 23 +- .../demos/charts/scatter/Clusters.meta.json | 23 +- .../demos/charts/sparklines/Bars.meta.json | 23 +- .../charts/sparklines/KpiCards.meta.json | 23 +- .../demos/charts/sparklines/Live.meta.json | 23 +- .../charts/sparklines/TableRows.meta.json | 23 +- .../demos/display/alert/Closable.meta.json | 23 +- .../demos/display/alert/Severities.meta.json | 23 +- .../demos/display/alert/WithTitle.meta.json | 23 +- .../display/aspect-ratio/Embed.meta.json | 23 +- .../display/audio-player/Voice.meta.json | 23 +- .../demos/display/avatar/Decorative.meta.json | 23 +- .../display/avatar/IconFallback.meta.json | 23 +- .../src/demos/display/avatar/Images.meta.json | 23 +- .../demos/display/avatar/Initials.meta.json | 23 +- .../display/avatar/ShapeStatus.meta.json | 23 +- site/src/demos/display/avatar/Sizes.meta.json | 23 +- site/src/demos/display/avatar/Stack.meta.json | 23 +- site/src/demos/display/badge/Colors.meta.json | 23 +- site/src/demos/display/badge/Counts.meta.json | 23 +- .../demos/display/badge/InlinePill.meta.json | 23 +- .../demos/display/badge/OnAvatars.meta.json | 23 +- .../demos/display/carousel/Autoplay.meta.json | 23 +- .../demos/display/carousel/Cards.meta.json | 23 +- .../display/carousel/Highlights.meta.json | 23 +- site/src/demos/display/chip/Basic.meta.json | 23 +- .../demos/display/chip/Deletable.meta.json | 23 +- site/src/demos/display/chip/Icons.meta.json | 23 +- .../demos/display/chip/Selectable.meta.json | 23 +- site/src/demos/display/chip/Status.meta.json | 23 +- .../src/demos/display/chip/Variants.meta.json | 23 +- .../display/code-snippet/Languages.meta.json | 23 +- .../display/conversation-item/Inbox.meta.json | 23 +- .../display/data-table/Controlled.meta.json | 23 +- .../demos/display/data-table/Groups.meta.json | 23 +- .../demos/display/data-table/Simple.meta.json | 23 +- .../display/divider/Horizontal.meta.json | 23 +- .../demos/display/divider/Vertical.meta.json | 23 +- .../display/external-link/InTable.meta.json | 23 +- .../demos/display/heading/Display.meta.json | 23 +- .../display/highlight/Animated.meta.json | 23 +- site/src/demos/display/image/Basic.meta.json | 23 +- site/src/demos/display/image/Ratio.meta.json | 23 +- .../demos/display/image/Responsive.meta.json | 23 +- .../demos/display/kanban/Pipeline.meta.json | 23 +- .../src/demos/display/kanban/Totals.meta.json | 23 +- site/src/demos/display/kbd/Basic.meta.json | 23 +- .../demos/display/live-list/Stream.meta.json | 23 +- .../display/media-grid/Gallery.meta.json | 23 +- .../message-bubble/Conversation.meta.json | 23 +- .../message-bubble/QuoteReactions.meta.json | 23 +- .../display/message-bubble/Status.meta.json | 23 +- .../notification-bell/Controlled.meta.json | 23 +- .../notification-bell/Default.meta.json | 23 +- .../demos/display/pagination/Basic.meta.json | 23 +- .../display/pagination/FewItems.meta.json | 23 +- .../display/pagination/Siblings.meta.json | 23 +- .../display/scroll-area/Horizontal.meta.json | 23 +- .../display/scroll-area/Vertical.meta.json | 23 +- .../display/strike-through/Rewrite.meta.json | 23 +- site/src/demos/display/table/Cards.meta.json | 23 +- site/src/demos/display/table/Dense.meta.json | 23 +- site/src/demos/display/table/Scroll.meta.json | 23 +- .../demos/display/timeline/Simple.meta.json | 23 +- .../demos/display/timeline/Variants.meta.json | 23 +- .../demos/display/tooltip/Children.meta.json | 23 +- .../src/demos/display/tooltip/Delay.meta.json | 23 +- .../demos/display/tooltip/OnIcons.meta.json | 23 +- .../src/demos/display/tooltip/Sides.meta.json | 23 +- .../display/virtual-list/History.meta.json | 23 +- site/src/demos/docs-kit/docs/Cards.meta.json | 23 +- .../demos/docs-kit/docs/CodeTabs.meta.json | 23 +- site/src/demos/docs-kit/docs/NavToc.meta.json | 23 +- site/src/demos/docs-kit/docs/Prose.meta.json | 23 +- site/src/demos/docs-kit/docs/Search.meta.json | 23 +- .../feedback/alert-dialog/Async.meta.json | 23 +- .../feedback/alert-dialog/Basic.meta.json | 23 +- .../alert-dialog/Controlled.meta.json | 23 +- .../alert-dialog/TypeToConfirm.meta.json | 23 +- .../feedback/dialog/Chromeless.meta.json | 23 +- .../feedback/dialog/Confirmation.meta.json | 23 +- site/src/demos/feedback/dialog/Form.meta.json | 23 +- site/src/demos/feedback/dialog/Info.meta.json | 23 +- .../src/demos/feedback/dialog/Split.meta.json | 23 +- .../feedback/dialog/SplitConfigured.meta.json | 23 +- .../feedback/drawer/Chromeless.meta.json | 23 +- .../feedback/drawer/CustomHeader.meta.json | 23 +- .../feedback/drawer/Notifications.meta.json | 23 +- .../src/demos/feedback/drawer/Right.meta.json | 23 +- .../src/demos/feedback/drawer/Sides.meta.json | 23 +- .../install-prompt/IosInstructions.meta.json | 23 +- .../install-prompt/NativePrompt.meta.json | 23 +- .../demos/feedback/lightbox/Basic.meta.json | 23 +- .../demos/feedback/lightbox/Gallery.meta.json | 23 +- .../feedback/progress/Animated.meta.json | 23 +- .../feedback/progress/Determinate.meta.json | 23 +- .../feedback/progress/Indeterminate.meta.json | 23 +- .../demos/feedback/progress/Sizes.meta.json | 23 +- .../src/demos/feedback/sheet/Footer.meta.json | 23 +- site/src/demos/feedback/sheet/Swipe.meta.json | 23 +- .../feedback/skeleton/CardLayout.meta.json | 23 +- .../demos/feedback/skeleton/Image.meta.json | 23 +- .../feedback/skeleton/SkeletonProp.meta.json | 23 +- .../feedback/skeleton/TextLines.meta.json | 23 +- .../demos/feedback/spinner/InButton.meta.json | 23 +- .../feedback/spinner/SectionLoader.meta.json | 23 +- .../demos/feedback/spinner/Sizes.meta.json | 23 +- .../feedback/splash-screen/Boot.meta.json | 23 +- .../demos/feedback/toast/Actions.meta.json | 23 +- .../src/demos/feedback/toast/Dedupe.meta.json | 23 +- .../src/demos/feedback/toast/Toasts.meta.json | 23 +- .../demos/foundation/colors/Brand.meta.json | 23 +- .../demos/foundation/colors/Outline.meta.json | 23 +- .../foundation/colors/Semantic.meta.json | 23 +- .../foundation/colors/Surfaces.meta.json | 23 +- .../demos/foundation/colors/Text.meta.json | 23 +- .../foundation/drag-and-drop/Board.meta.json | 23 +- .../drag-and-drop/DropZones.meta.json | 23 +- .../drag-and-drop/SortableList.meta.json | 23 +- .../materials/GlassButtons.meta.json | 23 +- .../foundation/materials/Tiers.meta.json | 23 +- .../foundation/materials/Tinted.meta.json | 23 +- .../demos/foundation/shadows/Scale.meta.json | 23 +- .../foundation/typography/Body.meta.json | 23 +- .../foundation/typography/Families.meta.json | 23 +- .../foundation/typography/Headings.meta.json | 23 +- .../foundation/typography/Roles.meta.json | 23 +- .../demos/inputs/button-group/Basic.meta.json | 23 +- .../inputs/button-group/SplitMenu.meta.json | 23 +- .../inputs/button/AlignBetween.meta.json | 23 +- .../inputs/button/ComponentTokens.meta.json | 23 +- .../demos/inputs/button/FullWidth.meta.json | 23 +- site/src/demos/inputs/button/Icons.meta.json | 23 +- .../src/demos/inputs/button/Loading.meta.json | 23 +- .../demos/inputs/button/NeutralTone.meta.json | 23 +- site/src/demos/inputs/button/Sizes.meta.json | 23 +- site/src/demos/inputs/button/States.meta.json | 23 +- .../inputs/button/TrailingHint.meta.json | 23 +- .../demos/inputs/button/Variants.meta.json | 23 +- .../demos/inputs/checkbox/Disabled.meta.json | 23 +- .../inputs/checkbox/RangeSelect.meta.json | 23 +- .../demos/inputs/checkbox/States.meta.json | 23 +- .../inputs/checkbox/WithoutLabel.meta.json | 23 +- .../inputs/chip-group/FormError.meta.json | 23 +- .../demos/inputs/chip-group/Wrap.meta.json | 23 +- .../src/demos/inputs/combobox/Basic.meta.json | 23 +- .../demos/inputs/combobox/Creatable.meta.json | 23 +- .../demos/inputs/combobox/Multiple.meta.json | 23 +- .../demos/inputs/combobox/States.meta.json | 23 +- .../inputs/copy-button/Default.meta.json | 23 +- .../inputs/copy-button/IconToast.meta.json | 23 +- .../inputs/copy-button/Variants.meta.json | 23 +- .../inputs/date-picker/Calendars.meta.json | 23 +- .../inputs/date-picker/Constraints.meta.json | 23 +- .../demos/inputs/date-picker/Fields.meta.json | 23 +- .../inputs/date-picker/Pickers.meta.json | 23 +- .../inputs/export-menu/Default.meta.json | 23 +- .../demos/inputs/file-upload/Basic.meta.json | 23 +- .../file-upload/ExternalTrigger.meta.json | 23 +- .../inputs/file-upload/Variants.meta.json | 23 +- .../demos/inputs/filter-group/Basic.meta.json | 23 +- .../inputs/masked-field/Documents.meta.json | 23 +- .../inputs/masked-field/MaskAction.meta.json | 23 +- .../inputs/masked-field/PhoneCep.meta.json | 23 +- .../inputs/money-input/Constraints.meta.json | 23 +- .../inputs/money-input/Currency.meta.json | 23 +- .../inputs/money-input/Default.meta.json | 23 +- .../inputs/money-input/Helpers.meta.json | 23 +- .../demos/inputs/number-field/Basic.meta.json | 23 +- .../inputs/number-field/States.meta.json | 23 +- .../inputs/password-strength/Custom.meta.json | 23 +- .../inputs/password-strength/Signup.meta.json | 23 +- .../demos/inputs/pin-input/Default.meta.json | 23 +- .../demos/inputs/pin-input/States.meta.json | 23 +- site/src/demos/inputs/radio/Cards.meta.json | 23 +- .../src/demos/inputs/radio/Disabled.meta.json | 23 +- .../demos/inputs/radio/Horizontal.meta.json | 23 +- .../src/demos/inputs/radio/Vertical.meta.json | 23 +- .../segmented-control/Default.meta.json | 23 +- .../inputs/segmented-control/Sizes.meta.json | 23 +- site/src/demos/inputs/select/Basic.meta.json | 23 +- .../demos/inputs/select/EmptyOption.meta.json | 23 +- .../demos/inputs/select/Placeholder.meta.json | 23 +- site/src/demos/inputs/select/States.meta.json | 23 +- .../demos/inputs/setting-item/Basic.meta.json | 23 +- site/src/demos/inputs/slider/Range.meta.json | 23 +- site/src/demos/inputs/slider/Single.meta.json | 23 +- .../inputs/slider/StepDisabled.meta.json | 23 +- .../inputs/swatch-group/Custom.meta.json | 23 +- .../inputs/swatch-group/Presets.meta.json | 23 +- site/src/demos/inputs/switch/Basic.meta.json | 23 +- .../demos/inputs/switch/Disabled.meta.json | 23 +- .../inputs/switch/SettingsList.meta.json | 23 +- .../inputs/switch/WithoutLabel.meta.json | 23 +- .../demos/inputs/text-area/Basic.meta.json | 23 +- .../demos/inputs/text-area/States.meta.json | 23 +- .../inputs/text-field/AddonButtons.meta.json | 23 +- .../demos/inputs/text-field/Addons.meta.json | 23 +- .../demos/inputs/text-field/Default.meta.json | 23 +- .../inputs/text-field/HiddenLabel.meta.json | 23 +- .../demos/inputs/text-field/Icons.meta.json | 23 +- .../inputs/text-field/Password.meta.json | 23 +- .../demos/inputs/text-field/States.meta.json | 23 +- .../demos/inputs/theme-toggle/Basic.meta.json | 23 +- .../inputs/toggle-group/Selection.meta.json | 23 +- .../inputs/toggle-group/Variants.meta.json | 23 +- .../demos/inputs/turnstile/SignIn.meta.json | 23 +- .../demos/inputs/use-form/NoSchema.meta.json | 23 +- .../demos/inputs/use-form/Submit.meta.json | 23 +- .../inputs/use-form/WithSchema.meta.json | 23 +- .../demos/layout/app-shell/Anatomy.meta.json | 23 +- .../layout/app-shell/ItemExtras.meta.json | 30 +- .../layout/app-shell/RailLabels.meta.json | 23 +- .../layout/app-shell/ResizablePanel.meta.json | 23 +- .../demos/layout/app-shell/Shell.meta.json | 23 +- .../layout/app-shell/ShortPanel.meta.json | 23 +- .../demos/layout/app-shell/Stage.meta.json | 23 +- .../layout/auth-layout/FlatPanel.meta.json | 23 +- .../domain-tokens/StatusTokens.meta.json | 23 +- .../demos/layout/empty-state/Basic.meta.json | 23 +- .../layout/empty-state/DenseCrm.meta.json | 31 +- .../demos/layout/empty-state/Hero.meta.json | 23 +- .../demos/layout/empty-state/NoIcon.meta.json | 23 +- .../layout/empty-state/NoResults.meta.json | 23 +- .../layout/empty-state/Offline.meta.json | 23 +- .../layout/error-page/Statuses.meta.json | 23 +- .../demos/layout/form-page/Basic.meta.json | 23 +- .../demos/layout/form-page/NoHeader.meta.json | 23 +- .../demos/layout/kpi-card/Metrics.meta.json | 23 +- .../layout/kpi-card/NegativeTrend.meta.json | 23 +- .../demos/layout/kpi-card/NoTrend.meta.json | 23 +- .../demos/layout/kpi-card/Sparkline.meta.json | 23 +- .../src/demos/layout/kpi-card/Stats.meta.json | 23 +- .../demos/layout/kpi-card/TrendTone.meta.json | 23 +- site/src/demos/layout/kpi-grid/Four.meta.json | 23 +- site/src/demos/layout/kpi-grid/Odd.meta.json | 23 +- .../layout/layout-primitives/Page.meta.json | 23 +- .../layout-primitives/Responsive.meta.json | 23 +- .../layout/layout-primitives/Stack.meta.json | 23 +- .../layout/list-page/AppListDemo.meta.json | 23 +- .../list-page/AppSettingsDemo.meta.json | 23 +- .../layout/list-page/PageShellDemo.meta.json | 23 +- .../demos/layout/mobile-pane/Chat.meta.json | 23 +- .../src/demos/layout/page-frame/Map.meta.json | 23 +- .../layout/page-shell/AsideReading.meta.json | 23 +- .../layout/page-shell/Dashboard.meta.json | 23 +- .../layout/preview-stage/Inline.meta.json | 23 +- .../demos/layout/preview-stage/Page.meta.json | 23 +- .../demos/layout/record-list/Apps.meta.json | 32 +- .../demos/layout/record-list/NoMenu.meta.json | 23 +- .../demos/layout/record-list/Phone.meta.json | 31 +- .../layout/resizable-panels/Nested.meta.json | 23 +- .../resizable-panels/Workspace.meta.json | 23 +- .../layout/sticky-actions/Dirty.meta.json | 23 +- .../layout/sticky-actions/Stretch.meta.json | 23 +- .../layout/theme-island/FullPage.meta.json | 23 +- .../layout/theme-island/Scoped.meta.json | 23 +- site/src/demos/map/stage/ThemedMap.meta.json | 23 +- .../demos/motion/basics/FadeRise.meta.json | 23 +- .../motion/hero-ambient/ClickRipple.meta.json | 23 +- .../hero-ambient/CompareSlider.meta.json | 23 +- .../hero-ambient/CursorSpotlight.meta.json | 23 +- .../motion/hero-ambient/CursorTrail.meta.json | 23 +- .../hero-ambient/GradientField.meta.json | 23 +- .../motion/hero-ambient/HeroIntro.meta.json | 23 +- .../motion/hero-ambient/TiltGlare.meta.json | 23 +- .../demos/motion/micro/AddToCartFly.meta.json | 23 +- .../demos/motion/micro/CopyCheck.meta.json | 23 +- .../motion/micro/HoldToConfirm.meta.json | 23 +- .../demos/motion/micro/LikeBurst.meta.json | 23 +- .../motion/micro/MagneticButton.meta.json | 23 +- .../demos/motion/micro/MorphToggle.meta.json | 23 +- .../demos/motion/micro/PressSpring.meta.json | 23 +- .../demos/motion/micro/SpringSwitch.meta.json | 23 +- .../src/demos/motion/micro/TiltCard.meta.json | 23 +- .../motion/scroll/HorizontalScroll.meta.json | 23 +- .../demos/motion/scroll/Parallax.meta.json | 23 +- .../demos/motion/scroll/PinnedSteps.meta.json | 23 +- .../motion/scroll/ReadingProgress.meta.json | 23 +- .../motion/scroll/ScrollCounter.meta.json | 23 +- .../motion/scroll/StaggerReveal.meta.json | 23 +- .../demos/motion/scroll/ViewReveal.meta.json | 23 +- site/src/demos/motion/text/BlurIn.meta.json | 23 +- site/src/demos/motion/text/CountUp.meta.json | 23 +- site/src/demos/motion/text/Marquee.meta.json | 23 +- site/src/demos/motion/text/Odometer.meta.json | 23 +- site/src/demos/motion/text/Scramble.meta.json | 23 +- .../demos/motion/text/ShineSweep.meta.json | 23 +- .../demos/motion/text/SplitReveal.meta.json | 23 +- .../demos/motion/text/Typewriter.meta.json | 23 +- .../motion/transitions/FilterList.meta.json | 23 +- .../motion/transitions/FlipReorder.meta.json | 23 +- .../motion/transitions/HeightSpring.meta.json | 23 +- .../transitions/SharedElement.meta.json | 23 +- .../motion/transitions/SheetSpring.meta.json | 23 +- .../transitions/StackedToasts.meta.json | 23 +- .../motion/transitions/TabIndicator.meta.json | 23 +- .../navigation/actions/Autofocus.meta.json | 23 +- .../navigation/actions/ClickOutside.meta.json | 23 +- .../navigation/actions/Clipboard.meta.json | 23 +- .../navigation/actions/FocusTrap.meta.json | 23 +- .../navigation/actions/Longpress.meta.json | 23 +- .../navigation/app-bar/Default.meta.json | 23 +- .../navigation/app-bar/WithLeading.meta.json | 23 +- .../navigation/breadcrumb/Levels.meta.json | 23 +- .../navigation/breadcrumb/Separator.meta.json | 23 +- .../command-palette/Async.meta.json | 23 +- .../navigation/command-palette/Keys.meta.json | 23 +- .../command-palette/Palette.meta.json | 23 +- .../navigation/context-menu/Files.meta.json | 30 +- .../navigation/context-menu/Rows.meta.json | 23 +- .../demos/navigation/drawer/Sides.meta.json | 23 +- .../hover-card/Controlled.meta.json | 23 +- .../navigation/hover-card/Profile.meta.json | 23 +- .../loading-boundary/Delayed.meta.json | 23 +- .../locale-switcher/Basic.meta.json | 23 +- .../locale-switcher/InHeader.meta.json | 23 +- .../navigation/menu/BrandIcons.meta.json | 29 +- .../navigation/menu/ContextMenu.meta.json | 23 +- .../demos/navigation/menu/Default.meta.json | 29 +- .../navigation/menu/IconTrigger.meta.json | 23 +- .../demos/navigation/menu/Selection.meta.json | 31 +- .../demos/navigation/menu/Submenu.meta.json | 30 +- .../navigation/menubar/Compact.meta.json | 23 +- .../demos/navigation/menubar/Editor.meta.json | 34 +- .../navigation-menu/Delays.meta.json | 23 +- .../navigation-menu/Header.meta.json | 23 +- .../demos/navigation/popover/Basic.meta.json | 23 +- .../navigation/popover/CloseOnPick.meta.json | 23 +- .../demos/navigation/popover/Form.meta.json | 23 +- .../navigation/stepper/Horizontal.meta.json | 23 +- .../navigation/tabs/Controlled.meta.json | 23 +- .../demos/navigation/tabs/Default.meta.json | 23 +- .../src/demos/navigation/tabs/Icons.meta.json | 23 +- site/src/demos/navigation/tabs/Line.meta.json | 23 +- .../navigation/tabs/Scrollable.meta.json | 23 +- .../surfaces/accordion/Controlled.meta.json | 23 +- .../surfaces/accordion/Multiple.meta.json | 23 +- .../demos/surfaces/accordion/Single.meta.json | 23 +- .../src/demos/surfaces/card/Accents.meta.json | 23 +- .../demos/surfaces/card/ContentCard.meta.json | 23 +- .../surfaces/card/HeaderActions.meta.json | 23 +- site/src/demos/surfaces/card/Link.meta.json | 23 +- .../src/demos/surfaces/card/Stacked.meta.json | 23 +- site/src/demos/surfaces/card/Stats.meta.json | 23 +- .../surfaces/collapsible/Default.meta.json | 23 +- .../demos/surfaces/collapsible/Open.meta.json | 23 +- .../surfaces/liquid-glass/Basic.meta.json | 23 +- .../surfaces/surface/Elevation.meta.json | 23 +- .../demos/surfaces/surface/Grain.meta.json | 23 +- .../surfaces/surface/Interactive.meta.json | 23 +- .../surfaces/surface/Materials.meta.json | 23 +- .../surfaces/surface/ToneVariant.meta.json | 23 +- 764 files changed, 53152 insertions(+), 390 deletions(-) create mode 100644 packages/ui/scripts/index-backfill.ts create mode 100644 packages/ui/scripts/index-build.ts create mode 100644 packages/ui/scripts/index-schema.ts create mode 100644 packages/ui/tests/demos-index.test.ts create mode 100644 site/public/api/v1/index.json create mode 100644 site/public/gallery/001.md create mode 100644 site/public/gallery/002.md create mode 100644 site/public/gallery/003.md create mode 100644 site/public/gallery/004.md create mode 100644 site/public/gallery/005.md create mode 100644 site/public/gallery/006.md create mode 100644 site/public/gallery/007.md create mode 100644 site/public/gallery/008.md create mode 100644 site/public/gallery/009.md create mode 100644 site/public/gallery/010.md create mode 100644 site/public/gallery/011.md create mode 100644 site/public/gallery/012.md create mode 100644 site/public/gallery/013.md create mode 100644 site/public/gallery/014.md create mode 100644 site/public/gallery/015.md create mode 100644 site/public/gallery/016.md create mode 100644 site/public/gallery/017.md create mode 100644 site/public/gallery/018.md create mode 100644 site/public/gallery/019.md create mode 100644 site/public/gallery/020.md create mode 100644 site/public/gallery/021.md create mode 100644 site/public/gallery/022.md create mode 100644 site/public/gallery/023.md create mode 100644 site/public/gallery/024.md create mode 100644 site/public/gallery/025.md create mode 100644 site/public/gallery/026.md create mode 100644 site/public/gallery/027.md create mode 100644 site/public/gallery/028.md create mode 100644 site/public/gallery/029.md create mode 100644 site/public/gallery/030.md create mode 100644 site/public/gallery/031.md create mode 100644 site/public/gallery/032.md create mode 100644 site/public/gallery/033.md create mode 100644 site/public/gallery/034.md create mode 100644 site/public/gallery/035.md create mode 100644 site/public/gallery/036.md create mode 100644 site/public/gallery/037.md create mode 100644 site/public/gallery/038.md create mode 100644 site/public/gallery/039.md create mode 100644 site/public/gallery/040.md create mode 100644 site/public/gallery/041.md create mode 100644 site/public/gallery/042.md create mode 100644 site/public/gallery/043.md create mode 100644 site/public/gallery/044.md create mode 100644 site/public/gallery/045.md create mode 100644 site/public/gallery/046.md create mode 100644 site/public/gallery/047.md create mode 100644 site/public/gallery/048.md create mode 100644 site/public/gallery/049.md create mode 100644 site/public/gallery/050.md create mode 100644 site/public/gallery/051.md create mode 100644 site/public/gallery/052.md create mode 100644 site/public/gallery/053.md create mode 100644 site/public/gallery/054.md create mode 100644 site/public/gallery/055.md create mode 100644 site/public/gallery/056.md create mode 100644 site/public/gallery/057.md create mode 100644 site/public/gallery/058.md create mode 100644 site/public/gallery/059.md create mode 100644 site/public/gallery/060.md create mode 100644 site/public/gallery/061.md create mode 100644 site/public/gallery/062.md create mode 100644 site/public/gallery/063.md create mode 100644 site/public/gallery/064.md create mode 100644 site/public/gallery/065.md create mode 100644 site/public/gallery/066.md create mode 100644 site/public/gallery/067.md create mode 100644 site/public/gallery/068.md create mode 100644 site/public/gallery/069.md create mode 100644 site/public/gallery/070.md create mode 100644 site/public/gallery/071.md create mode 100644 site/public/gallery/072.md create mode 100644 site/public/gallery/073.md create mode 100644 site/public/gallery/074.md create mode 100644 site/public/gallery/075.md create mode 100644 site/public/gallery/076.md create mode 100644 site/public/gallery/077.md create mode 100644 site/public/gallery/078.md create mode 100644 site/public/gallery/079.md create mode 100644 site/public/gallery/080.md create mode 100644 site/public/gallery/081.md create mode 100644 site/public/gallery/082.md create mode 100644 site/public/gallery/083.md create mode 100644 site/public/gallery/084.md create mode 100644 site/public/gallery/085.md create mode 100644 site/public/gallery/086.md create mode 100644 site/public/gallery/087.md create mode 100644 site/public/gallery/088.md create mode 100644 site/public/gallery/089.md create mode 100644 site/public/gallery/090.md create mode 100644 site/public/gallery/091.md create mode 100644 site/public/gallery/092.md create mode 100644 site/public/gallery/093.md create mode 100644 site/public/gallery/094.md create mode 100644 site/public/gallery/095.md create mode 100644 site/public/gallery/096.md create mode 100644 site/public/gallery/097.md create mode 100644 site/public/gallery/098.md create mode 100644 site/public/gallery/099.md create mode 100644 site/public/gallery/100.md create mode 100644 site/public/gallery/101.md create mode 100644 site/public/gallery/102.md create mode 100644 site/public/gallery/103.md create mode 100644 site/public/gallery/104.md create mode 100644 site/public/gallery/105.md create mode 100644 site/public/gallery/106.md create mode 100644 site/public/gallery/107.md create mode 100644 site/public/gallery/108.md create mode 100644 site/public/gallery/109.md create mode 100644 site/public/gallery/110.md create mode 100644 site/public/gallery/111.md create mode 100644 site/public/gallery/112.md create mode 100644 site/public/gallery/113.md create mode 100644 site/public/gallery/114.md create mode 100644 site/public/gallery/115.md create mode 100644 site/public/gallery/116.md create mode 100644 site/public/gallery/117.md create mode 100644 site/public/gallery/118.md create mode 100644 site/public/gallery/119.md create mode 100644 site/public/gallery/120.md create mode 100644 site/public/gallery/121.md create mode 100644 site/public/gallery/122.md create mode 100644 site/public/gallery/123.md create mode 100644 site/public/gallery/124.md create mode 100644 site/public/gallery/125.md create mode 100644 site/public/gallery/126.md create mode 100644 site/public/gallery/127.md create mode 100644 site/public/gallery/128.md create mode 100644 site/public/gallery/129.md create mode 100644 site/public/gallery/130.md create mode 100644 site/public/gallery/131.md create mode 100644 site/public/gallery/132.md create mode 100644 site/public/gallery/133.md create mode 100644 site/public/gallery/134.md create mode 100644 site/public/gallery/135.md create mode 100644 site/public/gallery/136.md create mode 100644 site/public/gallery/137.md create mode 100644 site/public/gallery/138.md create mode 100644 site/public/gallery/139.md create mode 100644 site/public/gallery/140.md create mode 100644 site/public/gallery/141.md create mode 100644 site/public/gallery/142.md create mode 100644 site/public/gallery/143.md create mode 100644 site/public/gallery/144.md create mode 100644 site/public/gallery/145.md create mode 100644 site/public/gallery/146.md create mode 100644 site/public/gallery/147.md create mode 100644 site/public/gallery/148.md create mode 100644 site/public/gallery/149.md create mode 100644 site/public/gallery/150.md create mode 100644 site/public/gallery/151.md create mode 100644 site/public/gallery/152.md create mode 100644 site/public/gallery/153.md create mode 100644 site/public/gallery/154.md create mode 100644 site/public/gallery/155.md create mode 100644 site/public/gallery/156.md create mode 100644 site/public/gallery/157.md create mode 100644 site/public/gallery/158.md create mode 100644 site/public/gallery/159.md create mode 100644 site/public/gallery/160.md create mode 100644 site/public/gallery/161.md create mode 100644 site/public/gallery/162.md create mode 100644 site/public/gallery/163.md create mode 100644 site/public/gallery/164.md create mode 100644 site/public/gallery/165.md create mode 100644 site/public/gallery/166.md create mode 100644 site/public/gallery/167.md create mode 100644 site/public/gallery/168.md create mode 100644 site/public/gallery/169.md create mode 100644 site/public/gallery/170.md create mode 100644 site/public/gallery/171.md create mode 100644 site/public/gallery/172.md create mode 100644 site/public/gallery/173.md create mode 100644 site/public/gallery/174.md create mode 100644 site/public/gallery/175.md create mode 100644 site/public/gallery/176.md create mode 100644 site/public/gallery/177.md create mode 100644 site/public/gallery/178.md create mode 100644 site/public/gallery/179.md create mode 100644 site/public/gallery/180.md create mode 100644 site/public/gallery/181.md create mode 100644 site/public/gallery/182.md create mode 100644 site/public/gallery/183.md create mode 100644 site/public/gallery/184.md create mode 100644 site/public/gallery/185.md create mode 100644 site/public/gallery/186.md create mode 100644 site/public/gallery/187.md create mode 100644 site/public/gallery/188.md create mode 100644 site/public/gallery/189.md create mode 100644 site/public/gallery/190.md create mode 100644 site/public/gallery/191.md create mode 100644 site/public/gallery/192.md create mode 100644 site/public/gallery/193.md create mode 100644 site/public/gallery/194.md create mode 100644 site/public/gallery/195.md create mode 100644 site/public/gallery/196.md create mode 100644 site/public/gallery/197.md create mode 100644 site/public/gallery/198.md create mode 100644 site/public/gallery/199.md create mode 100644 site/public/gallery/200.md create mode 100644 site/public/gallery/201.md create mode 100644 site/public/gallery/202.md create mode 100644 site/public/gallery/203.md create mode 100644 site/public/gallery/204.md create mode 100644 site/public/gallery/205.md create mode 100644 site/public/gallery/206.md create mode 100644 site/public/gallery/207.md create mode 100644 site/public/gallery/208.md create mode 100644 site/public/gallery/209.md create mode 100644 site/public/gallery/210.md create mode 100644 site/public/gallery/211.md create mode 100644 site/public/gallery/212.md create mode 100644 site/public/gallery/213.md create mode 100644 site/public/gallery/214.md create mode 100644 site/public/gallery/215.md create mode 100644 site/public/gallery/216.md create mode 100644 site/public/gallery/217.md create mode 100644 site/public/gallery/218.md create mode 100644 site/public/gallery/219.md create mode 100644 site/public/gallery/220.md create mode 100644 site/public/gallery/221.md create mode 100644 site/public/gallery/222.md create mode 100644 site/public/gallery/223.md create mode 100644 site/public/gallery/224.md create mode 100644 site/public/gallery/225.md create mode 100644 site/public/gallery/226.md create mode 100644 site/public/gallery/227.md create mode 100644 site/public/gallery/228.md create mode 100644 site/public/gallery/229.md create mode 100644 site/public/gallery/230.md create mode 100644 site/public/gallery/231.md create mode 100644 site/public/gallery/232.md create mode 100644 site/public/gallery/233.md create mode 100644 site/public/gallery/234.md create mode 100644 site/public/gallery/235.md create mode 100644 site/public/gallery/236.md create mode 100644 site/public/gallery/237.md create mode 100644 site/public/gallery/238.md create mode 100644 site/public/gallery/239.md create mode 100644 site/public/gallery/240.md create mode 100644 site/public/gallery/241.md create mode 100644 site/public/gallery/242.md create mode 100644 site/public/gallery/243.md create mode 100644 site/public/gallery/244.md create mode 100644 site/public/gallery/245.md create mode 100644 site/public/gallery/246.md create mode 100644 site/public/gallery/247.md create mode 100644 site/public/gallery/248.md create mode 100644 site/public/gallery/249.md create mode 100644 site/public/gallery/250.md create mode 100644 site/public/gallery/251.md create mode 100644 site/public/gallery/252.md create mode 100644 site/public/gallery/253.md create mode 100644 site/public/gallery/254.md create mode 100644 site/public/gallery/255.md create mode 100644 site/public/gallery/256.md create mode 100644 site/public/gallery/300.md create mode 100644 site/public/gallery/301.md create mode 100644 site/public/gallery/302.md create mode 100644 site/public/gallery/303.md create mode 100644 site/public/gallery/304.md create mode 100644 site/public/gallery/305.md create mode 100644 site/public/gallery/306.md create mode 100644 site/public/gallery/307.md create mode 100644 site/public/gallery/320.md create mode 100644 site/public/gallery/321.md create mode 100644 site/public/gallery/322.md create mode 100644 site/public/gallery/323.md create mode 100644 site/public/gallery/324.md create mode 100644 site/public/gallery/325.md create mode 100644 site/public/gallery/326.md create mode 100644 site/public/gallery/340.md create mode 100644 site/public/gallery/341.md create mode 100644 site/public/gallery/342.md create mode 100644 site/public/gallery/343.md create mode 100644 site/public/gallery/344.md create mode 100644 site/public/gallery/345.md create mode 100644 site/public/gallery/346.md create mode 100644 site/public/gallery/347.md create mode 100644 site/public/gallery/348.md create mode 100644 site/public/gallery/360.md create mode 100644 site/public/gallery/361.md create mode 100644 site/public/gallery/362.md create mode 100644 site/public/gallery/363.md create mode 100644 site/public/gallery/364.md create mode 100644 site/public/gallery/365.md create mode 100644 site/public/gallery/366.md create mode 100644 site/public/gallery/380.md create mode 100644 site/public/gallery/381.md create mode 100644 site/public/gallery/382.md create mode 100644 site/public/gallery/383.md create mode 100644 site/public/gallery/384.md create mode 100644 site/public/gallery/385.md create mode 100644 site/public/gallery/386.md create mode 100644 site/public/gallery/387.md create mode 100644 site/public/gallery/388.md create mode 100644 site/public/gallery/389.md create mode 100644 site/public/gallery/400.md create mode 100644 site/public/gallery/401.md create mode 100644 site/public/gallery/402.md create mode 100644 site/public/gallery/420.md create mode 100644 site/public/gallery/421.md create mode 100644 site/public/gallery/422.md create mode 100644 site/public/gallery/430.md create mode 100644 site/public/gallery/431.md create mode 100644 site/public/gallery/432.md create mode 100644 site/public/gallery/433.md create mode 100644 site/public/gallery/460.md create mode 100644 site/public/gallery/461.md create mode 100644 site/public/gallery/462.md create mode 100644 site/public/gallery/470.md create mode 100644 site/public/gallery/480.md create mode 100644 site/public/gallery/481.md create mode 100644 site/public/gallery/482.md create mode 100644 site/public/gallery/490.md create mode 100644 site/public/gallery/491.md create mode 100644 site/public/gallery/492.md create mode 100644 site/public/gallery/500.md create mode 100644 site/public/gallery/501.md create mode 100644 site/public/gallery/502.md create mode 100644 site/public/gallery/530.md create mode 100644 site/public/gallery/531.md create mode 100644 site/public/gallery/540.md create mode 100644 site/public/gallery/541.md create mode 100644 site/public/gallery/542.md create mode 100644 site/public/gallery/543.md create mode 100644 site/public/gallery/600.md create mode 100644 site/public/gallery/601.md create mode 100644 site/public/gallery/610.md create mode 100644 site/public/gallery/640.md create mode 100644 site/public/gallery/641.md create mode 100644 site/public/gallery/642.md create mode 100644 site/public/gallery/700.md create mode 100644 site/public/gallery/720.md create mode 100644 site/public/gallery/721.md create mode 100644 site/public/gallery/760.md create mode 100644 site/public/gallery/761.md create mode 100644 site/public/gallery/780.md create mode 100644 site/public/gallery/781.md create mode 100644 site/public/gallery/800.md create mode 100644 site/public/gallery/840.md create mode 100644 site/public/gallery/841.md create mode 100644 site/public/gallery/842.md create mode 100644 site/public/gallery/843.md create mode 100644 site/public/gallery/844.md create mode 100644 site/public/gallery/845.md create mode 100644 site/public/gallery/860.md create mode 100644 site/public/gallery/861.md create mode 100644 site/public/gallery/880.md create mode 100644 site/public/gallery/881.md create mode 100644 site/public/gallery/900.md create mode 100644 site/public/gallery/901.md create mode 100644 site/public/gallery/902.md create mode 100644 site/public/gallery/910.md create mode 100644 site/public/gallery/940.md create mode 100644 site/public/gallery/941.md create mode 100644 site/public/gallery/942.md create mode 100644 site/public/gallery/943.md create mode 100644 site/public/gallery/944.md create mode 100644 site/public/gallery/945.md create mode 100644 site/public/gallery/946.md create mode 100644 site/public/gallery/947.md create mode 100644 site/public/gallery/960.md create mode 100644 site/public/gallery/961.md create mode 100644 site/public/gallery/962.md create mode 100644 site/public/gallery/963.md create mode 100644 site/public/gallery/964.md create mode 100644 site/public/gallery/965.md create mode 100644 site/public/gallery/966.md create mode 100644 site/public/gallery/980.md create mode 100644 site/public/gallery/981.md create mode 100644 site/public/gallery/982.md create mode 100644 site/public/gallery/983.md create mode 100644 site/public/gallery/984.md create mode 100644 site/public/gallery/985.md create mode 100644 site/public/gallery/986.md create mode 100644 site/public/gallery/987.md diff --git a/AGENTS.md b/AGENTS.md index 0755c215..0d108d33 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -104,6 +104,18 @@ Reading failures: locators fail with an ARIA snapshot; traces are in `__traces__ --- +## The gallery index (demo facets) + +Every numbered demo (`site/src/demos///.svelte`, id in `site/src/demos/catalog.ts`) has a `Variant.meta.json` sidecar, and the sidecar carries an `index` block: the facets the gallery filters and searches on. `bun run --cwd packages/ui manifest -- --check` (and every build) fails when the block is missing or wrong, so a new demo cannot ship without it. + +- **Facets:** `kind` + `subtype`, `industry[]`, `style[]` (a `designStyle` id or one of the planned recipes), `motion` (`none` state-free, `subtle` state change, `expressive` entrance/scroll/loop, `immersive` physics/3D/shader/cursor-driven scene), `interaction[]`, `layout`, `colorMode`, `density`, `tech[]`, `cost`, `components[]`, `aliases[]` (lowercase search synonyms), `related[]` (catalog ids), `a11y { reducedMotion, keyboard }`, `added` (ISO date). The closed vocabularies live in `packages/ui/scripts/index-schema.ts`; an unknown value fails the check. +- **Inferred, never hand-typed:** `kind`, `subtype`, `components` (must equal the package imports of the demo), `tech`, `cost`, `added`. For a new demo run `bun packages/ui/scripts/index-backfill.ts` (it keeps every value already in a sidecar and fills these), then write `industry`, `style`, `motion`, `interaction`, `layout`, `colorMode`, `density`, `aliases`, `related` and `a11y.keyboard` by reading what the demo renders. Do not copy another demo's block. +- **Id ranges:** ids stay append-only; 3 digits to 999, 4 digits from 1000. `ID_RANGES` in `index-schema.ts` reserves a block per kind (components 001-299 and 400-999, motion 300-399 by subtype, sections 1000-2999 in blocks of 100 per subtype, backgrounds 3000-3199, techniques 3200-3399, 3D 3400-3499, shaders 3500-3599, pages 4000-4499, templates 4500-4999). The check fails when an id sits outside the block of its `kind`/`subtype`. Four legacy ids (`256`, `387-389`) are exempt (`LEGACY_OUT_OF_RANGE`); a new id never is. +- **Output:** the manifest build writes `site/public/api/v1/index.json` (every entry with its facets and per-facet counts) and one markdown twin per id at `site/public/gallery/.md`. Both are generated: never edit them; commit the regenerated files. +- **Originality:** write every title, summary, alias and prompt in our own words. Never name, link to or credit an external site or product in a demo, sidecar or generated file (`tests/demos-index.test.ts` scans the sidecars). + +--- + ## Critical rules 0. **Test first, always.** Every bug fix and issue starts with a regression test that reproduces the problem. Run it and confirm it fails on the current code, then fix, then confirm it passes. A PR reports that fail-before/pass-after evidence. Tests exist to prove a behavior or a fix, never to pad coverage: no tests of wiring, copies, mock echoes or incidental defaults. If a test cannot fail for a plausible bug, delete it. diff --git a/packages/ui/scripts/build-manifest.ts b/packages/ui/scripts/build-manifest.ts index 851461ef..09a92a06 100644 --- a/packages/ui/scripts/build-manifest.ts +++ b/packages/ui/scripts/build-manifest.ts @@ -27,6 +27,8 @@ import { loadMigrations, migrationMarkdown } from "../src/cli/migrations.ts"; import { STYLES, type SurfaceStyle } from "../src/lib/theme/define.ts"; import { DESIGN_STYLES } from "../src/lib/theme/styles.ts"; import { ENTRY_PAGES, parseRecipes, type Recipe } from "./ai-entry.ts"; +import { buildIndexFiles } from "./index-build.ts"; +import { checkIdRange, checkIndexFacets, ID_PATTERN, type IndexFacets } from "./index-schema.ts"; import { demoSources, mdxToMarkdown } from "./mdx-to-markdown.ts"; // CJS require keeps svelte2tsx out of Vite's transform pipeline (its sourceMappingURL points at a file it does not ship). @@ -1148,6 +1150,8 @@ export interface ManifestDemo { /** Exact timing of the motion, printed in e/ and p/ so an AI never guesses it. */ motion?: ManifestMotion[]; shape: "square" | "wide" | "tall"; + /** Index facets of the sidecar (`index.json`, `/gallery/.md`). */ + index: IndexFacets; components: string[]; imports: { from: string; names: string[] }[]; interactive: boolean; @@ -1197,7 +1201,7 @@ function demoUsages() { type DemoMeta = Pick< ManifestDemo, - "summary" | "use" | "spec" | "tags" | "shape" | "names" | "motion" + "summary" | "use" | "spec" | "tags" | "shape" | "names" | "motion" | "index" >; const META_KEYS: Record = { @@ -1208,6 +1212,7 @@ const META_KEYS: Record = { shape: true, names: true, motion: true, + index: true, }; const MOTION_REQUIRED = ["property", "duration", "easing"] as const; const MOTION_OPTIONAL = ["delay", "stagger", "spring"] as const; @@ -1217,7 +1222,11 @@ const isLines = (v: unknown): v is string[] => Array.isArray(v) && v.every((s) => typeof s === "string" && s.trim() !== ""); /** Problems of one sidecar against its demo source: shape of every field, and spec tokens quoted from the source. */ -export function checkMeta(meta: unknown, source: string): string[] { +export function checkMeta( + meta: unknown, + source: string, + ctx: { known?: ReadonlySet; ids?: ReadonlySet; self?: string } = {} +): string[] { if (!meta || typeof meta !== "object" || Array.isArray(meta)) return ["not an object"]; const m = meta as Record; const errors = Object.keys(m) @@ -1235,6 +1244,7 @@ export function checkMeta(meta: unknown, source: string): string[] { if (typeof m.shape !== "string" || !SHAPES[m.shape]) errors.push("shape: square | wide | tall"); if ("names" in m && (!isLines(m.names) || m.names.length < 1)) errors.push("names: non-empty strings, at least one"); + if ("index" in m) errors.push(...checkIndexFacets(m.index, ctx)); if ("motion" in m) { if (!Array.isArray(m.motion) || m.motion.length < 1) errors.push("motion: at least one entry when present"); @@ -1263,9 +1273,36 @@ export function checkMeta(meta: unknown, source: string): string[] { return errors; } -/** Problems that make the registry invalid: unregistered demos, missing files, duplicate ids/paths, missing or inaccurate sidecars. */ -export function checkCatalog(catalog: readonly { id: string; path: string }[]): string[] { +/** Library exports a demo imports from the package (the `components` facet is derived from these, never hand-typed). */ +export function importedNames(source: string): string[] { + return [ + ...new Set( + [ + ...source.matchAll(/import\s*\{([^}]+)\}\s*from\s*["']@codeatlasdev\/ui[^"']*["']/g), + ].flatMap(([, names]) => + names + .split(",") + .map( + (n) => + n + .trim() + .replace(/^type\s+/, "") + .split(/\s+as\s+/)[0] + ) + .filter(Boolean) + ) + ), + ]; +} + +/** Problems that make the registry invalid: unregistered demos, missing files, duplicate ids/paths, missing or inaccurate sidecars, missing or inconsistent index facets. */ +export function checkCatalog( + catalog: readonly { id: string; path: string }[], + components: readonly { name: string }[] +): string[] { const errors: string[] = []; + const ids = new Set(catalog.map((d) => d.id)); + const known = new Set(components.map((c) => c.name)); const walk = (dir: string, ext: string): string[] => readdirSync(dir, { withFileTypes: true }).flatMap((e) => e.isDirectory() @@ -1279,10 +1316,11 @@ export function checkCatalog(catalog: readonly { id: string; path: string }[]): if (!HELPERS.includes(p) && !paths.has(p)) errors.push(`demo without id: ${p}`); for (const p of walk(DEMOS_DIR, ".meta.json")) if (!paths.has(p)) errors.push(`sidecar without demo: ${p}.meta.json`); - const ids = new Set(); + const seen = new Set(); for (const d of catalog) { - if (ids.has(d.id)) errors.push(`duplicate id: ${d.id}`); - ids.add(d.id); + if (seen.has(d.id)) errors.push(`duplicate id: ${d.id}`); + seen.add(d.id); + if (!ID_PATTERN.test(d.id)) errors.push(`#${d.id} id: 3 digits up to 999, 4 digits from 1000`); if (!existsSync(join(DEMOS_DIR, `${d.path}.svelte`))) { errors.push(`#${d.id} missing file: ${d.path}`); continue; @@ -1300,7 +1338,21 @@ export function checkCatalog(catalog: readonly { id: string; path: string }[]): continue; } const source = readFileSync(join(DEMOS_DIR, `${d.path}.svelte`), "utf8"); - for (const e of checkMeta(meta, source)) errors.push(`#${d.id} ${d.path}.meta.json: ${e}`); + for (const e of checkMeta(meta, source, { known, ids, self: d.id })) + errors.push(`#${d.id} ${d.path}.meta.json: ${e}`); + const facets = (meta as { index?: IndexFacets }).index; + if (!facets) { + errors.push(`#${d.id} ${d.path}.meta.json: index: facets required (bun run index:backfill)`); + continue; + } + const range = checkIdRange(d.id, facets.kind, facets.subtype); + if (range) errors.push(`#${d.id} ${d.path}.meta.json: index: ${range}`); + const used = importedNames(source).filter((n) => known.has(n)); + const listed = [...facets.components].sort().join(); + if (listed !== [...used].sort().join()) + errors.push( + `#${d.id} ${d.path}.meta.json: index.components must be the imports of the demo: ${used.join(", ") || "none"}` + ); } if (paths.size !== catalog.length) errors.push("duplicate path in catalog"); return errors; @@ -1359,6 +1411,7 @@ export function buildDemos( ...(meta.names && { names: meta.names }), ...(meta.motion && { motion: meta.motion }), shape: meta.shape, + index: meta.index, components, imports, interactive: use?.interactive ?? false, @@ -1594,7 +1647,7 @@ if (import.meta.main) { ? (await import("../../../site/src/demos/catalog.ts")).CATALOG : []; // Every run (build included) refuses an invalid registry: a demo without an id must never ship. - const errors = catalog.length ? checkCatalog(catalog) : []; + const errors = catalog.length ? checkCatalog(catalog, manifest.components) : []; if (errors.length) { console.error(`demo catalog (site/src/demos/catalog.ts):\n ${errors.join("\n ")}`); process.exit(1); @@ -1634,6 +1687,12 @@ if (import.meta.main) { : { demos: [], files: {} }; if (demos.length) { write("demos.json", demos); + const indexFiles = buildIndexFiles(demos, manifest.version); + const siteRoot = resolve(ROOT, "../../site/public"); + for (const [name, body] of Object.entries(indexFiles)) { + mkdirSync(dirname(join(siteRoot, name)), { recursive: true }); + writeFileSync(join(siteRoot, name), body); + } for (const base of [join(dist, "api/v1"), resolve(ROOT, "../../site/public/api/v1")]) for (const [name, body] of Object.entries(files)) { mkdirSync(dirname(join(base, name)), { recursive: true }); diff --git a/packages/ui/scripts/index-backfill.ts b/packages/ui/scripts/index-backfill.ts new file mode 100644 index 00000000..9e3d9d40 --- /dev/null +++ b/packages/ui/scripts/index-backfill.ts @@ -0,0 +1,137 @@ +/** + * Writes the `index` facet block into every demo sidecar. + * + * bun packages/ui/scripts/index-backfill.ts [--infer] [--apply ] + * + * `--infer` derives what the code can prove: kind, subtype, components (the package imports), tech, cost, + * the reduced-motion flag and the added date (first commit that registered the id). It keeps any value a + * human already curated in the sidecar. `--apply` merges a hand-read file `{ "": { style, industry, + * motion, layout, colorMode, density, interaction, aliases, keyboard, related } }` over it. The result is + * validated by `checkCatalog` (`bun run --cwd packages/ui manifest -- --check`). + */ +import { execFileSync } from "node:child_process"; +import { readFileSync, writeFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { CATALOG } from "../../../site/src/demos/catalog.ts"; +import { buildManifest, importedNames } from "./build-manifest.ts"; +import { type IndexFacets, SUBTYPES } from "./index-schema.ts"; + +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../.."); +const DEMOS = join(ROOT, "site/src/demos"); + +const MOTION_SUBTYPE: Record = { + basics: "basics", + text: "text", + scroll: "scroll", + micro: "micro", + transitions: "transition", + "hero-ambient": "ambient", +}; +const COMPONENT_SUBTYPE: Record = { + inputs: "input", + display: "display", + feedback: "feedback", + navigation: "navigation", + surfaces: "surface", + layout: "layout", + charts: "chart", + foundation: "foundation", + map: "map", + "docs-kit": "display", +}; + +/** `kind` and `subtype` from the demo's folder. */ +export function inferKind(path: string): Pick { + const [group, page] = path.split("/"); + if (group === "motion") return { kind: "motion", subtype: MOTION_SUBTYPE[page] ?? "micro" }; + const subtype = COMPONENT_SUBTYPE[group]; + if (!subtype || !SUBTYPES.component.includes(subtype)) throw new Error(`no subtype for ${path}`); + return { kind: "component", subtype }; +} + +/** What the source proves about how the demo is built. */ +export function inferTech(source: string): Pick { + const tech = new Set(["css"]); + if ( + /\bspring\b|Spring\b|Tween\b|\banimate\(|requestAnimationFrame|IntersectionObserver|MotionStage/.test( + source + ) + ) + tech.add("svelte-motion"); + if (/ { + const log = execFileSync( + "git", + ["log", "--reverse", "--format=COMMIT %as", "-p", "-U0", "--", "site/src/demos/catalog.ts"], + { cwd: ROOT, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 } + ); + const out = new Map(); + let date = ""; + for (const line of log.split("\n")) { + if (line.startsWith("COMMIT ")) date = line.slice(7); + else { + const m = line.match(/^\+\s*\{ id: "(\d+)"/); + if (m && !out.has(m[1])) out.set(m[1], date); + } + } + return out; +} + +if (import.meta.main) { + const applyAt = process.argv.indexOf("--apply"); + const curated: Record & { keyboard?: boolean }> = + applyAt > 0 ? JSON.parse(readFileSync(process.argv[applyAt + 1], "utf8")) : {}; + const known = new Set(buildManifest().components.map((c) => c.name)); + const dates = addedDates(); + let written = 0; + for (const { id, path } of CATALOG) { + const file = join(DEMOS, `${path}.meta.json`); + const meta = JSON.parse(readFileSync(file, "utf8")); + const source = readFileSync(join(DEMOS, `${path}.svelte`), "utf8"); + const prior = (meta.index ?? {}) as Partial; + const hand = curated[id] ?? {}; + const { keyboard, ...handFacets } = hand; + const inferred = { + ...inferKind(path), + ...inferTech(source), + components: importedNames(source).filter((n) => known.has(n)), + added: dates.get(id) ?? new Date().toISOString().slice(0, 10), + }; + const reducedMotion = + /prefers-reduced-motion|\.reduced\b|MotionStage/.test(source) || + !inferred.tech.includes("svelte-motion"); + const index = { + kind: inferred.kind, + subtype: inferred.subtype, + industry: handFacets.industry ?? prior.industry, + style: handFacets.style ?? prior.style, + motion: handFacets.motion ?? prior.motion, + interaction: handFacets.interaction ?? prior.interaction, + layout: handFacets.layout ?? prior.layout, + colorMode: handFacets.colorMode ?? prior.colorMode, + density: handFacets.density ?? prior.density, + tech: inferred.tech, + cost: inferred.cost, + components: inferred.components, + aliases: handFacets.aliases ?? prior.aliases ?? [], + related: handFacets.related ?? prior.related ?? [], + a11y: { reducedMotion, keyboard: keyboard ?? prior.a11y?.keyboard ?? false }, + added: prior.added ?? inferred.added, + }; + const { index: _drop, ...rest } = meta; + writeFileSync(file, `${JSON.stringify({ ...rest, index }, null, "\t")}\n`); + written++; + } + // Same formatter as the rest of the repo, so the diff of a sidecar is only its `index` key. + execFileSync("bunx", ["biome", "format", "--write", DEMOS], { cwd: ROOT, stdio: "ignore" }); + console.log(`index facets written to ${written} sidecars`); +} diff --git a/packages/ui/scripts/index-build.ts b/packages/ui/scripts/index-build.ts new file mode 100644 index 00000000..84c65783 --- /dev/null +++ b/packages/ui/scripts/index-build.ts @@ -0,0 +1,156 @@ +/** + * The gallery index files, built from the demo records: + * api/v1/index.json every entry with its facets, plus per-facet counts (the file a search UI loads once) + * gallery/.md one markdown twin per id: facets, what it shows, spec, components, full source + * + * Pure: takes the demo records, returns `{ path: body }`. The caller decides where the files go. + */ +import type { IndexFacets } from "./index-schema.ts"; + +/** The fields of a demo record the index reads (a subset of `ManifestDemo`). */ +export interface IndexDemo { + id: string; + group: string; + page: string; + variant: string; + title: string; + summary: string; + use: string; + spec: string[]; + tags: string[]; + names?: string[]; + index: IndexFacets; + components: string[]; + needs: string; + demo: string; + source: string; +} + +export interface IndexEntry extends IndexFacets { + id: string; + path: string; + title: string; + summary: string; + use: string; + tags: string[]; + names: string[]; +} + +const SITE = "https://ui.codeatlas.com.br"; + +/** Facets that are filter chips, and how to read their values off an entry. */ +const COUNTED = { + kind: (e: IndexEntry) => [e.kind], + subtype: (e: IndexEntry) => [`${e.kind}/${e.subtype}`], + industry: (e: IndexEntry) => e.industry, + style: (e: IndexEntry) => e.style, + motion: (e: IndexEntry) => [e.motion], + interaction: (e: IndexEntry) => e.interaction, + layout: (e: IndexEntry) => [e.layout], + colorMode: (e: IndexEntry) => [e.colorMode], + density: (e: IndexEntry) => [e.density], + tech: (e: IndexEntry) => e.tech, + cost: (e: IndexEntry) => [e.cost], + component: (e: IndexEntry) => e.components, +} satisfies Record string[]>; + +export function indexEntries(demos: readonly IndexDemo[]): IndexEntry[] { + return demos.map((d) => ({ + ...d.index, + id: d.id, + path: `${d.group}/${d.page}/${d.variant}`, + title: d.title, + summary: d.summary, + use: d.use, + tags: d.tags, + names: d.names ?? [], + })); +} + +/** Count of entries per value of every filter facet; only values that occur are listed. */ +export function facetCounts( + entries: readonly IndexEntry[] +): Record> { + const out: Record> = {}; + for (const [facet, read] of Object.entries(COUNTED)) { + const counts: Record = {}; + for (const e of entries) for (const v of read(e)) counts[v] = (counts[v] ?? 0) + 1; + out[facet] = Object.fromEntries(Object.entries(counts).sort(([a], [b]) => a.localeCompare(b))); + } + return out; +} + +/** Markdown twin of one entry: what a person or an agent reads without loading the page. */ +export function entryMarkdown(d: IndexDemo): string { + const f = d.index; + const list = (v: string[]) => (v.length ? v.join(", ") : "none"); + return `${[ + ``, + `# #${d.id} ${d.title}`, + "", + d.summary, + "", + `When to use: ${d.use}`, + "", + "## Facets", + "", + `- Kind: ${f.kind} / ${f.subtype}`, + `- Industry: ${list(f.industry)}`, + `- Style: ${list(f.style)}`, + `- Motion: ${f.motion}`, + `- Interaction: ${list(f.interaction)}`, + `- Layout: ${f.layout}`, + `- Color mode: ${f.colorMode}`, + `- Density: ${f.density}`, + `- Tech: ${list(f.tech)} (cost: ${f.cost})`, + `- Reduced motion: ${f.a11y.reducedMotion ? "handled" : "not handled"}; keyboard: ${f.a11y.keyboard ? "operable" : "not interactive"}`, + `- Components: ${list(d.components)}`, + ...(f.aliases.length ? [`- Also searched as: ${f.aliases.join(", ")}`] : []), + "", + "## Spec", + "", + ...d.spec.map((s) => `- ${s}`), + "", + `Needs: ${d.needs}`, + `Live demo: ${d.demo}`, + `Reference: ${SITE}/api/v1/en/e/${d.id}.txt`, + "", + "## Source", + "", + "```svelte", + d.source.trimEnd(), + "```", + ].join("\n")}\n`; +} + +/** Header and facet counts indented for reading, one entry per line so the file stays small and diffs per demo. */ +function indexJson(entries: readonly IndexEntry[], version: string): string { + const head = JSON.stringify( + { + version, + count: entries.length, + urls: { + page: `${SITE}/gallery/{id}/`, + markdown: `${SITE}/gallery/{id}.md`, + reference: `${SITE}/api/v1/en/e/{id}.txt`, + }, + facets: facetCounts(entries), + }, + null, + "\t" + ); + return `${head.slice(0, -2)},\n\t"entries": [\n${entries.map((e) => `\t\t${JSON.stringify(e)}`).join(",\n")}\n\t]\n}\n`; +} + +/** `{ "api/v1/index.json": …, "gallery/.md": … }` for every demo. */ +export function buildIndexFiles( + demos: readonly IndexDemo[], + version: string +): Record { + const entries = indexEntries(demos); + const files: Record = { + "api/v1/index.json": indexJson(entries, version), + }; + for (const d of demos) files[`gallery/${d.id}.md`] = entryMarkdown(d); + return files; +} diff --git a/packages/ui/scripts/index-schema.ts b/packages/ui/scripts/index-schema.ts new file mode 100644 index 00000000..df933936 --- /dev/null +++ b/packages/ui/scripts/index-schema.ts @@ -0,0 +1,389 @@ +/** + * The gallery index: the facets every demo carries in its `Variant.meta.json` under the `index` key, + * the closed vocabulary they draw from, and the 4-digit id ranges reserved per kind. + * + * One module owns the schema: `checkMeta` (build-manifest.ts) validates with `checkIndexFacets`, + * `scripts/index-backfill.ts` writes with the same vocabulary, and `buildIndex` reads the result. + */ +import { DESIGN_STYLES } from "../src/lib/theme/styles.ts"; + +export const KINDS = [ + "component", + "section", + "page", + "motion", + "background", + "technique", + "3d", + "shader", + "style", + "template", +] as const; +export type Kind = (typeof KINDS)[number]; + +/** Allowed `subtype` values per kind (a closed list, so the facet chips stay stable and countable). */ +export const SUBTYPES: Record = { + component: [ + "input", + "display", + "feedback", + "navigation", + "surface", + "layout", + "chart", + "media", + "foundation", + "map", + ], + section: [ + "hero", + "logos", + "feature", + "bento", + "how-it-works", + "stats", + "testimonial", + "pricing", + "comparison", + "faq", + "cta", + "newsletter", + "team", + "careers", + "changelog", + "blog-list", + "contact", + "integrations", + "showcase", + "footer", + "navbar", + "banner", + "empty", + ], + page: [ + "landing", + "pricing", + "about", + "blog-post", + "blog-index", + "changelog", + "docs", + "auth", + "dashboard", + "settings", + "checkout", + "product", + "portfolio", + "waitlist", + "legal", + "error", + ], + motion: [ + "basics", + "text", + "scroll", + "micro", + "transition", + "ambient", + "gesture", + "cursor", + "loading", + "celebration", + ], + background: [ + "gradient", + "aurora", + "grid", + "noise", + "pattern", + "beams", + "particles", + "shader-field", + "ascii", + "image-treatment", + ], + technique: [ + "glass", + "spotlight", + "border-beam", + "mask-reveal", + "tilt", + "parallax", + "marquee", + "sticky-stack", + "magnetic", + "progressive-blur", + "duotone", + "kinetic-type", + ], + "3d": ["globe", "card-stack", "turntable", "carousel-wall", "terrain", "particle-object"], + shader: ["gradient-field", "distortion", "noise-flow", "dither", "ripple", "refraction"], + style: ["concept"], + template: ["multi-page"], +}; + +export const INDUSTRIES = [ + "saas", + "ai", + "devtools", + "fintech", + "e-commerce", + "health", + "education", + "agency", + "portfolio", + "media", + "travel", + "food", + "real-estate", + "nonprofit", + "gaming", + "crypto", + "general", +] as const; +/** `designStyle` ids plus the recipes the index adds ahead of their pages. */ +export const STYLES_VOCAB: readonly string[] = [ + ...Object.keys(DESIGN_STYLES), + "minimal", + "maximal", + "skeuo", + "aurora", + "luxe", + "pixel", + "memphis", + "flat", + "organic", +]; +export const MOTION_LEVELS = ["none", "subtle", "expressive", "immersive"] as const; +export const INTERACTIONS = [ + "hover", + "press", + "drag", + "scroll", + "cursor", + "keyboard", + "gyroscope", + "none", +] as const; +export const LAYOUTS = [ + "single-column", + "split", + "grid", + "bento", + "stack", + "carousel", + "full-bleed", + "sticky", + "overlay", +] as const; +export const COLOR_MODES = ["light", "dark", "both"] as const; +export const DENSITIES = ["compact", "regular", "airy"] as const; +export const TECHS = [ + "css", + "svelte-motion", + "canvas2d", + "svg-filter", + "webgl", + "view-transitions", +] as const; +export const COSTS = ["css", "js-light", "gpu"] as const; + +/** The facets of one demo, stored in its sidecar as `"index": {…}`. Title, summary, id and path come from elsewhere. */ +export interface IndexFacets { + kind: Kind; + subtype: string; + industry: string[]; + style: string[]; + /** How much the demo moves: none · subtle (state change) · expressive (entrance/scroll) · immersive (physics/3D/shader). */ + motion: (typeof MOTION_LEVELS)[number]; + interaction: string[]; + layout: (typeof LAYOUTS)[number]; + colorMode: (typeof COLOR_MODES)[number]; + density: (typeof DENSITIES)[number]; + tech: string[]; + cost: (typeof COSTS)[number]; + /** Library exports the demo uses (inferred from its imports, never hand-typed). */ + components: string[]; + /** Search synonyms ("disclosure" for an accordion). */ + aliases: string[]; + /** Ids of related demos. */ + related: string[]; + a11y: { reducedMotion: boolean; keyboard: boolean }; + /** ISO date the demo entered the catalog. */ + added: string; +} + +const FACET_KEYS = [ + "kind", + "subtype", + "industry", + "style", + "motion", + "interaction", + "layout", + "colorMode", + "density", + "tech", + "cost", + "components", + "aliases", + "related", + "a11y", + "added", +] as const; + +const isStrings = (v: unknown): v is string[] => + Array.isArray(v) && v.every((s) => typeof s === "string" && s.trim() !== ""); +const oneOf = (v: unknown, list: readonly string[]) => typeof v === "string" && list.includes(v); + +/** + * Problems of one `index` facet block. `known` is the set of real component names (components must be + * real exports) and `ids` the set of catalog ids (related must point at one); both are skipped when omitted. + */ +export function checkIndexFacets( + facets: unknown, + ctx: { known?: ReadonlySet; ids?: ReadonlySet; self?: string } = {} +): string[] { + if (!facets || typeof facets !== "object" || Array.isArray(facets)) + return ["index: object required"]; + const f = facets as Record; + const errors: string[] = Object.keys(f) + .filter((k) => !(FACET_KEYS as readonly string[]).includes(k)) + .map((k) => `index: unknown key "${k}"`); + const multi = (key: string, list: readonly string[], min: number) => { + const v = f[key]; + if (!isStrings(v) || v.length < min) + errors.push(`index.${key}: at least ${min} of ${list.join(" | ")}`); + else + for (const x of v) + if (!list.includes(x)) + errors.push(`index.${key}: "${x}" is not one of ${list.join(" | ")}`); + if (isStrings(v) && new Set(v).size !== v.length) errors.push(`index.${key}: duplicate value`); + }; + if (!oneOf(f.kind, KINDS)) errors.push(`index.kind: ${KINDS.join(" | ")}`); + else if (!oneOf(f.subtype, SUBTYPES[f.kind as Kind])) + errors.push( + `index.subtype: for kind "${f.kind}" one of ${SUBTYPES[f.kind as Kind].join(" | ")}` + ); + multi("industry", INDUSTRIES, 1); + multi("style", STYLES_VOCAB, 1); + if (!oneOf(f.motion, MOTION_LEVELS)) errors.push(`index.motion: ${MOTION_LEVELS.join(" | ")}`); + multi("interaction", INTERACTIONS, 1); + if (!oneOf(f.layout, LAYOUTS)) errors.push(`index.layout: ${LAYOUTS.join(" | ")}`); + if (!oneOf(f.colorMode, COLOR_MODES)) errors.push(`index.colorMode: ${COLOR_MODES.join(" | ")}`); + if (!oneOf(f.density, DENSITIES)) errors.push(`index.density: ${DENSITIES.join(" | ")}`); + multi("tech", TECHS, 1); + if (!oneOf(f.cost, COSTS)) errors.push(`index.cost: ${COSTS.join(" | ")}`); + if (!isStrings(f.components)) errors.push("index.components: array of component names"); + else if (ctx.known) + for (const c of f.components) + if (!ctx.known.has(c)) errors.push(`index.components: "${c}" is not a library component`); + if (!isStrings(f.aliases) || f.aliases.some((a) => a !== a.toLowerCase())) + errors.push("index.aliases: array of lowercase strings"); + if (!isStrings(f.related)) errors.push("index.related: array of catalog ids"); + else if (ctx.ids) + for (const r of f.related) { + if (!ctx.ids.has(r)) errors.push(`index.related: "${r}" is not a catalog id`); + if (r === ctx.self) errors.push("index.related: a demo cannot relate to itself"); + } + const a = f.a11y as Record | undefined; + if ( + !a || + typeof a !== "object" || + typeof a.reducedMotion !== "boolean" || + typeof a.keyboard !== "boolean" + ) + errors.push("index.a11y: { reducedMotion: boolean, keyboard: boolean }"); + if ( + typeof f.added !== "string" || + !/^\d{4}-\d{2}-\d{2}$/.test(f.added) || + Number.isNaN(Date.parse(f.added)) + ) + errors.push("index.added: ISO date YYYY-MM-DD"); + // Consistency a reader would otherwise trip over. + if (f.motion === "none" && f.cost === "gpu") + errors.push('index: motion "none" cannot cost "gpu"'); + if (isStrings(f.tech) && f.tech.includes("webgl") && f.cost !== "gpu") + errors.push('index: tech "webgl" costs "gpu"'); + return errors; +} + +/** + * Reserved id ranges per kind. Ids stay append-only and 3 digits widen to 4 only for new kinds; an + * existing id keeps its value and its range is the one of its group below. + * + * Legacy (3 digits): every component demo keeps the id it has, `001-299` and `900-999` (components); + * `300-399` is motion (text 300-319, scroll 320-339, micro 340-359, transitions 360-379, hero/ambient 380-399). + * New (4 digits): see `ID_RANGES`. + */ +export interface IdRange { + kind: Kind; + /** Subtype this block is for, or `*` for the whole kind. */ + subtype: string; + from: number; + to: number; +} + +export const ID_RANGES: readonly IdRange[] = [ + { kind: "component", subtype: "*", from: 1, to: 299 }, + { kind: "motion", subtype: "text", from: 300, to: 319 }, + { kind: "motion", subtype: "scroll", from: 320, to: 339 }, + { kind: "motion", subtype: "micro", from: 340, to: 359 }, + { kind: "motion", subtype: "transition", from: 360, to: 379 }, + { kind: "motion", subtype: "ambient", from: 380, to: 399 }, + { kind: "component", subtype: "*", from: 400, to: 999 }, + // Sections: blocks of 100 per subtype. + { kind: "section", subtype: "hero", from: 1000, to: 1099 }, + { kind: "section", subtype: "feature", from: 1100, to: 1199 }, + { kind: "section", subtype: "pricing", from: 1200, to: 1299 }, + { kind: "section", subtype: "cta", from: 1300, to: 1399 }, + { kind: "section", subtype: "testimonial", from: 1400, to: 1499 }, + { kind: "section", subtype: "footer", from: 1500, to: 1599 }, + { kind: "section", subtype: "faq", from: 1600, to: 1699 }, + { kind: "section", subtype: "navbar", from: 1700, to: 1799 }, + { kind: "section", subtype: "logos", from: 1800, to: 1899 }, + { kind: "section", subtype: "*", from: 1900, to: 2999 }, + { kind: "background", subtype: "*", from: 3000, to: 3199 }, + { kind: "technique", subtype: "*", from: 3200, to: 3399 }, + { kind: "3d", subtype: "*", from: 3400, to: 3499 }, + { kind: "shader", subtype: "*", from: 3500, to: 3599 }, + { kind: "page", subtype: "*", from: 4000, to: 4499 }, + { kind: "template", subtype: "*", from: 4500, to: 4999 }, +]; + +/** Ids are 3 digits up to 999 and 4 digits from 1000; `"001"` and `"1000"` are both valid, `"01"` and `"10000"` are not. */ +export const ID_PATTERN = /^(\d{3}|[1-9]\d{3})$/; + +/** The reserved block an id falls in, or undefined for an id outside every range. */ +export function rangeOf(id: string): IdRange | undefined { + const n = Number(id); + return ( + ID_RANGES.find((r) => n >= r.from && n <= r.to && r.subtype !== "*") ?? + ID_RANGES.find((r) => n >= r.from && n <= r.to) + ); +} + +/** + * Ids assigned before the ranges existed that sit outside the block of their kind. They keep their value + * (ids are never renumbered) and are the only exemptions: a new id must live in its kind's range. + */ +export const LEGACY_OUT_OF_RANGE: Readonly> = { + "256": "motion", // motion/basics/FadeRise + "387": "component", // inputs/checkbox/RangeSelect + "388": "component", // inputs/file-upload/ExternalTrigger + "389": "component", // inputs/text-field/HiddenLabel +}; + +/** Whether an id may carry these facets: its range must match the kind, and the subtype when the block names one. */ +export function checkIdRange(id: string, kind: string, subtype: string): string | undefined { + if (LEGACY_OUT_OF_RANGE[id]) + return LEGACY_OUT_OF_RANGE[id] === kind + ? undefined + : `legacy id ${id} is a "${LEGACY_OUT_OF_RANGE[id]}"`; + const r = rangeOf(id); + if (!r) return `id ${id} is outside every reserved range`; + if (r.kind !== kind) return `id ${id} is reserved for kind "${r.kind}", not "${kind}"`; + if (r.subtype !== "*" && r.subtype !== subtype) + return `id ${id} is reserved for subtype "${r.subtype}", not "${subtype}"`; + return undefined; +} diff --git a/packages/ui/tests/demos-catalog.test.ts b/packages/ui/tests/demos-catalog.test.ts index 35b68b6b..595b6c0c 100644 --- a/packages/ui/tests/demos-catalog.test.ts +++ b/packages/ui/tests/demos-catalog.test.ts @@ -9,6 +9,7 @@ import { checkMeta, HELPERS, } from "../scripts/build-manifest.ts"; +import { ID_PATTERN } from "../scripts/index-schema.ts"; const DEMOS = resolve(import.meta.dir, "../../../site/src/demos"); @@ -27,9 +28,9 @@ const manifest = buildManifest(); const { demos, files } = buildDemos(manifest, CATALOG); describe("demo catalog", () => { - it("ids are unique, 3-digit and strictly increasing", () => { + it("ids are unique, 3 or 4 digits and strictly increasing", () => { const ids = CATALOG.map((d) => d.id); - for (const id of ids) expect(id).toMatch(/^\d{3}$/); + for (const id of ids) expect(id).toMatch(ID_PATTERN); for (let i = 1; i < ids.length; i++) expect(Number(ids[i])).toBeGreaterThan(Number(ids[i - 1])); }); @@ -70,7 +71,7 @@ describe("demo catalog", () => { describe("demo meta sidecars", () => { it("every registered demo has an accurate sidecar and none is orphaned", () => { - expect(checkCatalog(CATALOG)).toEqual([]); + expect(checkCatalog(CATALOG, manifest.components)).toEqual([]); }); const source = ''; diff --git a/packages/ui/tests/demos-index.test.ts b/packages/ui/tests/demos-index.test.ts new file mode 100644 index 00000000..910d2122 --- /dev/null +++ b/packages/ui/tests/demos-index.test.ts @@ -0,0 +1,254 @@ +import { describe, expect, it } from "bun:test"; +import { readFileSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { CATALOG } from "../../../site/src/demos/catalog.ts"; +import { buildDemos, buildManifest, checkMeta, importedNames } from "../scripts/build-manifest.ts"; +import { buildIndexFiles, facetCounts, indexEntries } from "../scripts/index-build.ts"; +import { + checkIdRange, + checkIndexFacets, + ID_PATTERN, + ID_RANGES, + LEGACY_OUT_OF_RANGE, + rangeOf, + SUBTYPES, +} from "../scripts/index-schema.ts"; + +const DEMOS = resolve(import.meta.dir, "../../../site/src/demos"); +const manifest = buildManifest(); +const { demos } = buildDemos(manifest, CATALOG); + +const ok = { + kind: "section", + subtype: "pricing", + industry: ["saas"], + style: ["default", "bento"], + motion: "expressive", + interaction: ["hover", "scroll"], + layout: "grid", + colorMode: "both", + density: "regular", + tech: ["css", "svelte-motion"], + cost: "js-light", + components: ["Button"], + aliases: ["plans"], + related: ["001"], + a11y: { reducedMotion: true, keyboard: true }, + added: "2026-10-25", +}; +const ctx = { known: new Set(["Button"]), ids: new Set(["001", "002"]), self: "002" }; + +describe("index facet schema", () => { + it("accepts a complete block", () => { + expect(checkIndexFacets(ok, ctx)).toEqual([]); + }); + + it("rejects a subtype that does not belong to the kind", () => { + expect(checkIndexFacets({ ...ok, subtype: "input" }, ctx)[0]).toContain('for kind "section"'); + expect(checkIndexFacets({ ...ok, kind: "nope" }, ctx)[0]).toContain("index.kind"); + }); + + it("rejects values outside the closed vocabulary, empty and duplicate lists", () => { + expect(checkIndexFacets({ ...ok, industry: ["banking"] }, ctx)).toEqual([ + expect.stringContaining('"banking" is not one of'), + ]); + expect(checkIndexFacets({ ...ok, style: [] }, ctx)[0]).toContain("index.style: at least 1"); + expect(checkIndexFacets({ ...ok, tech: ["css", "css"] }, ctx)).toContain( + "index.tech: duplicate value" + ); + expect(checkIndexFacets({ ...ok, layout: "masonry" }, ctx)[0]).toContain("index.layout"); + expect(checkIndexFacets({ ...ok, colorMode: "auto" }, ctx)[0]).toContain("index.colorMode"); + }); + + it("rejects unknown components, dangling or self related ids and uppercase aliases", () => { + expect(checkIndexFacets({ ...ok, components: ["Nope"] }, ctx)).toEqual([ + 'index.components: "Nope" is not a library component', + ]); + expect(checkIndexFacets({ ...ok, related: ["999"] }, ctx)).toEqual([ + 'index.related: "999" is not a catalog id', + ]); + expect(checkIndexFacets({ ...ok, related: ["002"] }, ctx)).toEqual([ + "index.related: a demo cannot relate to itself", + ]); + expect(checkIndexFacets({ ...ok, aliases: ["Plans"] }, ctx)[0]).toContain("index.aliases"); + }); + + it("rejects a bad date, a missing a11y flag and contradictory motion/cost/tech", () => { + expect(checkIndexFacets({ ...ok, added: "25/10/2026" }, ctx)[0]).toContain("index.added"); + expect(checkIndexFacets({ ...ok, added: "2026-13-45" }, ctx)[0]).toContain("index.added"); + expect(checkIndexFacets({ ...ok, a11y: { reducedMotion: true } }, ctx)[0]).toContain( + "index.a11y" + ); + expect(checkIndexFacets({ ...ok, motion: "none", cost: "gpu" }, ctx)).toContain( + 'index: motion "none" cannot cost "gpu"' + ); + expect(checkIndexFacets({ ...ok, tech: ["webgl"], cost: "js-light" }, ctx)).toContain( + 'index: tech "webgl" costs "gpu"' + ); + }); + + it("rejects unknown keys and non-objects", () => { + expect(checkIndexFacets({ ...ok, mood: "calm" }, ctx)).toEqual(['index: unknown key "mood"']); + expect(checkIndexFacets([], ctx)).toEqual(["index: object required"]); + }); + + it("checkMeta validates the block when a sidecar carries one", () => { + const meta = { + summary: "A pricing card with a button.", + use: "Plan pickers on a pricing page.", + spec: ["`Button`"], + tags: ["pricing", "button"], + shape: "wide", + }; + expect(checkMeta({ ...meta, index: ok }, " + + + + +``` diff --git a/site/public/gallery/002.md b/site/public/gallery/002.md new file mode 100644 index 00000000..544d0b60 --- /dev/null +++ b/site/public/gallery/002.md @@ -0,0 +1,53 @@ + +# #002 With icons + +Buttons with an icon before the label, after the label, and with a ghost download action. + +When to use: Actions that benefit from an icon cue: favourite, continue to next step, download. + +## Facets + +- Kind: component / input +- Industry: general +- Style: default +- Motion: subtle +- Interaction: hover, press, keyboard +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: Button +- Also searched as: icon button, button with icon + +## Spec + +- Leading icon goes in `{#snippet leading()}` (`Star` from `@lucide/svelte`, `size={15}`) +- Trailing icon goes in `{#snippet trailing()}` (`ArrowRight`, `size={15}`) +- Variants shown: `variant="filled"`, `variant="outlined"`, `variant="ghost"` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/button#with-icons +Reference: https://ui.codeatlas.com.br/api/v1/en/e/002.txt + +## Source + +```svelte + + + + + +``` diff --git a/site/public/gallery/003.md b/site/public/gallery/003.md new file mode 100644 index 00000000..6708b3ef --- /dev/null +++ b/site/public/gallery/003.md @@ -0,0 +1,53 @@ + +# #003 Loading + +Buttons in the loading state, including one that toggles loading for two seconds when clicked. + +When to use: Submit and save buttons that wait on a request and must block repeat clicks while keeping their label. + +## Facets + +- Kind: component / input +- Industry: general +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: Button +- Also searched as: spinner, pending, busy + +## Spec + +- `loading` on `variant="filled"` and `variant="outlined"` shows the spinner in place of the leading icon +- Bound to state: `loading={saving}` with `let saving = $state(false)` +- `onclick={save}` sets `saving = true` and resets it with `setTimeout(() => (saving = false), 2000)` +- Label switches with state: `{saving ? 'Saving…' : 'Save (2s)'}` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/button#loading +Reference: https://ui.codeatlas.com.br/api/v1/en/e/003.txt + +## Source + +```svelte + + + + + +``` diff --git a/site/public/gallery/004.md b/site/public/gallery/004.md new file mode 100644 index 00000000..9ef5bd19 --- /dev/null +++ b/site/public/gallery/004.md @@ -0,0 +1,43 @@ + +# #004 Sizes + +The three button sizes side by side: small, medium and large. + +When to use: Choosing button density: `sm` in toolbars and tables, `md` by default, `lg` for hero or touch-first actions. + +## Facets + +- Kind: component / input +- Industry: general +- Style: default +- Motion: subtle +- Interaction: hover, press, keyboard +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: Button +- Also searched as: small medium large + +## Spec + +- `size="sm"` labelled Small +- `size="md"` labelled Medium +- `size="lg"` labelled Large + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/button#sizes +Reference: https://ui.codeatlas.com.br/api/v1/en/e/004.txt + +## Source + +```svelte + + + + + +``` diff --git a/site/public/gallery/005.md b/site/public/gallery/005.md new file mode 100644 index 00000000..19af2509 --- /dev/null +++ b/site/public/gallery/005.md @@ -0,0 +1,49 @@ + +# #005 States + +Disabled buttons, a destructive action with an icon, and a link rendered as a button. + +When to use: Showing non-interactive and dangerous actions, or navigating with button styling via `href`. + +## Facets + +- Kind: component / input +- Industry: general +- Style: default +- Motion: subtle +- Interaction: hover, press, keyboard +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: Button +- Also searched as: inactive, danger, link + +## Spec + +- `disabled` on `variant="filled"`, `variant="outlined"` and `variant="glass"` +- `variant="destructive"` with a `Trash2` icon in `{#snippet leading()}` (`size={15}`) +- `href="https://github.com/codeatlasdev/ui"` with `target="_blank"` renders an anchor + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/button#states +Reference: https://ui.codeatlas.com.br/api/v1/en/e/005.txt + +## Source + +```svelte + + + + + + + +``` diff --git a/site/public/gallery/006.md b/site/public/gallery/006.md new file mode 100644 index 00000000..2459c312 --- /dev/null +++ b/site/public/gallery/006.md @@ -0,0 +1,47 @@ + +# #006 Variants + +All five button variants in a row: filled, glass, outlined, ghost and destructive. + +When to use: Picking the emphasis of an action: filled for primary, outlined or ghost for secondary, destructive for irreversible. + +## Facets + +- Kind: component / input +- Industry: general +- Style: default, glassmorphism +- Motion: subtle +- Interaction: hover, press, keyboard +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: Button +- Also searched as: button styles, primary secondary + +## Spec + +- `variant="filled"` labelled Filled +- `variant="glass"` labelled Glass +- `variant="outlined"` labelled Outlined +- `variant="ghost"` labelled Ghost +- `variant="destructive"` labelled Destructive + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/button#variants +Reference: https://ui.codeatlas.com.br/api/v1/en/e/006.txt + +## Source + +```svelte + + + + + + + +``` diff --git a/site/public/gallery/007.md b/site/public/gallery/007.md new file mode 100644 index 00000000..a0941531 --- /dev/null +++ b/site/public/gallery/007.md @@ -0,0 +1,44 @@ + +# #007 Disabled + +Three disabled checkboxes: unchecked, checked and indeterminate. + +When to use: Showing a read-only option whose value the user cannot change, such as a setting locked by plan or permission. + +## Facets + +- Kind: component / input +- Industry: general +- Style: default +- Motion: none +- Interaction: none +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: not interactive +- Components: Checkbox +- Also searched as: tick box + +## Spec + +- `disabled` with `label="Disabled unchecked"` +- `checked disabled` with `label="Disabled checked"` +- `indeterminate disabled` with `label="Disabled indeterminate"` +- Three ` + import { Checkbox } from '@codeatlasdev/ui'; + + + + + +``` diff --git a/site/public/gallery/008.md b/site/public/gallery/008.md new file mode 100644 index 00000000..d845803a --- /dev/null +++ b/site/public/gallery/008.md @@ -0,0 +1,47 @@ + +# #008 States + +Checkboxes in unchecked, pre-checked and indeterminate states, the first two bound to local state. + +When to use: Boolean options in a form, such as terms acceptance or feature toggles, including a mixed state for partial selection. + +## Facets + +- Kind: component / input +- Industry: general +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: Checkbox +- Also searched as: tick box, tri-state + +## Spec + +- `bind:checked={basic}` with `let basic = $state(false)` and `label="Unchecked (click me)"` +- `bind:checked` bound to `let checked = $state(true)` with `label="Pre-checked"` +- `indeterminate` with `label="Indeterminate"` +- Three ` + import { Checkbox } from '@codeatlasdev/ui'; + + let basic = $state(false); + let checked = $state(true); + + + + + +``` diff --git a/site/public/gallery/009.md b/site/public/gallery/009.md new file mode 100644 index 00000000..9ff06816 --- /dev/null +++ b/site/public/gallery/009.md @@ -0,0 +1,46 @@ + +# #009 Without label + +Three checkboxes with no visible label, named only through aria-label: bound, checked and indeterminate. + +When to use: Row selection checkboxes in tables or lists where the label is implied by the row and only an accessible name is needed. + +## Facets + +- Kind: component / input +- Industry: general +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: Checkbox +- Also searched as: unlabelled, aria label only + +## Spec + +- `bind:checked={value}` with `aria-label="Unlabelled checkbox"` +- `checked` with `aria-label="Unlabelled checked checkbox"` +- `indeterminate` with `aria-label="Unlabelled indeterminate checkbox"` +- No `label` prop on any of the three + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/checkbox#without-label +Reference: https://ui.codeatlas.com.br/api/v1/en/e/009.txt + +## Source + +```svelte + + + + + +``` diff --git a/site/public/gallery/010.md b/site/public/gallery/010.md new file mode 100644 index 00000000..f30d1306 --- /dev/null +++ b/site/public/gallery/010.md @@ -0,0 +1,74 @@ + +# #010 Basic + +A searchable Combobox over eight frameworks that prints the selected value below the field. + +When to use: Picking one item from a long list where typing to filter is faster than scrolling a select. + +## Facets + +- Kind: component / input +- Industry: devtools +- Style: default +- Motion: subtle +- Interaction: keyboard, press +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: Combobox +- Also searched as: autocomplete, typeahead, searchable select + +## Spec + +- `` +- `const frameworks` has 8 options with `value` and `label` keys, from `svelte` to `sveltekit` +- `let selected = $state('')` starts empty +- `{#if selected}` renders `Selected:` with the value in `` +- Wrapper `.col` has `max-width: 320px` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/combobox#basic +Reference: https://ui.codeatlas.com.br/api/v1/en/e/010.txt + +## Source + +```svelte + + +
+ + {#if selected} +

Selected: {selected}

+ {/if} +
+ + +``` diff --git a/site/public/gallery/011.md b/site/public/gallery/011.md new file mode 100644 index 00000000..45219b2c --- /dev/null +++ b/site/public/gallery/011.md @@ -0,0 +1,73 @@ + +# #011 States + +Three Combobox states: with description and custom search and empty texts, with error and required, and disabled. + +When to use: Documenting validation, helper text and unavailable states of a searchable select in a form. + +## Facets + +- Kind: component / input +- Industry: devtools +- Style: default +- Motion: subtle +- Interaction: keyboard, press +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: Combobox +- Also searched as: validation, empty results + +## Spec + +- `frameworks` has 4 options, the last is `{ value: 'astro', label: 'Astro', disabled: true }` +- First field: `bind:value`, `description="Astro is disabled."`, `searchPlaceholder="Filter frameworks…"`, `emptyText="No framework matches."` +- Second field: `error="Pick a framework."` and `required` +- Third field: `disabled` with `placeholder="Unavailable"` +- Wrapper `.col` has `max-width: 320px` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/combobox#states +Reference: https://ui.codeatlas.com.br/api/v1/en/e/011.txt + +## Source + +```svelte + + +
+ + + +
+ + +``` diff --git a/site/public/gallery/012.md b/site/public/gallery/012.md new file mode 100644 index 00000000..024ab0c8 --- /dev/null +++ b/site/public/gallery/012.md @@ -0,0 +1,40 @@ + +# #012 Default + +A default CopyButton that copies an npm install command. + +When to use: Next to install commands, tokens or IDs so users can copy them in one click. + +## Facets + +- Kind: component / input +- Industry: devtools +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: CopyButton +- Also searched as: clipboard, copy to clipboard + +## Spec + +- `` +- Only the `value` prop is set, no other props + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/copy-button#default +Reference: https://ui.codeatlas.com.br/api/v1/en/e/012.txt + +## Source + +```svelte + + + +``` diff --git a/site/public/gallery/013.md b/site/public/gallery/013.md new file mode 100644 index 00000000..24023e94 --- /dev/null +++ b/site/public/gallery/013.md @@ -0,0 +1,43 @@ + +# #013 Icon only and toast + +An icon-only CopyButton and a labelled one that shows a toast after copying, with a Toaster mounted. + +When to use: Copying links or snippets where a compact icon is enough or where a visible confirmation toast is wanted. + +## Facets + +- Kind: component / input +- Industry: devtools +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: CopyButton, Toaster +- Also searched as: share link, copied toast + +## Spec + +- `` is rendered alongside the buttons +- `` is icon-only via an empty `label` +- `` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/copy-button#icon-only-and-toast +Reference: https://ui.codeatlas.com.br/api/v1/en/e/013.txt + +## Source + +```svelte + + + + + +``` diff --git a/site/public/gallery/014.md b/site/public/gallery/014.md new file mode 100644 index 00000000..13acc990 --- /dev/null +++ b/site/public/gallery/014.md @@ -0,0 +1,47 @@ + +# #014 Variants and sizes + +CopyButton in three variants and two extra sizes, side by side. + +When to use: Choosing the emphasis and size of a copy action to match the surrounding UI. + +## Facets + +- Kind: component / input +- Industry: devtools +- Style: default, glassmorphism +- Motion: subtle +- Interaction: hover, press, keyboard +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: CopyButton +- Also searched as: clipboard + +## Spec + +- `variant="filled"` with `value="filled"` +- `variant="glass"` with `value="glass"` +- `variant="outlined"` with `value="outlined"` +- `size="sm"` with `value="small"` +- `size="lg"` with `value="large"` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/copy-button#variants-and-sizes +Reference: https://ui.codeatlas.com.br/api/v1/en/e/014.txt + +## Source + +```svelte + + + + + + + +``` diff --git a/site/public/gallery/015.md b/site/public/gallery/015.md new file mode 100644 index 00000000..e26c8c94 --- /dev/null +++ b/site/public/gallery/015.md @@ -0,0 +1,80 @@ + +# #015 Calendar and RangeCalendar + +An inline single-date Calendar and a RangeCalendar, each printing its selected value below. + +When to use: Always-visible date selection such as booking a stay or choosing an event day without a popover. + +## Facets + +- Kind: component / input +- Industry: travel +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: grid +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: Calendar, RangeCalendar +- Also searched as: month grid, date range, inline calendar + +## Spec + +- `` +- `let value = $state(today(getLocalTimeZone()))` preselects today +- `` +- `let range = $state({ start: undefined, end: undefined })` starts empty +- Output lines show `fmt(value)`, `fmt(range.start)` and `fmt(range.end)`, with `–` when unset + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/date-picker#calendar-and-rangecalendar +Reference: https://ui.codeatlas.com.br/api/v1/en/e/015.txt + +## Source + +```svelte + + +
+ +

value: {fmt(value)}

+
+ +
+ +

start: {fmt(range.start)} · end: {fmt(range.end)}

+
+ + +``` diff --git a/site/public/gallery/016.md b/site/public/gallery/016.md new file mode 100644 index 00000000..27d9e2f9 --- /dev/null +++ b/site/public/gallery/016.md @@ -0,0 +1,75 @@ + +# #016 Min, max, error and disabled + +Date and time inputs with constraints: a bounded DatePicker, a required DatePicker with an error, a disabled DateField and a read-only TimeField. + +When to use: Forms that limit selectable dates, require a date of birth or show locked date and time values. + +## Facets + +- Kind: component / input +- Industry: saas +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: DateField, DatePicker, TimeField +- Also searched as: min max date, bounded, readonly + +## Spec + +- `minValue={min}` and `maxValue={max}` with `const max = today(tz).add({ days: 30 })` and `description="Only the next 30 days."` +- `required` with `error={requiredError}` derived as `'Enter your date of birth.'` while empty +- `` +- `` +- Wrapper `.col` has `max-width: 400px` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/date-picker#min-max-error-and-disabled +Reference: https://ui.codeatlas.com.br/api/v1/en/e/016.txt + +## Source + +```svelte + + +
+ + + + +
+ + +``` diff --git a/site/public/gallery/017.md b/site/public/gallery/017.md new file mode 100644 index 00000000..0bbfbb82 --- /dev/null +++ b/site/public/gallery/017.md @@ -0,0 +1,69 @@ + +# #017 DateField and TimeField + +Segmented DateField and TimeField inputs with bound values, minute granularity and 24h and 12h hour cycles. + +When to use: Typing a date or time directly in a form, such as a birthday or an appointment start time. + +## Facets + +- Kind: component / input +- Industry: general +- Style: default +- Motion: subtle +- Interaction: keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: DateField, TimeField +- Also searched as: segmented date, time input, 12 hour 24 hour + +## Spec + +- `` initialised with `new CalendarDate(2026, 10, 10)` +- `` +- `` initialised with `new Time(14, 30)` +- `` +- Two `value:` output lines print `birthday.toString()` and `time.toString()` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/date-picker#datefield-and-timefield +Reference: https://ui.codeatlas.com.br/api/v1/en/e/017.txt + +## Source + +```svelte + + +
+ + +

value: {birthday ? birthday.toString() : '–'}

+ + +

value: {time ? time.toString() : '–'}

+
+ + +``` diff --git a/site/public/gallery/018.md b/site/public/gallery/018.md new file mode 100644 index 00000000..a2b0fafa --- /dev/null +++ b/site/public/gallery/018.md @@ -0,0 +1,82 @@ + +# #018 DatePicker and DateRangePicker + +A DatePicker and a DateRangePicker with popover calendars, each printing its selected value. + +When to use: Choosing a delivery date or a reporting period where the user can type or pick from a calendar popover. + +## Facets + +- Kind: component / input +- Industry: travel +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: DatePicker, DateRangePicker +- Also searched as: calendar popover, date range, datepicker + +## Spec + +- `()` +- `` +- `let range = $state({ start: undefined, end: undefined })` +- Wrappers: `.col` has `max-width: 400px`, `.col--wide` has `max-width: 460px` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/date-picker#datepicker-and-daterangepicker +Reference: https://ui.codeatlas.com.br/api/v1/en/e/018.txt + +## Source + +```svelte + + +
+ +

value: {fmt(picked)}

+
+ +
+ +

start: {fmt(range.start)} · end: {fmt(range.end)}

+
+ + +``` diff --git a/site/public/gallery/019.md b/site/public/gallery/019.md new file mode 100644 index 00000000..e1947e49 --- /dev/null +++ b/site/public/gallery/019.md @@ -0,0 +1,63 @@ + +# #019 Default + +An ExportMenu exporting three domain rows with custom column headers and a status line reporting the last export. + +When to use: Table toolbars that let users download the current data set as a file in a chosen format. + +## Facets + +- Kind: component / input +- Industry: saas +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: ExportMenu +- Also searched as: download, csv, xlsx, dropdown + +## Spec + +- `data={rows}` with `const rows` of 3 objects with `domain`, `price` and `available` fields +- `filename="domains"` +- `columns` has 3 entries with `header` `Domain`, `Price (USD)` and `Available` +- `onexport` sets `last` to the exported `e.filename` and `e.rows` count +- Idle status text is `Pick a format to download.` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/export-menu#default +Reference: https://ui.codeatlas.com.br/api/v1/en/e/019.txt + +## Source + +```svelte + + +
+ (last = `${e.filename}: ${e.rows} rows`)} + /> +

{last || 'Pick a format to download.'}

+
+ + +``` diff --git a/site/public/gallery/020.md b/site/public/gallery/020.md new file mode 100644 index 00000000..32489d48 --- /dev/null +++ b/site/public/gallery/020.md @@ -0,0 +1,69 @@ + +# #020 Basic + +A multi-file FileUpload with a size limit and hint that reports how many files are selected. + +When to use: Attachment and document upload areas that accept several files and cap their size. + +## Facets + +- Kind: component / input +- Industry: saas +- Style: default +- Motion: subtle +- Interaction: press, drag, keyboard +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: FileUpload +- Also searched as: dropzone, attachment, drag and drop + +## Spec + +- `bind:files` with `let files = $state([])` +- `multiple` +- `hint="PNG, JPG, PDF up to 10 MB"` +- `maxSize={10 * 1024 * 1024}` +- `{#if files.length > 0}` shows the selected file count with a plural `s` +- Wrapper `.wrap` has `max-width: 480px` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/file-upload#basic +Reference: https://ui.codeatlas.com.br/api/v1/en/e/020.txt + +## Source + +```svelte + + +
+ + {#if files.length > 0} +

{files.length} file{files.length > 1 ? 's' : ''} selected

+ {/if} +
+ + +``` diff --git a/site/public/gallery/021.md b/site/public/gallery/021.md new file mode 100644 index 00000000..ce305200 --- /dev/null +++ b/site/public/gallery/021.md @@ -0,0 +1,54 @@ + +# #021 Images only and disabled + +Two FileUpload dropzones: one restricted to images with custom label and hint, and one disabled. + +When to use: Image-only upload fields and upload areas that are unavailable to the current user. + +## Facets + +- Kind: component / input +- Industry: saas +- Style: default +- Motion: subtle +- Interaction: press, drag, keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: FileUpload +- Also searched as: image upload, drop area + +## Spec + +- `accept="image/*"` with `label="Drop images here"` and `hint="PNG, JPG, GIF, WebP"` +- `disabled` with `label="Upload unavailable"` +- Wrapper `.wrap` stacks both with `max-width: 480px` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/file-upload#images-only-and-disabled +Reference: https://ui.codeatlas.com.br/api/v1/en/e/021.txt + +## Source + +```svelte + + +
+ + +
+ + +``` diff --git a/site/public/gallery/022.md b/site/public/gallery/022.md new file mode 100644 index 00000000..80dee0f6 --- /dev/null +++ b/site/public/gallery/022.md @@ -0,0 +1,50 @@ + +# #022 Role filter + +A FilterGroup of three role options bound to a value that is printed below. + +When to use: Filtering a list or table by one category, such as user role, with a short set of choices. + +## Facets + +- Kind: component / input +- Industry: saas +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: single-column +- Color mode: both +- Density: compact +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: FilterGroup +- Also searched as: chips, category filter, pill filter + +## Spec + +- `` +- `const roles` has 3 options with `value` and `label`: `admin`, `editor`, `viewer` +- `let role = $state('all')` starts at `all`, which is not one of the options +- `

Selected: {role}

` prints the current value + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/filter-group#role-filter +Reference: https://ui.codeatlas.com.br/api/v1/en/e/022.txt + +## Source + +```svelte + + + +

Selected: {role}

+``` diff --git a/site/public/gallery/023.md b/site/public/gallery/023.md new file mode 100644 index 00000000..7373eba4 --- /dev/null +++ b/site/public/gallery/023.md @@ -0,0 +1,94 @@ + +# #023 Documents and dates + +Five MaskedField inputs for Brazilian documents: CPF, CNPJ, combined CPF or CNPJ, birth date and RG, with validators on three. + +When to use: Brazilian registration and checkout forms that collect formatted identity documents and dates. + +## Facets + +- Kind: component / input +- Industry: fintech +- Style: default +- Motion: subtle +- Interaction: keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: MaskedField +- Also searched as: cpf, cnpj, input mask, id number + +## Spec + +- `mask={maskCPF}` with `validate={(v) => (isValidCPF(v) ? undefined : 'Invalid CPF')}` and `placeholder="000.000.000-00"` +- `mask={maskCNPJ}` with `validate={(v) => (isValidCNPJ(v) ? undefined : 'Invalid CNPJ')}` and `placeholder="00.000.000/0000-00"` +- `mask={maskCPFCNPJ}` with `description="Switches mask after 11 digits."` +- `mask={maskDateBR}` with `isValidDateBR(v)` validation and `placeholder="dd/mm/yyyy"` +- `mask={maskRG}` with `placeholder="00.000.000-0"` and no `validate` +- Imports come from `@codeatlasdev/ui/forms`, each field uses `bind:value` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/masked-field#documents-and-dates +Reference: https://ui.codeatlas.com.br/api/v1/en/e/023.txt + +## Source + +```svelte + + +
+ (isValidCPF(v) ? undefined : 'Invalid CPF')} + bind:value={cpf} + /> + (isValidCNPJ(v) ? undefined : 'Invalid CNPJ')} + bind:value={cnpj} + /> + + (isValidDateBR(v) ? undefined : 'Invalid date')} + bind:value={birth} + /> + +
+ + +``` diff --git a/site/public/gallery/024.md b/site/public/gallery/024.md new file mode 100644 index 00000000..98ef5512 --- /dev/null +++ b/site/public/gallery/024.md @@ -0,0 +1,80 @@ + +# #024 use:mask action + +A native input masked with the mask action, showing the bound value below. + +When to use: Adding input masking to a custom or third-party input that is not a MaskedField. + +## Facets + +- Kind: component / input +- Industry: fintech +- Style: default +- Motion: none +- Interaction: keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: none +- Also searched as: input mask, action, format as you type + +## Spec + +- `use:mask={maskCPF}` on a plain `` is linked to `id="mask-action-input"` +- Output line prints `value = {value || '–'}` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/masked-field#use-mask-action +Reference: https://ui.codeatlas.com.br/api/v1/en/e/024.txt + +## Source + +```svelte + + +
+ + +

value = {value || '–'}

+
+ + +``` diff --git a/site/public/gallery/025.md b/site/public/gallery/025.md new file mode 100644 index 00000000..8728df0c --- /dev/null +++ b/site/public/gallery/025.md @@ -0,0 +1,87 @@ + +# #025 Phone and CEP + +A PhoneField and a CepField that looks up an address asynchronously and prints the resolved street, district and city. + +When to use: Brazilian address and contact forms that autofill street data from a postal code lookup. + +## Facets + +- Kind: component / input +- Industry: e-commerce +- Style: default +- Motion: subtle +- Interaction: keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: CepField, PhoneField +- Also searched as: phone, postal code, address lookup, zip + +## Spec + +- `` +- ` (address = a)}` +- `async function lookup(raw: string)` waits 600 ms and returns a `CepAddress` only for `01310100`, otherwise `null` +- Result line shows `Looking up…` while pending, then `logradouro`, `bairro`, `localidade` and `uf` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/masked-field#phone-and-cep +Reference: https://ui.codeatlas.com.br/api/v1/en/e/025.txt + +## Source + +```svelte + + +
+ +

value = {phone || '–'}

+ (address = a)} + /> +

+ {#if pending}Looking up…{:else if address}{address.logradouro}, {address.bairro} · {address.localidade}/{address.uf}{:else}–{/if} +

+
+ + +``` diff --git a/site/public/gallery/026.md b/site/public/gallery/026.md new file mode 100644 index 00000000..87e2b176 --- /dev/null +++ b/site/public/gallery/026.md @@ -0,0 +1,67 @@ + +# #026 Min, max, negative, error and disabled + +Four MoneyInput fields showing min and max bounds, negative values, an error message and a disabled state. + +When to use: Amount fields in payment and ledger forms that limit ranges, allow adjustments or lock a value. + +## Facets + +- Kind: component / input +- Industry: fintech +- Style: default +- Motion: subtle +- Interaction: keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: MoneyInput +- Also searched as: currency input, min max, amount + +## Spec + +- `label="Deposit"` with `min={1000}`, `max={10000}` and `description="Between R$ 10,00 and R$ 100,00."` +- `` with `let adjustment = $state(-2500)` +- `` +- `` +- Wrapper `.col` has `max-width: 400px` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/money-input#min-max-negative-error-and-disabled +Reference: https://ui.codeatlas.com.br/api/v1/en/e/026.txt + +## Source + +```svelte + + +
+ + + + +
+ + +``` diff --git a/site/public/gallery/027.md b/site/public/gallery/027.md new file mode 100644 index 00000000..d8d49583 --- /dev/null +++ b/site/public/gallery/027.md @@ -0,0 +1,58 @@ + +# #027 Currency and locale + +MoneyInput in three currencies and locales: USD en-US, JPY without decimals and EUR de-DE. + +When to use: Price fields in multi-currency products that must format and parse per locale. + +## Facets + +- Kind: component / input +- Industry: fintech, e-commerce +- Style: default +- Motion: subtle +- Interaction: keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: MoneyInput +- Also searched as: locale, price input, multi currency + +## Spec + +- `currency="USD" locale="en-US"` with `bind:value={usd}` starting at `$state(123456)` +- `currency="JPY" locale="en-US"` with `bind:value={jpy}` starting at `$state(1234)` +- `currency="EUR" locale="de-DE"` with `bind:value={eur}` starting at `$state(123456)` +- Labels: `Price (USD, en-US)`, `Price (JPY, no decimals)` and `Preis (EUR, de-DE)` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/money-input#currency-and-locale +Reference: https://ui.codeatlas.com.br/api/v1/en/e/027.txt + +## Source + +```svelte + + +
+ + + +
+ + +``` diff --git a/site/public/gallery/028.md b/site/public/gallery/028.md new file mode 100644 index 00000000..d928a936 --- /dev/null +++ b/site/public/gallery/028.md @@ -0,0 +1,64 @@ + +# #028 Default + +A default MoneyInput bound to a cents value, with the raw cents and formatted display printed below. + +When to use: A price or amount field where the model stores integer cents and the UI shows a formatted amount. + +## Facets + +- Kind: component / input +- Industry: fintech +- Style: default +- Motion: subtle +- Interaction: keyboard +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: MoneyInput +- Also searched as: price input, cents, currency + +## Spec + +- `` +- `let price = $state(123456)` holds cents +- Output line prints `{price} cents → {centsToDisplay(price)}` +- Wrapper `.col` has `max-width: 400px` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/money-input#default +Reference: https://ui.codeatlas.com.br/api/v1/en/e/028.txt + +## Source + +```svelte + + +
+ +

value = {price} cents → {centsToDisplay(price)}

+
+ + +``` diff --git a/site/public/gallery/029.md b/site/public/gallery/029.md new file mode 100644 index 00000000..df55ff42 --- /dev/null +++ b/site/public/gallery/029.md @@ -0,0 +1,60 @@ + +# #029 Helpers + +Two money helper functions evaluated inline: centsToDisplay and parseInputToCents. + +When to use: Formatting stored cents for display or parsing user-typed currency strings outside a MoneyInput. + +## Facets + +- Kind: component / input +- Industry: fintech +- Style: minimal +- Motion: none +- Interaction: none +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: not interactive +- Components: none +- Also searched as: format, parse, utility + +## Spec + +- `centsToDisplay(123456)` is printed with its result +- `parseInputToCents('R$ 1.234,56')` is printed with its result +- Both helpers are imported from `@codeatlasdev/ui/forms` +- No input component is rendered + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/money-input#helpers +Reference: https://ui.codeatlas.com.br/api/v1/en/e/029.txt + +## Source + +```svelte + + +
+

centsToDisplay(123456) = {centsToDisplay(123456)}

+

parseInputToCents("R$ 1.234,56") = {parseInputToCents('R$ 1.234,56')}

+
+ + +``` diff --git a/site/public/gallery/030.md b/site/public/gallery/030.md new file mode 100644 index 00000000..fbdc6003 --- /dev/null +++ b/site/public/gallery/030.md @@ -0,0 +1,47 @@ + +# #030 Basic + +Two NumberField inputs in a Stack: an integer quantity with bounds and a decimal price with a step. + +When to use: Quantity and price steppers in order or cart forms. + +## Facets + +- Kind: component / input +- Industry: e-commerce +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: NumberField, Stack +- Also searched as: stepper, quantity, spinbutton + +## Spec + +- `` wraps both fields +- `` with `let qty = $state(1)` +- `` with `let price = $state(9.99)` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/number-field#basic +Reference: https://ui.codeatlas.com.br/api/v1/en/e/030.txt + +## Source + +```svelte + + + + + + +``` diff --git a/site/public/gallery/031.md b/site/public/gallery/031.md new file mode 100644 index 00000000..d3b9d7fd --- /dev/null +++ b/site/public/gallery/031.md @@ -0,0 +1,51 @@ + +# #031 States + +Four NumberField states in a Stack: with description, with error and required, disabled, and small. + +When to use: Documenting helper text, validation, locked and compact variants of numeric fields such as seat counts. + +## Facets + +- Kind: component / input +- Industry: saas +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: NumberField, Stack +- Also searched as: stepper, validation, seats + +## Spec + +- `` +- `bind:value={seats}` with `let seats = $state(60)`, `error="Plans support up to 50 seats."` and `required` +- `` +- `` +- `` wraps the four fields + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/number-field#states +Reference: https://ui.codeatlas.com.br/api/v1/en/e/031.txt + +## Source + +```svelte + + + + + + + + +``` diff --git a/site/public/gallery/032.md b/site/public/gallery/032.md new file mode 100644 index 00000000..49be66e1 --- /dev/null +++ b/site/public/gallery/032.md @@ -0,0 +1,65 @@ + +# #032 Default + +A verification-code PinInput that reports its value and completion, plus a masked four-digit PIN. + +When to use: One-time code entry for sign-in verification, or a short secret PIN that must be hidden. + +## Facets + +- Kind: component / input +- Industry: saas +- Style: default +- Motion: subtle +- Interaction: keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: PinInput +- Also searched as: otp, verification code, one time code, passcode + +## Spec + +- ` (done = v)} />` +- Output line prints `value: {code || '–'} · completed: {done || '–'}` +- `` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/pin-input#default +Reference: https://ui.codeatlas.com.br/api/v1/en/e/032.txt + +## Source + +```svelte + + +
+
+ (done = v)} /> +

value: {code || '–'} · completed: {done || '–'}

+
+ +
+ + +``` diff --git a/site/public/gallery/033.md b/site/public/gallery/033.md new file mode 100644 index 00000000..6870494f --- /dev/null +++ b/site/public/gallery/033.md @@ -0,0 +1,57 @@ + +# #033 States + +Four PinInput states: with description, with error, disabled with a partial value, and a text-type five-character key. + +When to use: Documenting helper text, error feedback and non-numeric code variants of one-time code inputs. + +## Facets + +- Kind: component / input +- Industry: saas +- Style: default +- Motion: subtle +- Interaction: keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: PinInput +- Also searched as: otp, error state, access code + +## Spec + +- `description="Check your email."` with `bind:value={hinted}` starting at `$state('123')` +- `error="Invalid or expired code."` with `value="123456"` +- `disabled` with `value="12"` +- `` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/pin-input#states +Reference: https://ui.codeatlas.com.br/api/v1/en/e/033.txt + +## Source + +```svelte + + +
+ + + + +
+ + +``` diff --git a/site/public/gallery/034.md b/site/public/gallery/034.md new file mode 100644 index 00000000..f17a2214 --- /dev/null +++ b/site/public/gallery/034.md @@ -0,0 +1,50 @@ + +# #034 Cards + +Radio group in `cards` variant with three plans, one disabled, bound to a selected plan. + +When to use: Choosing a subscription plan or any option that needs a title and a short description. + +## Facets + +- Kind: component / input +- Industry: saas +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: RadioGroup +- Also searched as: plan picker, selectable cards, pricing choice + +## Spec + +- ` + import { RadioGroup } from '@codeatlasdev/ui'; + + let plan = $state('pro'); + + const plans = [ + { value: 'free', label: 'Free', description: 'For side projects.' }, + { value: 'pro', label: 'Pro', description: 'For teams that ship weekly.' }, + { value: 'team', label: 'Team', description: 'Shared billing and audit log.', disabled: true }, + ]; + + + +``` diff --git a/site/public/gallery/035.md b/site/public/gallery/035.md new file mode 100644 index 00000000..788f6793 --- /dev/null +++ b/site/public/gallery/035.md @@ -0,0 +1,57 @@ + +# #035 Disabled + +Two radio groups: one with a single disabled item and one fully disabled group. + +When to use: Showing unavailable choices in a form: one option locked, or a whole question locked. + +## Facets + +- Kind: component / input +- Industry: general +- Style: default +- Motion: none +- Interaction: keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: RadioGroup +- Also searched as: inactive options + +## Spec + +- First group `items={yesNo}` with `orientation="horizontal"` and `bind:value={answer}` +- `yesNo` has Yes, No, Maybe, and Maybe carries `disabled: true` +- Second group `items={fruits}` with `value="banana"` and `disabled` on the whole group +- `fruits` has 3 items: Apple, Banana, Cherry + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/radio#disabled +Reference: https://ui.codeatlas.com.br/api/v1/en/e/035.txt + +## Source + +```svelte + + + + +``` diff --git a/site/public/gallery/036.md b/site/public/gallery/036.md new file mode 100644 index 00000000..54a2bc3e --- /dev/null +++ b/site/public/gallery/036.md @@ -0,0 +1,49 @@ + +# #036 Horizontal + +Radio group laid out in a row with Small, Medium and Large options. + +When to use: Short choice lists of 2 to 4 options, such as a size picker, where a row fits the layout. + +## Facets + +- Kind: component / input +- Industry: general +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: RadioGroup +- Also searched as: inline radio, row + +## Spec + +- ` + import { RadioGroup } from '@codeatlasdev/ui'; + + let size = $state('md'); + + const sizes = [ + { value: 'sm', label: 'Small' }, + { value: 'md', label: 'Medium' }, + { value: 'lg', label: 'Large' }, + ]; + + + +``` diff --git a/site/public/gallery/037.md b/site/public/gallery/037.md new file mode 100644 index 00000000..105db217 --- /dev/null +++ b/site/public/gallery/037.md @@ -0,0 +1,60 @@ + +# #037 Vertical + +Default vertical radio group of three fruits with the selected value printed below. + +When to use: Single choice from a short list in a form or settings panel. + +## Facets + +- Kind: component / input +- Industry: general +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: RadioGroup +- Also searched as: option list, single choice + +## Spec + +- `{fruit}
` under the group + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/radio#vertical +Reference: https://ui.codeatlas.com.br/api/v1/en/e/037.txt + +## Source + +```svelte + + + +

Selected: {fruit}

+ + +``` diff --git a/site/public/gallery/038.md b/site/public/gallery/038.md new file mode 100644 index 00000000..1ddffaa6 --- /dev/null +++ b/site/public/gallery/038.md @@ -0,0 +1,81 @@ + +# #038 Default + +Two segmented controls: a text-only Week/Month/Year period and a List/Grid view switch with icons. + +When to use: Switching a view mode or time range inline above a list, chart or dashboard. + +## Facets + +- Kind: component / input +- Industry: saas +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: SegmentedControl +- Also searched as: tabs switch, view toggle, period selector + +## Spec + +- Period control `aria-label="Period"` with `bind:value={period}` and 3 items: Week, Month, Year +- Current value is shown as `value: {period}` +- View control `aria-label="View"` with 2 items using `icon: LayoutList` and `icon: LayoutGrid` +- Icons come from `@lucide/svelte` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/segmented-control#default +Reference: https://ui.codeatlas.com.br/api/v1/en/e/038.txt + +## Source + +```svelte + + +
+
+ +

value: {period}

+
+ +
+ + +``` diff --git a/site/public/gallery/039.md b/site/public/gallery/039.md new file mode 100644 index 00000000..d43fc5d5 --- /dev/null +++ b/site/public/gallery/039.md @@ -0,0 +1,65 @@ + +# #039 Small, full width, disabled item + +Small full-width segmented control with four items, one disabled. + +When to use: Compact filter bar that should stretch to its container, such as a table toolbar. + +## Facets + +- Kind: component / input +- Industry: general +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: single-column +- Color mode: both +- Density: compact +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: SegmentedControl +- Also searched as: full width switch, disabled segment + +## Spec + +- ` + import { SegmentedControl } from '@codeatlasdev/ui'; + + let size = $state('b'); + + +
+ +
+ + +``` diff --git a/site/public/gallery/040.md b/site/public/gallery/040.md new file mode 100644 index 00000000..c7f25daf --- /dev/null +++ b/site/public/gallery/040.md @@ -0,0 +1,77 @@ + +# #040 Basic + +Select with five framework options and the chosen value printed below. + +When to use: Picking one value from a known list in a form, such as a framework or category. + +## Facets + +- Kind: component / input +- Industry: devtools +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: Select +- Also searched as: dropdown, picker, listbox + +## Spec + +- ` +

Selected: {framework}

+ + + +``` diff --git a/site/public/gallery/041.md b/site/public/gallery/041.md new file mode 100644 index 00000000..3ed76823 --- /dev/null +++ b/site/public/gallery/041.md @@ -0,0 +1,66 @@ + +# #041 Empty-value option + +Select whose first option has an empty value labelled All projects, with the raw value printed. + +When to use: Filters where an empty value means no filter, such as All projects. + +## Facets + +- Kind: component / input +- Industry: saas +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: Select +- Also searched as: all option, filter dropdown, reset option + +## Spec + +- ` +

Filter value: {JSON.stringify(project)}

+ + + +``` diff --git a/site/public/gallery/042.md b/site/public/gallery/042.md new file mode 100644 index 00000000..27cef03c --- /dev/null +++ b/site/public/gallery/042.md @@ -0,0 +1,60 @@ + +# #042 Placeholder + +Select with no initial value that shows placeholder text until a country is chosen. + +When to use: Required choice in a form where no default should be preselected. + +## Facets + +- Kind: component / input +- Industry: travel +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: single-column +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: Select +- Also searched as: country picker, dropdown, empty state + +## Spec + +- ` + + + +``` diff --git a/site/public/gallery/043.md b/site/public/gallery/043.md new file mode 100644 index 00000000..df51c5e5 --- /dev/null +++ b/site/public/gallery/043.md @@ -0,0 +1,72 @@ + +# #043 States + +Three selects showing a description, an error and a disabled state, with one disabled option. + +When to use: Showing helper text, validation errors and locked fields on dropdowns in a form. + +## Facets + +- Kind: component / input +- Industry: saas +- Style: default +- Motion: subtle +- Interaction: press, keyboard +- Layout: stack +- Color mode: both +- Density: regular +- Tech: css (cost: css) +- Reduced motion: handled; keyboard: operable +- Components: Select +- Also searched as: validation, disabled option, helper text + +## Spec + +- Plan select has `description="Enterprise is disabled."` and `bind:value={plan}` +- `plans` has Free, Pro, Enterprise, and Enterprise has `disabled: true` +- Region select has `error="Pick a region to continue."` and no bound value +- Size select has `disabled` with `bind:value={size}` +- 3 selects stacked in `.col` with `max-width: 320px` + +Needs: @codeatlasdev/ui@4.5.0 +Live demo: https://ui.codeatlas.com.br/docs/inputs/select#states +Reference: https://ui.codeatlas.com.br/api/v1/en/e/043.txt + +## Source + +```svelte + + +
+ +