@bettercms-ai/kit 0.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -16
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/adapters/dock-values.d.ts +28 -0
- package/dist/adapters/dock-values.js +41 -0
- package/dist/adapters/dock-values.js.map +1 -0
- package/dist/adapters/icons.d.ts +15 -0
- package/dist/adapters/icons.js +27 -0
- package/dist/adapters/icons.js.map +1 -0
- package/dist/adapters/lossy.d.ts +326 -0
- package/dist/adapters/lossy.js +414 -0
- package/dist/adapters/lossy.js.map +1 -0
- package/dist/adapters/prod-types.d.ts +546 -0
- package/dist/adapters/prod-types.js +714 -0
- package/dist/adapters/prod-types.js.map +1 -0
- package/dist/adapters/schema.d.ts +154 -0
- package/dist/adapters/schema.js +3020 -0
- package/dist/adapters/schema.js.map +1 -0
- package/dist/adapters/values.d.ts +221 -0
- package/dist/adapters/values.js +623 -0
- package/dist/adapters/values.js.map +1 -0
- package/dist/brand/logo.d.ts +17 -0
- package/dist/brand/logo.js +96 -0
- package/dist/brand/logo.js.map +1 -0
- package/dist/context.d.ts +359 -0
- package/dist/context.js +217 -0
- package/dist/context.js.map +1 -0
- package/dist/controls/button.d.ts +72 -0
- package/dist/controls/button.js +578 -0
- package/dist/controls/button.js.map +1 -0
- package/dist/controls/c1.d.ts +11 -0
- package/dist/controls/c1.js +24 -0
- package/dist/controls/c1.js.map +1 -0
- package/dist/controls/c2.d.ts +10 -0
- package/dist/controls/c2.js +19 -0
- package/dist/controls/c2.js.map +1 -0
- package/dist/controls/choice.d.ts +44 -0
- package/dist/controls/choice.js +463 -0
- package/dist/controls/choice.js.map +1 -0
- package/dist/controls/code-register.d.ts +1 -0
- package/dist/controls/code-register.js +8 -0
- package/dist/controls/code-register.js.map +1 -0
- package/dist/controls/code.d.ts +42 -0
- package/dist/controls/code.js +397 -0
- package/dist/controls/code.js.map +1 -0
- package/dist/controls/color.d.ts +5 -0
- package/dist/controls/color.js +130 -0
- package/dist/controls/color.js.map +1 -0
- package/dist/controls/component-services.d.ts +156 -0
- package/dist/controls/component-services.js +7 -0
- package/dist/controls/component-services.js.map +1 -0
- package/dist/controls/date-register.d.ts +1 -0
- package/dist/controls/date-register.js +9 -0
- package/dist/controls/date-register.js.map +1 -0
- package/dist/controls/date-zone.d.ts +104 -0
- package/dist/controls/date-zone.js +278 -0
- package/dist/controls/date-zone.js.map +1 -0
- package/dist/controls/date.d.ts +7 -0
- package/dist/controls/date.js +466 -0
- package/dist/controls/date.js.map +1 -0
- package/dist/controls/form-picker-register.d.ts +1 -0
- package/dist/controls/form-picker-register.js +19 -0
- package/dist/controls/form-picker-register.js.map +1 -0
- package/dist/controls/form-picker.d.ts +43 -0
- package/dist/controls/form-picker.js +85 -0
- package/dist/controls/form-picker.js.map +1 -0
- package/dist/controls/icon-register.d.ts +1 -0
- package/dist/controls/icon-register.js +8 -0
- package/dist/controls/icon-register.js.map +1 -0
- package/dist/controls/icon.d.ts +40 -0
- package/dist/controls/icon.js +360 -0
- package/dist/controls/icon.js.map +1 -0
- package/dist/controls/index.d.ts +5 -0
- package/dist/controls/index.js +38 -0
- package/dist/controls/index.js.map +1 -0
- package/dist/controls/json-register.d.ts +1 -0
- package/dist/controls/json-register.js +8 -0
- package/dist/controls/json-register.js.map +1 -0
- package/dist/controls/json.d.ts +2 -0
- package/dist/controls/json.js +82 -0
- package/dist/controls/json.js.map +1 -0
- package/dist/controls/link.d.ts +260 -0
- package/dist/controls/link.js +1186 -0
- package/dist/controls/link.js.map +1 -0
- package/dist/controls/location-register.d.ts +1 -0
- package/dist/controls/location-register.js +8 -0
- package/dist/controls/location-register.js.map +1 -0
- package/dist/controls/location.d.ts +16 -0
- package/dist/controls/location.js +221 -0
- package/dist/controls/location.js.map +1 -0
- package/dist/controls/media-register.d.ts +1 -0
- package/dist/controls/media-register.js +12 -0
- package/dist/controls/media-register.js.map +1 -0
- package/dist/controls/media-services.d.ts +46 -0
- package/dist/controls/media-services.js +0 -0
- package/dist/controls/media.d.ts +104 -0
- package/dist/controls/media.js +1396 -0
- package/dist/controls/media.js.map +1 -0
- package/dist/controls/number.d.ts +22 -0
- package/dist/controls/number.js +196 -0
- package/dist/controls/number.js.map +1 -0
- package/dist/controls/reference-register.d.ts +1 -0
- package/dist/controls/reference-register.js +10 -0
- package/dist/controls/reference-register.js.map +1 -0
- package/dist/controls/reference-services.d.ts +58 -0
- package/dist/controls/reference-services.js +0 -0
- package/dist/controls/reference.d.ts +23 -0
- package/dist/controls/reference.js +777 -0
- package/dist/controls/reference.js.map +1 -0
- package/dist/controls/rich-text/blocks.d.ts +49 -0
- package/dist/controls/rich-text/blocks.js +1859 -0
- package/dist/controls/rich-text/blocks.js.map +1 -0
- package/dist/controls/rich-text/catalog.d.ts +101 -0
- package/dist/controls/rich-text/catalog.js +623 -0
- package/dist/controls/rich-text/catalog.js.map +1 -0
- package/dist/controls/rich-text/codec.d.ts +64 -0
- package/dist/controls/rich-text/codec.js +34 -0
- package/dist/controls/rich-text/codec.js.map +1 -0
- package/dist/controls/rich-text/component-card.d.ts +14 -0
- package/dist/controls/rich-text/component-card.js +1024 -0
- package/dist/controls/rich-text/component-card.js.map +1 -0
- package/dist/controls/rich-text/component-panel.d.ts +47 -0
- package/dist/controls/rich-text/component-panel.js +141 -0
- package/dist/controls/rich-text/component-panel.js.map +1 -0
- package/dist/controls/rich-text/control.d.ts +20 -0
- package/dist/controls/rich-text/control.js +296 -0
- package/dist/controls/rich-text/control.js.map +1 -0
- package/dist/controls/rich-text/dom.d.ts +233 -0
- package/dist/controls/rich-text/dom.js +1019 -0
- package/dist/controls/rich-text/dom.js.map +1 -0
- package/dist/controls/rich-text/editor.d.ts +57 -0
- package/dist/controls/rich-text/editor.js +1630 -0
- package/dist/controls/rich-text/editor.js.map +1 -0
- package/dist/controls/rich-text/floating.d.ts +28 -0
- package/dist/controls/rich-text/floating.js +154 -0
- package/dist/controls/rich-text/floating.js.map +1 -0
- package/dist/controls/rich-text/index.d.ts +1 -0
- package/dist/controls/rich-text/index.js +12 -0
- package/dist/controls/rich-text/index.js.map +1 -0
- package/dist/controls/rich-text/link-form.d.ts +13 -0
- package/dist/controls/rich-text/link-form.js +147 -0
- package/dist/controls/rich-text/link-form.js.map +1 -0
- package/dist/controls/rich-text/markdown.d.ts +6 -0
- package/dist/controls/rich-text/markdown.js +181 -0
- package/dist/controls/rich-text/markdown.js.map +1 -0
- package/dist/controls/rich-text/ops.d.ts +35 -0
- package/dist/controls/rich-text/ops.js +316 -0
- package/dist/controls/rich-text/ops.js.map +1 -0
- package/dist/controls/rich-text/plain.d.ts +17 -0
- package/dist/controls/rich-text/plain.js +44 -0
- package/dist/controls/rich-text/plain.js.map +1 -0
- package/dist/controls/rich-text/toolbar.d.ts +89 -0
- package/dist/controls/rich-text/toolbar.js +343 -0
- package/dist/controls/rich-text/toolbar.js.map +1 -0
- package/dist/controls/scale.d.ts +56 -0
- package/dist/controls/scale.js +563 -0
- package/dist/controls/scale.js.map +1 -0
- package/dist/controls/slot-register.d.ts +1 -0
- package/dist/controls/slot-register.js +16 -0
- package/dist/controls/slot-register.js.map +1 -0
- package/dist/controls/slot-services.d.ts +122 -0
- package/dist/controls/slot-services.js +155 -0
- package/dist/controls/slot-services.js.map +1 -0
- package/dist/controls/slot.d.ts +33 -0
- package/dist/controls/slot.js +366 -0
- package/dist/controls/slot.js.map +1 -0
- package/dist/controls/table/index.d.ts +1 -0
- package/dist/controls/table/index.js +12 -0
- package/dist/controls/table/index.js.map +1 -0
- package/dist/controls/table/prod.d.ts +21 -0
- package/dist/controls/table/prod.js +79 -0
- package/dist/controls/table/prod.js.map +1 -0
- package/dist/controls/table/refs.d.ts +50 -0
- package/dist/controls/table/refs.js +79 -0
- package/dist/controls/table/refs.js.map +1 -0
- package/dist/controls/table/stored.d.ts +15 -0
- package/dist/controls/table/stored.js +44 -0
- package/dist/controls/table/stored.js.map +1 -0
- package/dist/controls/table/table-editor.d.ts +103 -0
- package/dist/controls/table/table-editor.js +2725 -0
- package/dist/controls/table/table-editor.js.map +1 -0
- package/dist/controls/table/table-link.d.ts +24 -0
- package/dist/controls/table/table-link.js +180 -0
- package/dist/controls/table/table-link.js.map +1 -0
- package/dist/controls/table/table.d.ts +9 -0
- package/dist/controls/table/table.js +103 -0
- package/dist/controls/table/table.js.map +1 -0
- package/dist/controls/text.d.ts +17 -0
- package/dist/controls/text.js +322 -0
- package/dist/controls/text.js.map +1 -0
- package/dist/controls/unknown-value.d.ts +17 -0
- package/dist/controls/unknown-value.js +66 -0
- package/dist/controls/unknown-value.js.map +1 -0
- package/dist/core/core.d.ts +63 -0
- package/dist/core/core.js +171 -0
- package/dist/core/core.js.map +1 -0
- package/dist/core/facets.d.ts +347 -0
- package/dist/core/facets.js +334 -0
- package/dist/core/facets.js.map +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +3 -0
- package/dist/engine/array.d.ts +101 -0
- package/dist/engine/array.js +906 -0
- package/dist/engine/array.js.map +1 -0
- package/dist/engine/bits.d.ts +24 -0
- package/dist/engine/bits.js +60 -0
- package/dist/engine/bits.js.map +1 -0
- package/dist/engine/breadcrumb.d.ts +45 -0
- package/dist/engine/breadcrumb.js +63 -0
- package/dist/engine/breadcrumb.js.map +1 -0
- package/dist/engine/expand.d.ts +66 -0
- package/dist/engine/expand.js +153 -0
- package/dist/engine/expand.js.map +1 -0
- package/dist/engine/field-list.d.ts +20 -0
- package/dist/engine/field-list.js +19 -0
- package/dist/engine/field-list.js.map +1 -0
- package/dist/engine/field-shell.d.ts +49 -0
- package/dist/engine/field-shell.js +988 -0
- package/dist/engine/field-shell.js.map +1 -0
- package/dist/engine/frame.d.ts +122 -0
- package/dist/engine/frame.js +601 -0
- package/dist/engine/frame.js.map +1 -0
- package/dist/engine/group-summary.d.ts +5 -0
- package/dist/engine/group-summary.js +70 -0
- package/dist/engine/group-summary.js.map +1 -0
- package/dist/engine/grouping.d.ts +238 -0
- package/dist/engine/grouping.js +996 -0
- package/dist/engine/grouping.js.map +1 -0
- package/dist/engine/item-list.d.ts +99 -0
- package/dist/engine/item-list.js +725 -0
- package/dist/engine/item-list.js.map +1 -0
- package/dist/engine/kit-field.d.ts +72 -0
- package/dist/engine/kit-field.js +558 -0
- package/dist/engine/kit-field.js.map +1 -0
- package/dist/engine/schema-form.d.ts +116 -0
- package/dist/engine/schema-form.js +462 -0
- package/dist/engine/schema-form.js.map +1 -0
- package/dist/engine/slot.d.ts +13 -0
- package/dist/engine/slot.js +216 -0
- package/dist/engine/slot.js.map +1 -0
- package/dist/index.d.ts +45 -3139
- package/dist/index.js +47 -2036
- package/dist/index.js.map +1 -0
- package/dist/kit.css +75 -0
- package/dist/lib/cn.d.ts +4 -0
- package/dist/lib/cn.js +56 -0
- package/dist/lib/cn.js.map +1 -0
- package/dist/lib/columns.d.ts +14 -0
- package/dist/lib/columns.js +30 -0
- package/dist/lib/columns.js.map +1 -0
- package/dist/lib/frames.d.ts +36 -0
- package/dist/lib/frames.js +214 -0
- package/dist/lib/frames.js.map +1 -0
- package/dist/lib/icon-image.d.ts +10 -0
- package/dist/lib/icon-image.js +8 -0
- package/dist/lib/icon-image.js.map +1 -0
- package/dist/lib/icons.d.ts +16 -0
- package/dist/lib/icons.js +173 -0
- package/dist/lib/icons.js.map +1 -0
- package/dist/lib/input-modality.d.ts +11 -0
- package/dist/lib/input-modality.js +47 -0
- package/dist/lib/input-modality.js.map +1 -0
- package/dist/lib/key-event.d.ts +11 -0
- package/dist/lib/key-event.js +16 -0
- package/dist/lib/key-event.js.map +1 -0
- package/dist/lib/path.d.ts +29 -0
- package/dist/lib/path.js +66 -0
- package/dist/lib/path.js.map +1 -0
- package/dist/lib/schema.d.ts +80 -0
- package/dist/lib/schema.js +384 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/utils.d.ts +7 -0
- package/dist/lib/utils.js +37 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/validate.d.ts +30 -0
- package/dist/lib/validate.js +224 -0
- package/dist/lib/validate.js.map +1 -0
- package/dist/library/analysis.d.ts +23 -0
- package/dist/library/analysis.js +160 -0
- package/dist/library/analysis.js.map +1 -0
- package/dist/library/catalog.d.ts +57 -0
- package/dist/library/catalog.js +307 -0
- package/dist/library/catalog.js.map +1 -0
- package/dist/library/coreNotes.d.ts +8 -0
- package/dist/library/coreNotes.js +111 -0
- package/dist/library/coreNotes.js.map +1 -0
- package/dist/library/coverage.d.ts +40 -0
- package/dist/library/coverage.js +59 -0
- package/dist/library/coverage.js.map +1 -0
- package/dist/library/entries/advanced.entries.d.ts +2 -0
- package/dist/library/entries/advanced.entries.js +6579 -0
- package/dist/library/entries/advanced.entries.js.map +1 -0
- package/dist/library/entries/commerce.entries.d.ts +2 -0
- package/dist/library/entries/commerce.entries.js +4140 -0
- package/dist/library/entries/commerce.entries.js.map +1 -0
- package/dist/library/entries/company.entries.d.ts +2 -0
- package/dist/library/entries/company.entries.js +2731 -0
- package/dist/library/entries/company.entries.js.map +1 -0
- package/dist/library/entries/conversion.entries.d.ts +2 -0
- package/dist/library/entries/conversion.entries.js +3707 -0
- package/dist/library/entries/conversion.entries.js.map +1 -0
- package/dist/library/entries/data.entries.d.ts +7 -0
- package/dist/library/entries/data.entries.js +4613 -0
- package/dist/library/entries/data.entries.js.map +1 -0
- package/dist/library/entries/events.entries.d.ts +2 -0
- package/dist/library/entries/events.entries.js +3428 -0
- package/dist/library/entries/events.entries.js.map +1 -0
- package/dist/library/entries/features.entries.d.ts +2 -0
- package/dist/library/entries/features.entries.js +3157 -0
- package/dist/library/entries/features.entries.js.map +1 -0
- package/dist/library/entries/heroes.entries.d.ts +2 -0
- package/dist/library/entries/heroes.entries.js +3401 -0
- package/dist/library/entries/heroes.entries.js.map +1 -0
- package/dist/library/entries/index.d.ts +3 -0
- package/dist/library/entries/index.js +35 -0
- package/dist/library/entries/index.js.map +1 -0
- package/dist/library/entries/listings.entries.d.ts +2 -0
- package/dist/library/entries/listings.entries.js +2976 -0
- package/dist/library/entries/listings.entries.js.map +1 -0
- package/dist/library/entries/longform.entries.d.ts +2 -0
- package/dist/library/entries/longform.entries.js +4227 -0
- package/dist/library/entries/longform.entries.js.map +1 -0
- package/dist/library/entries/media.entries.d.ts +2 -0
- package/dist/library/entries/media.entries.js +2781 -0
- package/dist/library/entries/media.entries.js.map +1 -0
- package/dist/library/entries/navigation.entries.d.ts +2 -0
- package/dist/library/entries/navigation.entries.js +4011 -0
- package/dist/library/entries/navigation.entries.js.map +1 -0
- package/dist/library/entries/pricing.entries.d.ts +2 -0
- package/dist/library/entries/pricing.entries.js +2383 -0
- package/dist/library/entries/pricing.entries.js.map +1 -0
- package/dist/library/entries/social-proof.entries.d.ts +2 -0
- package/dist/library/entries/social-proof.entries.js +2297 -0
- package/dist/library/entries/social-proof.entries.js.map +1 -0
- package/dist/library/examples/bento.d.ts +92 -0
- package/dist/library/examples/bento.js +201 -0
- package/dist/library/examples/bento.js.map +1 -0
- package/dist/library/examples/blocks.d.ts +14 -0
- package/dist/library/examples/blocks.js +313 -0
- package/dist/library/examples/blocks.js.map +1 -0
- package/dist/library/examples/faq.d.ts +28 -0
- package/dist/library/examples/faq.js +230 -0
- package/dist/library/examples/faq.js.map +1 -0
- package/dist/library/examples/hero.d.ts +43 -0
- package/dist/library/examples/hero.js +222 -0
- package/dist/library/examples/hero.js.map +1 -0
- package/dist/library/examples/index.d.ts +36 -0
- package/dist/library/examples/index.js +109 -0
- package/dist/library/examples/index.js.map +1 -0
- package/dist/library/examples/journalStory.d.ts +93 -0
- package/dist/library/examples/journalStory.js +338 -0
- package/dist/library/examples/journalStory.js.map +1 -0
- package/dist/library/examples/megaMenu.d.ts +134 -0
- package/dist/library/examples/megaMenu.js +442 -0
- package/dist/library/examples/megaMenu.js.map +1 -0
- package/dist/library/examples/pricing.d.ts +103 -0
- package/dist/library/examples/pricing.js +479 -0
- package/dist/library/examples/pricing.js.map +1 -0
- package/dist/library/examples/tabs.d.ts +87 -0
- package/dist/library/examples/tabs.js +180 -0
- package/dist/library/examples/tabs.js.map +1 -0
- package/dist/library/export.d.ts +42 -0
- package/dist/library/export.js +80 -0
- package/dist/library/export.js.map +1 -0
- package/dist/library/index.d.ts +13 -0
- package/dist/library/index.js +6 -0
- package/dist/library/kit-catalog.json +1510 -0
- package/dist/library/types.d.ts +42 -0
- package/dist/library/types.js +0 -0
- package/dist/schema/adapters.d.ts +28 -0
- package/dist/schema/adapters.js +492 -0
- package/dist/schema/adapters.js.map +1 -0
- package/dist/schema/help.d.ts +27 -0
- package/dist/schema/help.js +56 -0
- package/dist/schema/help.js.map +1 -0
- package/dist/schema/scale.d.ts +69 -0
- package/dist/schema/scale.js +250 -0
- package/dist/schema/scale.js.map +1 -0
- package/dist/schema/sources.d.ts +103 -0
- package/dist/schema/sources.js +65 -0
- package/dist/schema/sources.js.map +1 -0
- package/dist/schema/table.d.ts +319 -0
- package/dist/schema/table.js +1199 -0
- package/dist/schema/table.js.map +1 -0
- package/dist/schema/types.d.ts +297 -0
- package/dist/schema/types.js +14 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/table/table-view.d.ts +14 -0
- package/dist/table/table-view.js +140 -0
- package/dist/table/table-view.js.map +1 -0
- package/dist/table/table.css +73 -0
- package/dist/testing/corpus.d.ts +63 -0
- package/dist/testing/corpus.js +129 -0
- package/dist/testing/corpus.js.map +1 -0
- package/dist/testing/test-form.d.ts +11 -0
- package/dist/testing/test-form.js +38 -0
- package/dist/testing/test-form.js.map +1 -0
- package/dist/theme.css +345 -0
- package/dist/theme.d.ts +20 -1504
- package/dist/theme.js +43 -4126
- package/dist/theme.js.map +1 -0
- package/dist/tokens.css +542 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.js +0 -0
- package/dist/ui/action-tray.d.ts +54 -0
- package/dist/ui/action-tray.js +154 -0
- package/dist/ui/action-tray.js.map +1 -0
- package/dist/ui/autocomplete.d.ts +30 -0
- package/dist/ui/autocomplete.js +139 -0
- package/dist/ui/autocomplete.js.map +1 -0
- package/dist/ui/button.d.ts +57 -0
- package/dist/ui/button.js +201 -0
- package/dist/ui/button.js.map +1 -0
- package/dist/ui/choice-switch.d.ts +36 -0
- package/dist/ui/choice-switch.js +57 -0
- package/dist/ui/choice-switch.js.map +1 -0
- package/dist/ui/controls.d.ts +83 -0
- package/dist/ui/controls.js +331 -0
- package/dist/ui/controls.js.map +1 -0
- package/dist/ui/display.d.ts +163 -0
- package/dist/ui/display.js +455 -0
- package/dist/ui/display.js.map +1 -0
- package/dist/ui/expanded-editor-modal.d.ts +26 -0
- package/dist/ui/expanded-editor-modal.js +140 -0
- package/dist/ui/expanded-editor-modal.js.map +1 -0
- package/dist/ui/field-context.d.ts +12 -0
- package/dist/ui/field-context.js +23 -0
- package/dist/ui/field-context.js.map +1 -0
- package/dist/ui/floating-panel.d.ts +152 -0
- package/dist/ui/floating-panel.js +928 -0
- package/dist/ui/floating-panel.js.map +1 -0
- package/dist/ui/index.d.ts +16 -0
- package/dist/ui/index.js +18 -0
- package/dist/ui/input.d.ts +78 -0
- package/dist/ui/input.js +250 -0
- package/dist/ui/input.js.map +1 -0
- package/dist/ui/mode-bar.d.ts +31 -0
- package/dist/ui/mode-bar.js +65 -0
- package/dist/ui/mode-bar.js.map +1 -0
- package/dist/ui/overlay.d.ts +195 -0
- package/dist/ui/overlay.js +697 -0
- package/dist/ui/overlay.js.map +1 -0
- package/dist/ui/path-crumbs.d.ts +90 -0
- package/dist/ui/path-crumbs.js +566 -0
- package/dist/ui/path-crumbs.js.map +1 -0
- package/dist/ui/presence.d.ts +16 -0
- package/dist/ui/presence.js +65 -0
- package/dist/ui/presence.js.map +1 -0
- package/dist/ui/select.d.ts +66 -0
- package/dist/ui/select.js +407 -0
- package/dist/ui/select.js.map +1 -0
- package/dist/ui/shortcut-hint.d.ts +14 -0
- package/dist/ui/shortcut-hint.js +35 -0
- package/dist/ui/shortcut-hint.js.map +1 -0
- package/dist/ui/spinner.d.ts +24 -0
- package/dist/ui/spinner.js +42 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/ui/split-button.d.ts +32 -0
- package/dist/ui/split-button.js +60 -0
- package/dist/ui/split-button.js.map +1 -0
- package/dist/ui/surfaces.d.ts +23 -0
- package/dist/ui/surfaces.js +26 -0
- package/dist/ui/surfaces.js.map +1 -0
- package/dist/ui/tab.d.ts +38 -0
- package/dist/ui/tab.js +87 -0
- package/dist/ui/tab.js.map +1 -0
- package/dist/ui/toast.d.ts +20 -0
- package/dist/ui/toast.js +115 -0
- package/dist/ui/toast.js.map +1 -0
- package/dist/ui/tooltip-root.d.ts +13 -0
- package/dist/ui/tooltip-root.js +77 -0
- package/dist/ui/tooltip-root.js.map +1 -0
- package/dist/ui/variants.d.ts +15 -0
- package/dist/ui/variants.js +14 -0
- package/dist/ui/variants.js.map +1 -0
- package/package.json +76 -50
- package/LICENSE +0 -21
- package/dist/_chunks-cjs/_visual-editing.js +0 -5898
- package/dist/_chunks-cjs/refractor.js +0 -22
- package/dist/_chunks-es/_visual-editing.mjs +0 -5904
- package/dist/_chunks-es/refractor.mjs +0 -25
- package/dist/_color.d.mts +0 -119
- package/dist/_color.d.ts +0 -119
- package/dist/_color.js +0 -1412
- package/dist/_color.mjs +0 -1411
- package/dist/_visual-editing.d.mts +0 -1126
- package/dist/_visual-editing.d.ts +0 -1126
- package/dist/_visual-editing.js +0 -30
- package/dist/_visual-editing.mjs +0 -30
- package/dist/index.d.mts +0 -3140
- package/dist/index.mjs +0 -2044
- package/dist/theme.d.mts +0 -1506
- package/dist/theme.mjs +0 -4126
- package/exports/_visual-editing.ts +0 -31
- package/exports/index.ts +0 -1
- package/exports/theme.ts +0 -1
- package/src/color/index.d.mts +0 -119
- package/src/color/index.mjs +0 -1411
- package/src/core/__workshop__/constants.ts +0 -293
- package/src/core/_compat.ts +0 -264
- package/src/core/components/autocomplete/__mocks__/apiStore.ts +0 -49
- package/src/core/components/autocomplete/__mocks__/countries.ts +0 -245
- package/src/core/components/autocomplete/__workshop__/async.tsx +0 -156
- package/src/core/components/autocomplete/__workshop__/constrainedHeight.tsx +0 -137
- package/src/core/components/autocomplete/__workshop__/custom.tsx +0 -89
- package/src/core/components/autocomplete/__workshop__/example.tsx +0 -79
- package/src/core/components/autocomplete/__workshop__/focusAndBlur.tsx +0 -36
- package/src/core/components/autocomplete/__workshop__/fullscreen.tsx +0 -294
- package/src/core/components/autocomplete/__workshop__/index.ts +0 -39
- package/src/core/components/autocomplete/__workshop__/types.ts +0 -4
- package/src/core/components/autocomplete/autocomplete.styles.tsx +0 -39
- package/src/core/components/autocomplete/autocomplete.tsx +0 -767
- package/src/core/components/autocomplete/autocompleteOption.tsx +0 -45
- package/src/core/components/autocomplete/autocompleteReducer.ts +0 -47
- package/src/core/components/autocomplete/constants.ts +0 -28
- package/src/core/components/autocomplete/index.ts +0 -2
- package/src/core/components/autocomplete/types.ts +0 -108
- package/src/core/components/breadcrumbs/__workshop__/example.tsx +0 -62
- package/src/core/components/breadcrumbs/__workshop__/index.ts +0 -14
- package/src/core/components/breadcrumbs/breadcrumbs.styles.ts +0 -18
- package/src/core/components/breadcrumbs/breadcrumbs.test.tsx +0 -51
- package/src/core/components/breadcrumbs/breadcrumbs.tsx +0 -133
- package/src/core/components/breadcrumbs/index.ts +0 -1
- package/src/core/components/dialog/__workshop__/activate.tsx +0 -134
- package/src/core/components/dialog/__workshop__/autoFocus.tsx +0 -29
- package/src/core/components/dialog/__workshop__/index.ts +0 -19
- package/src/core/components/dialog/__workshop__/layering.tsx +0 -41
- package/src/core/components/dialog/__workshop__/nested.tsx +0 -72
- package/src/core/components/dialog/__workshop__/onScroll.tsx +0 -39
- package/src/core/components/dialog/__workshop__/panes.tsx +0 -82
- package/src/core/components/dialog/__workshop__/position.tsx +0 -30
- package/src/core/components/dialog/__workshop__/props.tsx +0 -75
- package/src/core/components/dialog/__workshop__/provider.tsx +0 -11
- package/src/core/components/dialog/__workshop__/wrapped.tsx +0 -76
- package/src/core/components/dialog/dialog.tsx +0 -430
- package/src/core/components/dialog/dialogContext.ts +0 -20
- package/src/core/components/dialog/dialogProvider.tsx +0 -35
- package/src/core/components/dialog/index.ts +0 -4
- package/src/core/components/dialog/styles.ts +0 -77
- package/src/core/components/dialog/useDialog.ts +0 -11
- package/src/core/components/hotkeys/__workshop__/index.ts +0 -14
- package/src/core/components/hotkeys/__workshop__/plain.tsx +0 -9
- package/src/core/components/hotkeys/hotkeys.test.tsx +0 -42
- package/src/core/components/hotkeys/hotkeys.tsx +0 -66
- package/src/core/components/hotkeys/index.ts +0 -1
- package/src/core/components/index.ts +0 -9
- package/src/core/components/menu/__workshop__/asComponent.tsx +0 -45
- package/src/core/components/menu/__workshop__/avatarMenu.tsx +0 -51
- package/src/core/components/menu/__workshop__/closableMenuButton.tsx +0 -46
- package/src/core/components/menu/__workshop__/constrainedInBoundary.tsx +0 -134
- package/src/core/components/menu/__workshop__/customMenuItem.tsx +0 -45
- package/src/core/components/menu/__workshop__/customSelectedState.tsx +0 -39
- package/src/core/components/menu/__workshop__/disableFocusOnClose.tsx +0 -40
- package/src/core/components/menu/__workshop__/groups.tsx +0 -156
- package/src/core/components/menu/__workshop__/index.ts +0 -89
- package/src/core/components/menu/__workshop__/menuButton.tsx +0 -80
- package/src/core/components/menu/__workshop__/menuGroupRight.tsx +0 -58
- package/src/core/components/menu/__workshop__/nestedMenu.tsx +0 -22
- package/src/core/components/menu/__workshop__/onCloseMenuButton.tsx +0 -41
- package/src/core/components/menu/__workshop__/selectedItem.tsx +0 -69
- package/src/core/components/menu/__workshop__/shouldFocus.tsx +0 -47
- package/src/core/components/menu/__workshop__/tones.tsx +0 -25
- package/src/core/components/menu/__workshop__/withoutArrow.tsx +0 -32
- package/src/core/components/menu/helpers.ts +0 -82
- package/src/core/components/menu/index.ts +0 -5
- package/src/core/components/menu/menu.spacing.test.tsx +0 -63
- package/src/core/components/menu/menu.test.tsx +0 -125
- package/src/core/components/menu/menu.tsx +0 -178
- package/src/core/components/menu/menuButton.tsx +0 -268
- package/src/core/components/menu/menuContext.ts +0 -18
- package/src/core/components/menu/menuDivider.ts +0 -23
- package/src/core/components/menu/menuGroup.spacing.test.tsx +0 -63
- package/src/core/components/menu/menuGroup.tsx +0 -233
- package/src/core/components/menu/menuItem.spacing.test.tsx +0 -59
- package/src/core/components/menu/menuItem.tsx +0 -197
- package/src/core/components/menu/useMenu.ts +0 -21
- package/src/core/components/menu/useMenuController.ts +0 -231
- package/src/core/components/skeleton/__workshop__/delay.tsx +0 -70
- package/src/core/components/skeleton/__workshop__/index.ts +0 -11
- package/src/core/components/skeleton/__workshop__/skeleton.tsx +0 -64
- package/src/core/components/skeleton/index.ts +0 -2
- package/src/core/components/skeleton/skeleton.tsx +0 -57
- package/src/core/components/skeleton/styles.ts +0 -50
- package/src/core/components/skeleton/textSkeleton.tsx +0 -122
- package/src/core/components/tab/__workshop__/example.tsx +0 -79
- package/src/core/components/tab/__workshop__/index.ts +0 -14
- package/src/core/components/tab/index.ts +0 -3
- package/src/core/components/tab/tab.tsx +0 -101
- package/src/core/components/tab/tabList.test.tsx +0 -48
- package/src/core/components/tab/tabList.tsx +0 -70
- package/src/core/components/tab/tabPanel.tsx +0 -39
- package/src/core/components/toast/__workshop__/hook.tsx +0 -71
- package/src/core/components/toast/__workshop__/index.ts +0 -19
- package/src/core/components/toast/__workshop__/toast.tsx +0 -26
- package/src/core/components/toast/index.ts +0 -4
- package/src/core/components/toast/styles.ts +0 -77
- package/src/core/components/toast/toast.test.tsx +0 -101
- package/src/core/components/toast/toast.tsx +0 -198
- package/src/core/components/toast/toastContext.ts +0 -7
- package/src/core/components/toast/toastLayer.tsx +0 -51
- package/src/core/components/toast/toastProvider.tsx +0 -113
- package/src/core/components/toast/toastState.ts +0 -6
- package/src/core/components/toast/types.ts +0 -26
- package/src/core/components/toast/useToast.ts +0 -25
- package/src/core/components/tree/__workshop__/basic.perf.ts +0 -19
- package/src/core/components/tree/__workshop__/basic.tsx +0 -105
- package/src/core/components/tree/__workshop__/index.ts +0 -21
- package/src/core/components/tree/__workshop__/tabFromElement.tsx +0 -84
- package/src/core/components/tree/helpers.ts +0 -105
- package/src/core/components/tree/index.ts +0 -4
- package/src/core/components/tree/style.ts +0 -105
- package/src/core/components/tree/tree.test.tsx +0 -55
- package/src/core/components/tree/tree.tsx +0 -240
- package/src/core/components/tree/treeContext.ts +0 -10
- package/src/core/components/tree/treeGroup.tsx +0 -28
- package/src/core/components/tree/treeItem.test.tsx +0 -65
- package/src/core/components/tree/treeItem.tsx +0 -224
- package/src/core/components/tree/types.ts +0 -25
- package/src/core/components/tree/useTree.ts +0 -17
- package/src/core/constants.ts +0 -79
- package/src/core/helpers/animation.ts +0 -27
- package/src/core/helpers/element.ts +0 -55
- package/src/core/helpers/focus.ts +0 -100
- package/src/core/helpers/index.ts +0 -4
- package/src/core/helpers/scroll.ts +0 -15
- package/src/core/hooks/index.ts +0 -12
- package/src/core/hooks/useArrayProp.ts +0 -17
- package/src/core/hooks/useClickOutside.test.tsx +0 -482
- package/src/core/hooks/useClickOutside.ts +0 -118
- package/src/core/hooks/useClickOutsideEvent.test.tsx +0 -115
- package/src/core/hooks/useClickOutsideEvent.ts +0 -73
- package/src/core/hooks/useCustomValidity.ts +0 -13
- package/src/core/hooks/useDelayed.test.ts +0 -88
- package/src/core/hooks/useDelayedState.ts +0 -36
- package/src/core/hooks/useElementRect/__workshop__/example.tsx +0 -32
- package/src/core/hooks/useElementRect/__workshop__/index.ts +0 -14
- package/src/core/hooks/useElementRect/index.ts +0 -1
- package/src/core/hooks/useElementRect/useElementRect.ts +0 -13
- package/src/core/hooks/useElementSize.ts +0 -19
- package/src/core/hooks/useForwardedRef.ts +0 -19
- package/src/core/hooks/useGlobalKeyDown.ts +0 -24
- package/src/core/hooks/useIsomorphicEffect.ts +0 -7
- package/src/core/hooks/useMatchMedia.ts +0 -28
- package/src/core/hooks/useMediaIndex/__workshop__/index.ts +0 -14
- package/src/core/hooks/useMediaIndex/__workshop__/test.tsx +0 -14
- package/src/core/hooks/useMediaIndex/index.ts +0 -1
- package/src/core/hooks/useMediaIndex/useMediaIndex.test.tsx +0 -36
- package/src/core/hooks/useMediaIndex/useMediaIndex.ts +0 -102
- package/src/core/hooks/useMounted.ts +0 -15
- package/src/core/hooks/usePrefersDark.hydration.test.tsx +0 -56
- package/src/core/hooks/usePrefersDark.test.tsx +0 -19
- package/src/core/hooks/usePrefersDark.ts +0 -16
- package/src/core/hooks/usePrefersReducedMotion.hydration.test.tsx +0 -56
- package/src/core/hooks/usePrefersReducedMotion.test.tsx +0 -19
- package/src/core/hooks/usePrefersReducedMotion.ts +0 -16
- package/src/core/index.ts +0 -10
- package/src/core/lib/createGlobalScopedContext.ts +0 -36
- package/src/core/lib/globalScope.ts +0 -19
- package/src/core/lib/isRecord.ts +0 -3
- package/src/core/middleware/origin.ts +0 -47
- package/src/core/observers/elementSizeObserver.ts +0 -116
- package/src/core/observers/index.ts +0 -2
- package/src/core/observers/resizeObserver.ts +0 -9
- package/src/core/primitives/_selectable/index.ts +0 -1
- package/src/core/primitives/_selectable/selectable.tsx +0 -15
- package/src/core/primitives/_selectable/style.ts +0 -115
- package/src/core/primitives/avatar/__workshop__/asButton.tsx +0 -14
- package/src/core/primitives/avatar/__workshop__/index.ts +0 -29
- package/src/core/primitives/avatar/__workshop__/stack.tsx +0 -25
- package/src/core/primitives/avatar/__workshop__/withinButton.tsx +0 -63
- package/src/core/primitives/avatar/__workshop__/withinMenuItem.tsx +0 -70
- package/src/core/primitives/avatar/avatar.tsx +0 -200
- package/src/core/primitives/avatar/avatarCounter.tsx +0 -91
- package/src/core/primitives/avatar/avatarStack.tsx +0 -100
- package/src/core/primitives/avatar/index.ts +0 -4
- package/src/core/primitives/avatar/styles.ts +0 -167
- package/src/core/primitives/avatar/types.ts +0 -17
- package/src/core/primitives/badge/__workshop__/index.ts +0 -19
- package/src/core/primitives/badge/__workshop__/props.tsx +0 -30
- package/src/core/primitives/badge/__workshop__/tones.tsx +0 -21
- package/src/core/primitives/badge/badge.test.tsx +0 -16
- package/src/core/primitives/badge/badge.tsx +0 -70
- package/src/core/primitives/badge/index.ts +0 -1
- package/src/core/primitives/badge/styles.ts +0 -20
- package/src/core/primitives/badge/types.ts +0 -8
- package/src/core/primitives/box/__workshop__/asComponent.tsx +0 -31
- package/src/core/primitives/box/__workshop__/index.ts +0 -24
- package/src/core/primitives/box/__workshop__/props.tsx +0 -20
- package/src/core/primitives/box/__workshop__/responsive.tsx +0 -20
- package/src/core/primitives/box/box.test.tsx +0 -142
- package/src/core/primitives/box/box.tsx +0 -154
- package/src/core/primitives/box/index.ts +0 -1
- package/src/core/primitives/button/__workshop__/custom.tsx +0 -51
- package/src/core/primitives/button/__workshop__/customIcons.tsx +0 -30
- package/src/core/primitives/button/__workshop__/disabled.tsx +0 -74
- package/src/core/primitives/button/__workshop__/index.ts +0 -59
- package/src/core/primitives/button/__workshop__/labelUploadButton.tsx +0 -36
- package/src/core/primitives/button/__workshop__/mixedChildren.tsx +0 -12
- package/src/core/primitives/button/__workshop__/props.tsx +0 -55
- package/src/core/primitives/button/__workshop__/stacked.tsx +0 -58
- package/src/core/primitives/button/__workshop__/styled1.tsx +0 -17
- package/src/core/primitives/button/__workshop__/styled2.tsx +0 -19
- package/src/core/primitives/button/__workshop__/uploadButton.tsx +0 -28
- package/src/core/primitives/button/button.test.tsx +0 -82
- package/src/core/primitives/button/button.tsx +0 -210
- package/src/core/primitives/button/index.ts +0 -1
- package/src/core/primitives/button/styles.ts +0 -117
- package/src/core/primitives/card/__workshop__/allTones.tsx +0 -25
- package/src/core/primitives/card/__workshop__/asButton.tsx +0 -107
- package/src/core/primitives/card/__workshop__/asComponent.tsx +0 -27
- package/src/core/primitives/card/__workshop__/checkered.tsx +0 -26
- package/src/core/primitives/card/__workshop__/index.ts +0 -18
- package/src/core/primitives/card/__workshop__/interactive.tsx +0 -30
- package/src/core/primitives/card/__workshop__/listNavigation.tsx +0 -80
- package/src/core/primitives/card/__workshop__/props.tsx +0 -53
- package/src/core/primitives/card/__workshop__/selected.tsx +0 -94
- package/src/core/primitives/card/__workshop__/styled.tsx +0 -14
- package/src/core/primitives/card/card.tsx +0 -128
- package/src/core/primitives/card/index.ts +0 -2
- package/src/core/primitives/card/styles.ts +0 -147
- package/src/core/primitives/card/types.ts +0 -11
- package/src/core/primitives/checkbox/__workshop__/example.tsx +0 -25
- package/src/core/primitives/checkbox/__workshop__/index.ts +0 -13
- package/src/core/primitives/checkbox/__workshop__/props.tsx +0 -33
- package/src/core/primitives/checkbox/__workshop__/readOnly.tsx +0 -16
- package/src/core/primitives/checkbox/__workshop__/tones.tsx +0 -28
- package/src/core/primitives/checkbox/checkbox.tsx +0 -73
- package/src/core/primitives/checkbox/index.ts +0 -1
- package/src/core/primitives/checkbox/styles.ts +0 -128
- package/src/core/primitives/code/__workshop__/index.ts +0 -19
- package/src/core/primitives/code/__workshop__/opticalAlignment.tsx +0 -48
- package/src/core/primitives/code/__workshop__/props.tsx +0 -21
- package/src/core/primitives/code/code.tsx +0 -55
- package/src/core/primitives/code/index.ts +0 -1
- package/src/core/primitives/code/refractor.tsx +0 -20
- package/src/core/primitives/code/styles.ts +0 -77
- package/src/core/primitives/container/__workshop__/example.tsx +0 -20
- package/src/core/primitives/container/__workshop__/index.ts +0 -8
- package/src/core/primitives/container/container.tsx +0 -54
- package/src/core/primitives/container/index.ts +0 -2
- package/src/core/primitives/container/styles.ts +0 -23
- package/src/core/primitives/container/types.ts +0 -6
- package/src/core/primitives/flex/__workshop__/example.tsx +0 -34
- package/src/core/primitives/flex/__workshop__/gap.tsx +0 -25
- package/src/core/primitives/flex/__workshop__/index.ts +0 -11
- package/src/core/primitives/flex/flex.tsx +0 -64
- package/src/core/primitives/flex/index.ts +0 -1
- package/src/core/primitives/grid/__workshop__/index.ts +0 -10
- package/src/core/primitives/grid/__workshop__/responsive.tsx +0 -54
- package/src/core/primitives/grid/grid.test.tsx +0 -80
- package/src/core/primitives/grid/grid.tsx +0 -74
- package/src/core/primitives/grid/index.ts +0 -1
- package/src/core/primitives/heading/__workshop__/index.ts +0 -19
- package/src/core/primitives/heading/__workshop__/opticalAlignment.tsx +0 -54
- package/src/core/primitives/heading/__workshop__/plain.tsx +0 -32
- package/src/core/primitives/heading/heading.tsx +0 -88
- package/src/core/primitives/heading/index.ts +0 -1
- package/src/core/primitives/heading/styles.ts +0 -64
- package/src/core/primitives/heading/types.ts +0 -7
- package/src/core/primitives/index.ts +0 -27
- package/src/core/primitives/inline/__workshop__/index.ts +0 -8
- package/src/core/primitives/inline/__workshop__/plain.tsx +0 -27
- package/src/core/primitives/inline/index.ts +0 -1
- package/src/core/primitives/inline/inline.test.tsx +0 -51
- package/src/core/primitives/inline/inline.tsx +0 -63
- package/src/core/primitives/inline/styles.ts +0 -32
- package/src/core/primitives/inline/types.ts +0 -6
- package/src/core/primitives/kbd/__workshop__/index.ts +0 -8
- package/src/core/primitives/kbd/__workshop__/plain.tsx +0 -9
- package/src/core/primitives/kbd/index.ts +0 -1
- package/src/core/primitives/kbd/kbd.tsx +0 -69
- package/src/core/primitives/label/__workshop__/index.ts +0 -19
- package/src/core/primitives/label/__workshop__/opticalAlignment.tsx +0 -48
- package/src/core/primitives/label/__workshop__/plain.tsx +0 -24
- package/src/core/primitives/label/index.ts +0 -1
- package/src/core/primitives/label/label.tsx +0 -87
- package/src/core/primitives/label/styles.ts +0 -45
- package/src/core/primitives/popover/__workshop__/AlignedStory.tsx +0 -77
- package/src/core/primitives/popover/__workshop__/MarginsStory.tsx +0 -31
- package/src/core/primitives/popover/__workshop__/MatchReferenceWidthStory.tsx +0 -32
- package/src/core/primitives/popover/__workshop__/OpenOnMountStory.tsx +0 -11
- package/src/core/primitives/popover/__workshop__/PlainStory.tsx +0 -71
- package/src/core/primitives/popover/__workshop__/RecursiveStory.tsx +0 -71
- package/src/core/primitives/popover/__workshop__/SidePanelStory.tsx +0 -89
- package/src/core/primitives/popover/__workshop__/TestStory.tsx +0 -97
- package/src/core/primitives/popover/__workshop__/index.ts +0 -49
- package/src/core/primitives/popover/constants.ts +0 -22
- package/src/core/primitives/popover/floating-ui/size.ts +0 -99
- package/src/core/primitives/popover/helpers.ts +0 -31
- package/src/core/primitives/popover/index.ts +0 -2
- package/src/core/primitives/popover/popover.tsx +0 -511
- package/src/core/primitives/popover/popoverCard.tsx +0 -166
- package/src/core/primitives/popover/types.ts +0 -7
- package/src/core/primitives/radio/__workshop__/example.tsx +0 -63
- package/src/core/primitives/radio/__workshop__/index.ts +0 -11
- package/src/core/primitives/radio/__workshop__/plain.tsx +0 -28
- package/src/core/primitives/radio/index.ts +0 -1
- package/src/core/primitives/radio/radio.tsx +0 -51
- package/src/core/primitives/radio/styles.ts +0 -117
- package/src/core/primitives/select/__workshop__/index.ts +0 -11
- package/src/core/primitives/select/__workshop__/plain.tsx +0 -27
- package/src/core/primitives/select/__workshop__/readOnly.tsx +0 -23
- package/src/core/primitives/select/index.ts +0 -1
- package/src/core/primitives/select/select.test.tsx +0 -52
- package/src/core/primitives/select/select.tsx +0 -94
- package/src/core/primitives/select/styles.ts +0 -166
- package/src/core/primitives/spinner/__workshop__/Props.tsx +0 -15
- package/src/core/primitives/spinner/__workshop__/index.ts +0 -14
- package/src/core/primitives/spinner/index.ts +0 -1
- package/src/core/primitives/spinner/spinner.tsx +0 -46
- package/src/core/primitives/stack/__workshop__/index.ts +0 -14
- package/src/core/primitives/stack/__workshop__/plain.tsx +0 -32
- package/src/core/primitives/stack/index.ts +0 -1
- package/src/core/primitives/stack/stack.test.tsx +0 -57
- package/src/core/primitives/stack/stack.tsx +0 -57
- package/src/core/primitives/stack/styles.ts +0 -32
- package/src/core/primitives/switch/__workshop__/example.tsx +0 -23
- package/src/core/primitives/switch/__workshop__/index.ts +0 -11
- package/src/core/primitives/switch/__workshop__/props.tsx +0 -21
- package/src/core/primitives/switch/index.ts +0 -1
- package/src/core/primitives/switch/styles.ts +0 -173
- package/src/core/primitives/switch/switch.tsx +0 -68
- package/src/core/primitives/text/__workshop__/colored.tsx +0 -30
- package/src/core/primitives/text/__workshop__/example.tsx +0 -39
- package/src/core/primitives/text/__workshop__/index.ts +0 -24
- package/src/core/primitives/text/__workshop__/opticalAlignment.tsx +0 -48
- package/src/core/primitives/text/index.ts +0 -1
- package/src/core/primitives/text/styles.ts +0 -74
- package/src/core/primitives/text/text.tsx +0 -89
- package/src/core/primitives/textArea/__workshop__/index.ts +0 -14
- package/src/core/primitives/textArea/__workshop__/plain.tsx +0 -50
- package/src/core/primitives/textArea/index.ts +0 -1
- package/src/core/primitives/textArea/textArea.tsx +0 -120
- package/src/core/primitives/textInput/__workshop__/clearButton.tsx +0 -30
- package/src/core/primitives/textInput/__workshop__/customValidity.tsx +0 -17
- package/src/core/primitives/textInput/__workshop__/index.ts +0 -44
- package/src/core/primitives/textInput/__workshop__/plain.tsx +0 -98
- package/src/core/primitives/textInput/__workshop__/readOnly.tsx +0 -9
- package/src/core/primitives/textInput/__workshop__/states.tsx +0 -53
- package/src/core/primitives/textInput/__workshop__/tones.tsx +0 -277
- package/src/core/primitives/textInput/__workshop__/typed.tsx +0 -23
- package/src/core/primitives/textInput/index.ts +0 -1
- package/src/core/primitives/textInput/textInput.test.tsx +0 -48
- package/src/core/primitives/textInput/textInput.tsx +0 -395
- package/src/core/primitives/tooltip/__workshop__/customPortal.tsx +0 -102
- package/src/core/primitives/tooltip/__workshop__/index.ts +0 -29
- package/src/core/primitives/tooltip/__workshop__/overflowingBoundary.tsx +0 -74
- package/src/core/primitives/tooltip/__workshop__/props.tsx +0 -107
- package/src/core/primitives/tooltip/__workshop__/resizableBoundary.tsx +0 -86
- package/src/core/primitives/tooltip/constants.ts +0 -23
- package/src/core/primitives/tooltip/index.ts +0 -2
- package/src/core/primitives/tooltip/tooltip.test.tsx +0 -452
- package/src/core/primitives/tooltip/tooltip.tsx +0 -495
- package/src/core/primitives/tooltip/tooltipCard.tsx +0 -109
- package/src/core/primitives/tooltip/tooltipDelayGroup/index.ts +0 -4
- package/src/core/primitives/tooltip/tooltipDelayGroup/tooltipDelayGroupContext.tsx +0 -11
- package/src/core/primitives/tooltip/tooltipDelayGroup/tooltipDelayGroupProvider.tsx +0 -57
- package/src/core/primitives/tooltip/tooltipDelayGroup/types.ts +0 -10
- package/src/core/primitives/tooltip/tooltipDelayGroup/useTooltipDelayGroup.ts +0 -13
- package/src/core/primitives/types.ts +0 -179
- package/src/core/styles/border/borderStyle.ts +0 -56
- package/src/core/styles/border/index.ts +0 -2
- package/src/core/styles/border/types.ts +0 -10
- package/src/core/styles/box/boxStyle.ts +0 -69
- package/src/core/styles/box/index.ts +0 -2
- package/src/core/styles/box/types.ts +0 -11
- package/src/core/styles/card/_cardColorStyle.ts +0 -108
- package/src/core/styles/card/index.ts +0 -1
- package/src/core/styles/flex/flexItemStyle.ts +0 -27
- package/src/core/styles/flex/flexStyle.ts +0 -70
- package/src/core/styles/flex/index.ts +0 -3
- package/src/core/styles/flex/types.ts +0 -25
- package/src/core/styles/focusRing/index.ts +0 -25
- package/src/core/styles/font/codeFontStyle.ts +0 -13
- package/src/core/styles/font/headingFontStyle.ts +0 -13
- package/src/core/styles/font/index.ts +0 -6
- package/src/core/styles/font/labelFontStyle.ts +0 -13
- package/src/core/styles/font/responsiveFont.ts +0 -103
- package/src/core/styles/font/textAlignStyle.ts +0 -19
- package/src/core/styles/font/textFontStyle.ts +0 -13
- package/src/core/styles/font/types.ts +0 -35
- package/src/core/styles/grid/gridItemStyle.ts +0 -82
- package/src/core/styles/grid/gridStyle.ts +0 -108
- package/src/core/styles/grid/index.ts +0 -3
- package/src/core/styles/grid/types.ts +0 -37
- package/src/core/styles/helpers.ts +0 -72
- package/src/core/styles/index.ts +0 -5
- package/src/core/styles/input/index.ts +0 -2
- package/src/core/styles/input/responsiveInputPaddingStyle.ts +0 -78
- package/src/core/styles/input/textInputStyle.ts +0 -263
- package/src/core/styles/internal.ts +0 -12
- package/src/core/styles/margin/index.ts +0 -2
- package/src/core/styles/margin/marginStyle.ts +0 -21
- package/src/core/styles/margin/marginsStyle.test.ts +0 -33
- package/src/core/styles/margin/types.ts +0 -9
- package/src/core/styles/padding/index.ts +0 -2
- package/src/core/styles/padding/paddingStyle.test.ts +0 -33
- package/src/core/styles/padding/paddingStyle.ts +0 -21
- package/src/core/styles/padding/types.ts +0 -12
- package/src/core/styles/radius/index.ts +0 -2
- package/src/core/styles/radius/radiusStyle.ts +0 -23
- package/src/core/styles/radius/types.ts +0 -8
- package/src/core/styles/shadow/index.ts +0 -2
- package/src/core/styles/shadow/shadowStyle.ts +0 -29
- package/src/core/styles/shadow/types.ts +0 -6
- package/src/core/styles/types.ts +0 -8
- package/src/core/theme/__workshop__/build/Root.tsx +0 -367
- package/src/core/theme/__workshop__/build/helpers.ts +0 -46
- package/src/core/theme/__workshop__/build/story.tsx +0 -465
- package/src/core/theme/__workshop__/canvas.tsx +0 -352
- package/src/core/theme/__workshop__/color.tsx +0 -62
- package/src/core/theme/__workshop__/debug/story.tsx +0 -408
- package/src/core/theme/__workshop__/index.ts +0 -39
- package/src/core/theme/__workshop__/layer.tsx +0 -78
- package/src/core/theme/__workshop__/nestedProvider.tsx +0 -15
- package/src/core/theme/index.ts +0 -5
- package/src/core/theme/theme.test.tsx +0 -73
- package/src/core/theme/themeColorProvider.tsx +0 -29
- package/src/core/theme/themeContext.ts +0 -10
- package/src/core/theme/themeProvider.tsx +0 -62
- package/src/core/theme/types.ts +0 -12
- package/src/core/theme/useRootTheme.ts +0 -17
- package/src/core/theme/useTheme.ts +0 -16
- package/src/core/types/avatar.ts +0 -14
- package/src/core/types/badge.ts +0 -12
- package/src/core/types/box.ts +0 -19
- package/src/core/types/button.ts +0 -21
- package/src/core/types/card.ts +0 -6
- package/src/core/types/component.ts +0 -32
- package/src/core/types/dialog.ts +0 -4
- package/src/core/types/flex.ts +0 -30
- package/src/core/types/grid.ts +0 -14
- package/src/core/types/gridItem.ts +0 -29
- package/src/core/types/index.ts +0 -15
- package/src/core/types/placement.ts +0 -18
- package/src/core/types/popover.ts +0 -4
- package/src/core/types/radius.ts +0 -4
- package/src/core/types/selectable.ts +0 -6
- package/src/core/types/text.ts +0 -4
- package/src/core/utils/arrow/arrow.tsx +0 -123
- package/src/core/utils/arrow/cmds.ts +0 -91
- package/src/core/utils/arrow/index.ts +0 -1
- package/src/core/utils/boundaryElement/__workshop__/index.ts +0 -14
- package/src/core/utils/boundaryElement/__workshop__/plain.tsx +0 -19
- package/src/core/utils/boundaryElement/boundaryElement.test.tsx +0 -100
- package/src/core/utils/boundaryElement/boundaryElementContext.ts +0 -7
- package/src/core/utils/boundaryElement/boundaryElementProvider.tsx +0 -24
- package/src/core/utils/boundaryElement/index.ts +0 -3
- package/src/core/utils/boundaryElement/types.ts +0 -7
- package/src/core/utils/boundaryElement/useBoundaryElement.ts +0 -26
- package/src/core/utils/conditionalWrapper/conditionalWrapper.tsx +0 -21
- package/src/core/utils/conditionalWrapper/index.ts +0 -1
- package/src/core/utils/elementQuery/__workshop__/customMedia.tsx +0 -30
- package/src/core/utils/elementQuery/__workshop__/index.ts +0 -14
- package/src/core/utils/elementQuery/elementQuery.tsx +0 -53
- package/src/core/utils/elementQuery/helpers.ts +0 -27
- package/src/core/utils/elementQuery/index.ts +0 -1
- package/src/core/utils/errorBoundary.tsx +0 -48
- package/src/core/utils/getElementRef.ts +0 -26
- package/src/core/utils/index.ts +0 -9
- package/src/core/utils/layer/__workshop__/_debug.tsx +0 -17
- package/src/core/utils/layer/__workshop__/index.ts +0 -24
- package/src/core/utils/layer/__workshop__/multipleRoots.tsx +0 -49
- package/src/core/utils/layer/__workshop__/nested.tsx +0 -98
- package/src/core/utils/layer/__workshop__/responsiveZOffset.tsx +0 -13
- package/src/core/utils/layer/getLayerContext.test.ts +0 -30
- package/src/core/utils/layer/getLayerContext.ts +0 -21
- package/src/core/utils/layer/index.ts +0 -4
- package/src/core/utils/layer/layer.test.tsx +0 -108
- package/src/core/utils/layer/layer.tsx +0 -98
- package/src/core/utils/layer/layerContext.ts +0 -7
- package/src/core/utils/layer/layerProvider.tsx +0 -111
- package/src/core/utils/layer/types.ts +0 -9
- package/src/core/utils/layer/useLayer.ts +0 -26
- package/src/core/utils/portal/__workshop__/index.ts +0 -14
- package/src/core/utils/portal/__workshop__/named.tsx +0 -63
- package/src/core/utils/portal/index.ts +0 -4
- package/src/core/utils/portal/portal.test.tsx +0 -103
- package/src/core/utils/portal/portal.ts +0 -32
- package/src/core/utils/portal/portalContext.ts +0 -31
- package/src/core/utils/portal/portalProvider.tsx +0 -47
- package/src/core/utils/portal/types.ts +0 -9
- package/src/core/utils/portal/usePortal.ts +0 -25
- package/src/core/utils/spanWithTextOverflow.tsx +0 -10
- package/src/core/utils/srOnly/index.ts +0 -1
- package/src/core/utils/srOnly/srOnly.tsx +0 -36
- package/src/core/utils/virtualList/__workshop__/changingProps.tsx +0 -36
- package/src/core/utils/virtualList/__workshop__/elementScroll.tsx +0 -24
- package/src/core/utils/virtualList/__workshop__/index.ts +0 -29
- package/src/core/utils/virtualList/__workshop__/infiniteList.tsx +0 -48
- package/src/core/utils/virtualList/__workshop__/windowScrolll.tsx +0 -22
- package/src/core/utils/virtualList/index.ts +0 -1
- package/src/core/utils/virtualList/virtualList.tsx +0 -200
- package/src/theme/build/_deprecated/color/_selectable/createSelectableTones.ts +0 -76
- package/src/theme/build/_deprecated/color/_solid/createSolidTones.ts +0 -54
- package/src/theme/build/_deprecated/color/button/createButtonModes.ts +0 -22
- package/src/theme/build/_deprecated/color/button/createButtonTones.ts +0 -55
- package/src/theme/build/_deprecated/color/card/createCardStates.ts +0 -60
- package/src/theme/build/_deprecated/color/color.test.ts +0 -63
- package/src/theme/build/_deprecated/color/defaults.ts +0 -390
- package/src/theme/build/_deprecated/color/factory.ts +0 -142
- package/src/theme/build/_deprecated/color/index.ts +0 -1
- package/src/theme/build/_deprecated/color/input/createInputModes.ts +0 -81
- package/src/theme/build/_deprecated/color/muted/createMuted.ts +0 -54
- package/src/theme/build/_deprecated/color/spot/createSpot.ts +0 -20
- package/src/theme/build/buildColorTheme.test.ts +0 -183
- package/src/theme/build/buildColorTheme.ts +0 -499
- package/src/theme/build/buildTheme.test.ts +0 -87
- package/src/theme/build/buildTheme.ts +0 -36
- package/src/theme/build/colorToken/colorToken.ts +0 -19
- package/src/theme/build/colorToken/compileColorToken.ts +0 -21
- package/src/theme/build/colorToken/index.ts +0 -2
- package/src/theme/build/colorToken/types.ts +0 -6
- package/src/theme/build/index.ts +0 -3
- package/src/theme/build/lib/color-fns/blend/index.ts +0 -3
- package/src/theme/build/lib/color-fns/blend/mix.ts +0 -21
- package/src/theme/build/lib/color-fns/blend/multiply.ts +0 -18
- package/src/theme/build/lib/color-fns/blend/screen.ts +0 -18
- package/src/theme/build/lib/color-fns/color-fns.test.ts +0 -68
- package/src/theme/build/lib/color-fns/contrastRatio.ts +0 -31
- package/src/theme/build/lib/color-fns/convert.ts +0 -150
- package/src/theme/build/lib/color-fns/index.ts +0 -6
- package/src/theme/build/lib/color-fns/parse.ts +0 -60
- package/src/theme/build/lib/color-fns/rgba.ts +0 -10
- package/src/theme/build/lib/color-fns/types.ts +0 -28
- package/src/theme/build/lib/isRecord.ts +0 -3
- package/src/theme/build/lib/lazy.ts +0 -30
- package/src/theme/build/lib/utils.ts +0 -19
- package/src/theme/build/merge.ts +0 -26
- package/src/theme/build/mixThemeColor.test.ts +0 -21
- package/src/theme/build/mixThemeColor.ts +0 -53
- package/src/theme/build/renderColorTheme.ts +0 -532
- package/src/theme/build/renderColorValue.ts +0 -85
- package/src/theme/build/resolveColorTokens.ts +0 -268
- package/src/theme/build/theme.test.ts +0 -27
- package/src/theme/config/helpers.ts +0 -55
- package/src/theme/config/index.ts +0 -4
- package/src/theme/config/system.ts +0 -115
- package/src/theme/config/tokens/color/index.ts +0 -1
- package/src/theme/config/tokens/color/types.ts +0 -133
- package/src/theme/config/tokens/index.ts +0 -4
- package/src/theme/config/tokens/parseTokenKey.test.ts +0 -64
- package/src/theme/config/tokens/parseTokenKey.ts +0 -67
- package/src/theme/config/tokens/parseTokenValue.test.ts +0 -42
- package/src/theme/config/tokens/parseTokenValue.ts +0 -133
- package/src/theme/config/tokens/types.ts +0 -53
- package/src/theme/config/types.ts +0 -46
- package/src/theme/defaults/colorPalette.ts +0 -5
- package/src/theme/defaults/colorTokens.ts +0 -648
- package/src/theme/defaults/config.ts +0 -84
- package/src/theme/defaults/fonts.ts +0 -227
- package/src/theme/getScopedTheme.ts +0 -98
- package/src/theme/index.ts +0 -5
- package/src/theme/system/avatar.ts +0 -14
- package/src/theme/system/color/_constants.ts +0 -45
- package/src/theme/system/color/_helpers.ts +0 -24
- package/src/theme/system/color/_system.ts +0 -40
- package/src/theme/system/color/avatar.ts +0 -15
- package/src/theme/system/color/badge.ts +0 -16
- package/src/theme/system/color/button.ts +0 -17
- package/src/theme/system/color/color.ts +0 -34
- package/src/theme/system/color/index.ts +0 -13
- package/src/theme/system/color/input.ts +0 -25
- package/src/theme/system/color/kbd.ts +0 -8
- package/src/theme/system/color/selectable.ts +0 -12
- package/src/theme/system/color/shadow.ts +0 -7
- package/src/theme/system/color/state.ts +0 -43
- package/src/theme/system/color/syntax.ts +0 -41
- package/src/theme/system/css.ts +0 -14
- package/src/theme/system/focusRing.ts +0 -7
- package/src/theme/system/font.ts +0 -52
- package/src/theme/system/index.ts +0 -11
- package/src/theme/system/input.ts +0 -33
- package/src/theme/system/layer.ts +0 -8
- package/src/theme/system/shadow.ts +0 -19
- package/src/theme/system/styles.ts +0 -13
- package/src/theme/system/theme.ts +0 -138
- package/src/theme/system/v0/avatar.ts +0 -10
- package/src/theme/system/v0/color/_generic.ts +0 -35
- package/src/theme/system/v0/color/_system.ts +0 -17
- package/src/theme/system/v0/color/base.ts +0 -21
- package/src/theme/system/v0/color/button.ts +0 -42
- package/src/theme/system/v0/color/card.ts +0 -19
- package/src/theme/system/v0/color/color.ts +0 -40
- package/src/theme/system/v0/color/index.ts +0 -11
- package/src/theme/system/v0/color/input.ts +0 -34
- package/src/theme/system/v0/color/muted.ts +0 -26
- package/src/theme/system/v0/color/selectable.ts +0 -31
- package/src/theme/system/v0/color/solid.ts +0 -26
- package/src/theme/system/v0/color/spot.ts +0 -13
- package/src/theme/system/v0/index.ts +0 -3
- package/src/theme/system/v0/input.ts +0 -23
- package/src/theme/versioning/getTheme_v2.ts +0 -57
- package/src/theme/versioning/index.ts +0 -5
- package/src/theme/versioning/is_v0.ts +0 -6
- package/src/theme/versioning/is_v2.ts +0 -6
- package/src/theme/versioning/themeColor_v0_v2.ts +0 -243
- package/src/theme/versioning/themeColor_v2_v2_9.ts +0 -49
- package/src/theme/versioning/v0_v2.ts +0 -94
- package/src/theme/versioning/v2_v0.ts +0 -135
|
@@ -0,0 +1,4613 @@
|
|
|
1
|
+
import { tableFrom } from "../../schema/table.js";
|
|
2
|
+
//#region src/library/entries/data.entries.ts
|
|
3
|
+
var pic = (seed, alt, w = 1600, h = 1e3) => ({
|
|
4
|
+
url: `https://picsum.photos/seed/${seed}/${w}/${h}`,
|
|
5
|
+
alt,
|
|
6
|
+
name: `${seed}.jpg`,
|
|
7
|
+
width: w * 2,
|
|
8
|
+
height: h * 2
|
|
9
|
+
});
|
|
10
|
+
/** Key fields: the stable machine name other fields (formulas, sort, dependencies) point at. */
|
|
11
|
+
var KEY_PATTERN = "^[a-z][a-zA-Z0-9]*$";
|
|
12
|
+
var KEY_MESSAGE = "Use camelCase letters and numbers, like hoursPerRun.";
|
|
13
|
+
var ENTRIES = [
|
|
14
|
+
{
|
|
15
|
+
id: "data-table-specs",
|
|
16
|
+
label: "Spec table",
|
|
17
|
+
proves: "Typed columns · rows from manual / CSV / collection · default sort · caption + row headers for screen readers",
|
|
18
|
+
category: "Data & interactive",
|
|
19
|
+
tags: [
|
|
20
|
+
"repeater",
|
|
21
|
+
"conditional",
|
|
22
|
+
"validation",
|
|
23
|
+
"a11y"
|
|
24
|
+
],
|
|
25
|
+
brand: "Caldera",
|
|
26
|
+
previewLayout: "table",
|
|
27
|
+
schema: {
|
|
28
|
+
name: "dataTable",
|
|
29
|
+
title: "Data table",
|
|
30
|
+
kind: "block",
|
|
31
|
+
icon: "table",
|
|
32
|
+
category: "Data & interactive",
|
|
33
|
+
description: "Rows and typed columns. Sortable, searchable, readable on phones",
|
|
34
|
+
variants: [
|
|
35
|
+
{
|
|
36
|
+
id: "lined",
|
|
37
|
+
name: "Lined",
|
|
38
|
+
description: "Hairlines between rows"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "striped",
|
|
42
|
+
name: "Striped",
|
|
43
|
+
description: "Alternating row tint"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "cards",
|
|
47
|
+
name: "Cards on phones",
|
|
48
|
+
description: "Each row becomes a card under 640px"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
preview: {
|
|
52
|
+
title: "heading",
|
|
53
|
+
subtitle: "table.caption",
|
|
54
|
+
icon: "table"
|
|
55
|
+
},
|
|
56
|
+
groups: [
|
|
57
|
+
{
|
|
58
|
+
name: "content",
|
|
59
|
+
title: "Content",
|
|
60
|
+
default: true
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "data",
|
|
64
|
+
title: "Data",
|
|
65
|
+
icon: "database"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "display",
|
|
69
|
+
title: "Display"
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
fields: [
|
|
73
|
+
{
|
|
74
|
+
name: "eyebrow",
|
|
75
|
+
title: "Eyebrow",
|
|
76
|
+
type: "string",
|
|
77
|
+
group: "content",
|
|
78
|
+
validation: { max: 32 }
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "heading",
|
|
82
|
+
title: "Heading",
|
|
83
|
+
type: "string",
|
|
84
|
+
group: "content",
|
|
85
|
+
validation: {
|
|
86
|
+
required: true,
|
|
87
|
+
recommendedMax: 60
|
|
88
|
+
},
|
|
89
|
+
ai: true
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "source",
|
|
93
|
+
title: "Rows come from",
|
|
94
|
+
type: "select",
|
|
95
|
+
group: "data",
|
|
96
|
+
layout: "segmented",
|
|
97
|
+
initialValue: "manual",
|
|
98
|
+
options: [
|
|
99
|
+
{
|
|
100
|
+
value: "manual",
|
|
101
|
+
label: "Typed here"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
value: "csv",
|
|
105
|
+
label: "CSV file"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
value: "collection",
|
|
109
|
+
label: "Collection"
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "table",
|
|
115
|
+
title: "Table",
|
|
116
|
+
type: "table",
|
|
117
|
+
group: "data",
|
|
118
|
+
hidden: {
|
|
119
|
+
field: "source",
|
|
120
|
+
equals: "manual"
|
|
121
|
+
},
|
|
122
|
+
validation: { required: true },
|
|
123
|
+
table: {
|
|
124
|
+
maxColumns: 8,
|
|
125
|
+
maxRows: 60,
|
|
126
|
+
columnTypes: [
|
|
127
|
+
"text",
|
|
128
|
+
"number",
|
|
129
|
+
"money",
|
|
130
|
+
"boolean",
|
|
131
|
+
"rating",
|
|
132
|
+
"link",
|
|
133
|
+
"reference"
|
|
134
|
+
],
|
|
135
|
+
jsonLd: ["Dataset", "ItemList"],
|
|
136
|
+
settings: {
|
|
137
|
+
phones: [],
|
|
138
|
+
sort: true,
|
|
139
|
+
search: true
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "csvFile",
|
|
145
|
+
title: "CSV file",
|
|
146
|
+
type: "file",
|
|
147
|
+
group: "data",
|
|
148
|
+
hidden: {
|
|
149
|
+
field: "source",
|
|
150
|
+
equals: "csv"
|
|
151
|
+
},
|
|
152
|
+
validation: { required: true },
|
|
153
|
+
description: "First row = headers matching the column keys."
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "delimiter",
|
|
157
|
+
title: "Separator",
|
|
158
|
+
type: "select",
|
|
159
|
+
group: "data",
|
|
160
|
+
layout: "segmented",
|
|
161
|
+
hidden: {
|
|
162
|
+
field: "source",
|
|
163
|
+
equals: "csv"
|
|
164
|
+
},
|
|
165
|
+
initialValue: "comma",
|
|
166
|
+
options: [
|
|
167
|
+
{
|
|
168
|
+
value: "comma",
|
|
169
|
+
label: "Comma"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
value: "semicolon",
|
|
173
|
+
label: "Semicolon"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
value: "tab",
|
|
177
|
+
label: "Tab"
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: "collection",
|
|
183
|
+
title: "Collection",
|
|
184
|
+
type: "reference",
|
|
185
|
+
group: "data",
|
|
186
|
+
reference: { to: ["collection"] },
|
|
187
|
+
hidden: {
|
|
188
|
+
field: "source",
|
|
189
|
+
equals: "collection"
|
|
190
|
+
},
|
|
191
|
+
validation: { required: true },
|
|
192
|
+
description: "One row per entry."
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "defaultSort",
|
|
196
|
+
title: "Sort when the page loads",
|
|
197
|
+
type: "object",
|
|
198
|
+
group: "display",
|
|
199
|
+
icon: "arrows-sort",
|
|
200
|
+
fields: [{
|
|
201
|
+
name: "column",
|
|
202
|
+
title: "Column key",
|
|
203
|
+
type: "string",
|
|
204
|
+
validation: {
|
|
205
|
+
pattern: KEY_PATTERN,
|
|
206
|
+
patternMessage: KEY_MESSAGE
|
|
207
|
+
}
|
|
208
|
+
}, {
|
|
209
|
+
name: "direction",
|
|
210
|
+
title: "Direction",
|
|
211
|
+
type: "select",
|
|
212
|
+
layout: "segmented",
|
|
213
|
+
initialValue: "asc",
|
|
214
|
+
options: [{
|
|
215
|
+
value: "asc",
|
|
216
|
+
label: "A → Z"
|
|
217
|
+
}, {
|
|
218
|
+
value: "desc",
|
|
219
|
+
label: "Z → A"
|
|
220
|
+
}]
|
|
221
|
+
}]
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: "pageSize",
|
|
225
|
+
title: "Rows per page",
|
|
226
|
+
type: "number",
|
|
227
|
+
group: "display",
|
|
228
|
+
numeric: {
|
|
229
|
+
min: 5,
|
|
230
|
+
max: 100,
|
|
231
|
+
step: 5
|
|
232
|
+
},
|
|
233
|
+
description: "Leave empty to show every row."
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
value: {
|
|
238
|
+
_variant: "cards",
|
|
239
|
+
eyebrow: "Sizing",
|
|
240
|
+
heading: "Find the skillet that fits your fire.",
|
|
241
|
+
source: "manual",
|
|
242
|
+
table: tableFrom("Caldera skillets and grates compared by diameter, weight, material, induction support and price.", [
|
|
243
|
+
{
|
|
244
|
+
_key: "model",
|
|
245
|
+
label: "Model"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
_key: "diameter",
|
|
249
|
+
label: "Diameter",
|
|
250
|
+
type: "number",
|
|
251
|
+
unit: "in",
|
|
252
|
+
sortable: true
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
_key: "weight",
|
|
256
|
+
label: "Weight",
|
|
257
|
+
type: "number",
|
|
258
|
+
unit: "lb",
|
|
259
|
+
sortable: true,
|
|
260
|
+
description: "Without the lid"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
_key: "material",
|
|
264
|
+
label: "Material",
|
|
265
|
+
sortable: true
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
_key: "induction",
|
|
269
|
+
label: "Induction",
|
|
270
|
+
type: "boolean",
|
|
271
|
+
trueLabel: "Works on induction",
|
|
272
|
+
falseLabel: "Not for induction"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
_key: "price",
|
|
276
|
+
label: "Price",
|
|
277
|
+
type: "money",
|
|
278
|
+
currency: "USD",
|
|
279
|
+
sortable: true
|
|
280
|
+
}
|
|
281
|
+
], [
|
|
282
|
+
[
|
|
283
|
+
"Ridge Skillet 8\"",
|
|
284
|
+
8,
|
|
285
|
+
3.9,
|
|
286
|
+
"Cast iron",
|
|
287
|
+
true,
|
|
288
|
+
55
|
|
289
|
+
],
|
|
290
|
+
{
|
|
291
|
+
cells: [
|
|
292
|
+
"Ridge Skillet 10\"",
|
|
293
|
+
10,
|
|
294
|
+
5.2,
|
|
295
|
+
"Cast iron",
|
|
296
|
+
true,
|
|
297
|
+
{
|
|
298
|
+
value: 65,
|
|
299
|
+
note: "f1"
|
|
300
|
+
}
|
|
301
|
+
],
|
|
302
|
+
highlight: true
|
|
303
|
+
},
|
|
304
|
+
[
|
|
305
|
+
"Ridge Skillet 12\"",
|
|
306
|
+
12,
|
|
307
|
+
7.4,
|
|
308
|
+
"Cast iron",
|
|
309
|
+
true,
|
|
310
|
+
85
|
|
311
|
+
],
|
|
312
|
+
[
|
|
313
|
+
"Carbon Pan 11\"",
|
|
314
|
+
11,
|
|
315
|
+
3.1,
|
|
316
|
+
"Carbon steel",
|
|
317
|
+
true,
|
|
318
|
+
79
|
|
319
|
+
],
|
|
320
|
+
[
|
|
321
|
+
"Cable Fold Grate",
|
|
322
|
+
16,
|
|
323
|
+
4.6,
|
|
324
|
+
"Stainless steel",
|
|
325
|
+
{
|
|
326
|
+
value: false,
|
|
327
|
+
note: "f2"
|
|
328
|
+
},
|
|
329
|
+
189
|
|
330
|
+
]
|
|
331
|
+
], {
|
|
332
|
+
id: "tbl_skillet_sizes",
|
|
333
|
+
headerColumn: true,
|
|
334
|
+
notes: "Weights are without the lid. Every piece ships pre-seasoned and carries the lifetime warranty.",
|
|
335
|
+
footnotes: [{
|
|
336
|
+
_key: "f1",
|
|
337
|
+
text: "Our bestseller."
|
|
338
|
+
}, {
|
|
339
|
+
_key: "f2",
|
|
340
|
+
text: "Open fire and gas only."
|
|
341
|
+
}],
|
|
342
|
+
design: {
|
|
343
|
+
responsive: "cards",
|
|
344
|
+
sticky: true,
|
|
345
|
+
hideCaption: true
|
|
346
|
+
},
|
|
347
|
+
jsonLd: {
|
|
348
|
+
type: "ItemList",
|
|
349
|
+
axis: "rows"
|
|
350
|
+
}
|
|
351
|
+
}),
|
|
352
|
+
delimiter: "comma",
|
|
353
|
+
defaultSort: {
|
|
354
|
+
column: "diameter",
|
|
355
|
+
direction: "asc"
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
notes: {
|
|
359
|
+
why: "Columns are DATA, not schema: an editor adds a 'Capacity' column without a developer, so the rows live in ONE `table` field (CPO D42): typed, keyed columns (Number in lb, Money in USD, Yes / no with its words), rows whose cells are keyed by column key (reorder-safe), the first column as row headers, caption + notes + footnotes, and design (cards on phones, sticky header). Source is a segmented select so typed rows, a CSV file and a collection are mutually exclusive branches. Sorting is a per-column flag; the filter box is `table.filterable`.",
|
|
360
|
+
sanity: "Sanity's native Portable Text table (Studio v6.6, July 2026) is rows › cells › Portable Text with a `headerRows` count — no column types or keys; the older @sanity/table is rows of string cells. Our `table` keeps typed, keyed columns, row headers and a CSV/collection source Sanity would need a custom input for.",
|
|
361
|
+
htmlSignals: [
|
|
362
|
+
"<table> with <thead><th scope=\"col\"> and more than 2 rows — or a CSS grid with role=\"table\" / role=\"row\"",
|
|
363
|
+
"Header cells with sort affordances: aria-sort, buttons inside <th>, ▲▼ icons",
|
|
364
|
+
"Numeric columns right-aligned or with units (in, lb, $, %) in every cell of a column",
|
|
365
|
+
"A <caption> or a heading directly above the table",
|
|
366
|
+
"Under 640px rows turn into stacked cards with label: value pairs (data-label attributes)"
|
|
367
|
+
],
|
|
368
|
+
mcp: "Read the <table> with the table detector (docs/TABLES.md): <caption> (or the nearest heading) → `table.caption` (write one if missing — never empty); <thead> th → columns in order with stable `_key`s (camel-cased header text); units stripped into `unit` ('Weight (lb)' → Number, unit lb); per-column type from every body cell (numbers → Number, currency symbol → Money + code, ✓/✗/Yes/No → Yes / no, ★ or x/5 → Rating, <a> in every cell → Link); a first column of th[scope=row] (or an empty top-left header) → `headerColumn: true`; footnote marks (sup, *) → `footnotes` + the cell's `note`. More than 60 rows or a feed → `source: csv` and emit the CSV. Zebra striping and card layouts are design (`design.striped`, `design.responsive`), not fields.",
|
|
369
|
+
pitfalls: [
|
|
370
|
+
"RESOLVED (D42): the `table` type types cells by their column (a cell may carry its own type), keys cells by column `_key`, and edits as a grid with paste from Sheets / Excel / CSV — no more drill-in per cell.",
|
|
371
|
+
"SCHEMA GAP: `defaultSort.column` must match a column `_key` of `table`; nothing checks it yet (gap 3, options from a sibling field).",
|
|
372
|
+
"Past ~60 rows, keep the data in CSV or a collection; the grid stays usable but the page gets heavy.",
|
|
373
|
+
"Do not model 'striped' or 'compact' as booleans — they are variants."
|
|
374
|
+
]
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
id: "table-comparison",
|
|
379
|
+
label: "Table",
|
|
380
|
+
proves: "The `table` type (D42): caption · row headers · typed columns (Number, Money, Yes / no, Rating, Reference) · row groups · per-row and per-cell types · footnotes · notes · ItemList JSON-LD",
|
|
381
|
+
category: "Data & interactive",
|
|
382
|
+
tags: [
|
|
383
|
+
"validation",
|
|
384
|
+
"a11y",
|
|
385
|
+
"seo-structured",
|
|
386
|
+
"references",
|
|
387
|
+
"commerce"
|
|
388
|
+
],
|
|
389
|
+
brand: "Caldera",
|
|
390
|
+
previewLayout: "table",
|
|
391
|
+
schema: {
|
|
392
|
+
name: "tableBlock",
|
|
393
|
+
title: "Table",
|
|
394
|
+
kind: "block",
|
|
395
|
+
icon: "table",
|
|
396
|
+
category: "Data & interactive",
|
|
397
|
+
description: "A real data table: comparisons, specs, pricing, reports",
|
|
398
|
+
preview: {
|
|
399
|
+
title: "heading",
|
|
400
|
+
subtitle: "table.caption",
|
|
401
|
+
icon: "table"
|
|
402
|
+
},
|
|
403
|
+
fields: [
|
|
404
|
+
{
|
|
405
|
+
name: "eyebrow",
|
|
406
|
+
title: "Eyebrow",
|
|
407
|
+
type: "string",
|
|
408
|
+
validation: { max: 32 }
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: "heading",
|
|
412
|
+
title: "Heading",
|
|
413
|
+
type: "string",
|
|
414
|
+
validation: { recommendedMax: 60 },
|
|
415
|
+
ai: true
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
name: "intro",
|
|
419
|
+
title: "Intro",
|
|
420
|
+
type: "text",
|
|
421
|
+
rows: 2,
|
|
422
|
+
validation: { recommendedMax: 200 }
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
name: "table",
|
|
426
|
+
title: "Table",
|
|
427
|
+
type: "table",
|
|
428
|
+
validation: { required: true },
|
|
429
|
+
table: {
|
|
430
|
+
jsonLd: [
|
|
431
|
+
"ItemList",
|
|
432
|
+
"Dataset",
|
|
433
|
+
"Offers"
|
|
434
|
+
],
|
|
435
|
+
settings: {
|
|
436
|
+
sort: true,
|
|
437
|
+
search: true
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
]
|
|
442
|
+
},
|
|
443
|
+
value: {
|
|
444
|
+
eyebrow: "Field notes",
|
|
445
|
+
heading: "Which pan for which fire?",
|
|
446
|
+
intro: "Weights from our own scale, heat-up times over one chimney of lump charcoal.",
|
|
447
|
+
table: tableFrom("Caldera pans compared: weight, heat-up time, lid, price and rating", [
|
|
448
|
+
{
|
|
449
|
+
_key: "pan",
|
|
450
|
+
label: "Pan",
|
|
451
|
+
type: "reference",
|
|
452
|
+
refTo: ["col_products"]
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
_key: "weight",
|
|
456
|
+
label: "Weight",
|
|
457
|
+
type: "number",
|
|
458
|
+
unit: "kg",
|
|
459
|
+
sortable: true
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
_key: "heat",
|
|
463
|
+
label: "Heats in",
|
|
464
|
+
type: "number",
|
|
465
|
+
unit: "min",
|
|
466
|
+
sortable: true,
|
|
467
|
+
description: "Time to 200 °C over a full chimney of lump charcoal"
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
_key: "lid",
|
|
471
|
+
label: "Lid",
|
|
472
|
+
type: "boolean",
|
|
473
|
+
trueLabel: "Lid included",
|
|
474
|
+
falseLabel: "No lid"
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
_key: "price",
|
|
478
|
+
label: "Price",
|
|
479
|
+
type: "money",
|
|
480
|
+
currency: "USD",
|
|
481
|
+
sortable: true
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
_key: "rating",
|
|
485
|
+
label: "Rating",
|
|
486
|
+
type: "rating",
|
|
487
|
+
sortable: true
|
|
488
|
+
}
|
|
489
|
+
], [
|
|
490
|
+
{
|
|
491
|
+
cells: [
|
|
492
|
+
{
|
|
493
|
+
ref: "en_p3",
|
|
494
|
+
label: "Ridge Skillet 10\"",
|
|
495
|
+
href: "/products/ridge-skillet-10"
|
|
496
|
+
},
|
|
497
|
+
2.4,
|
|
498
|
+
7,
|
|
499
|
+
false,
|
|
500
|
+
72,
|
|
501
|
+
4.7
|
|
502
|
+
],
|
|
503
|
+
group: "pans"
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
cells: [
|
|
507
|
+
{
|
|
508
|
+
ref: "en_p2",
|
|
509
|
+
label: "Ember Dutch Oven 5.5qt",
|
|
510
|
+
href: "/products/ember-dutch-oven"
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
value: 6.2,
|
|
514
|
+
note: "f1"
|
|
515
|
+
},
|
|
516
|
+
9,
|
|
517
|
+
true,
|
|
518
|
+
165,
|
|
519
|
+
4.9
|
|
520
|
+
],
|
|
521
|
+
group: "pans",
|
|
522
|
+
highlight: true
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
cells: [
|
|
526
|
+
{
|
|
527
|
+
value: "Carbon Steel Pan 10\"",
|
|
528
|
+
type: "text"
|
|
529
|
+
},
|
|
530
|
+
1.5,
|
|
531
|
+
3,
|
|
532
|
+
false,
|
|
533
|
+
75,
|
|
534
|
+
4.6
|
|
535
|
+
],
|
|
536
|
+
group: "pans"
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
cells: [
|
|
540
|
+
{
|
|
541
|
+
ref: "en_p1",
|
|
542
|
+
label: "Cable Fold Grill",
|
|
543
|
+
href: "/products/cable-fold"
|
|
544
|
+
},
|
|
545
|
+
1.1,
|
|
546
|
+
{
|
|
547
|
+
value: "Heats with the fire",
|
|
548
|
+
type: "text"
|
|
549
|
+
},
|
|
550
|
+
false,
|
|
551
|
+
89,
|
|
552
|
+
4.8
|
|
553
|
+
],
|
|
554
|
+
group: "grates"
|
|
555
|
+
}
|
|
556
|
+
], {
|
|
557
|
+
id: "tbl_kit_pans",
|
|
558
|
+
headerColumn: true,
|
|
559
|
+
rowGroups: [{
|
|
560
|
+
_key: "pans",
|
|
561
|
+
label: "Pans"
|
|
562
|
+
}, {
|
|
563
|
+
_key: "grates",
|
|
564
|
+
label: "Grates"
|
|
565
|
+
}],
|
|
566
|
+
notes: "Weights on a kitchen scale; heat-up over one full chimney of lump charcoal, 2025 season.",
|
|
567
|
+
footnotes: [{
|
|
568
|
+
_key: "f1",
|
|
569
|
+
text: "With the lid, which doubles as a skillet."
|
|
570
|
+
}],
|
|
571
|
+
design: {
|
|
572
|
+
responsive: "stack",
|
|
573
|
+
hideCaption: true
|
|
574
|
+
},
|
|
575
|
+
jsonLd: {
|
|
576
|
+
type: "ItemList",
|
|
577
|
+
axis: "rows"
|
|
578
|
+
}
|
|
579
|
+
})
|
|
580
|
+
},
|
|
581
|
+
notes: {
|
|
582
|
+
why: "The reference entry for the `table` type (CPO D42). The section is a heading + intro + ONE `table` field: columns are typed and keyed (Number in kg, Money in USD, Yes / no with its own words, Rating, a Reference column that links each row to its product), cells are keyed by column so a reorder never misaligns, rows sit in row groups, the first column is the row header. One odd value ('Heats with the fire') is a cell with its own type; the highlighted row is a flag, not a color typed into content. ItemList JSON-LD is opted in because the table IS the comparison on the page.",
|
|
583
|
+
sanity: "Sanity's native Portable Text table (v6.6) would store rows › cells › blocks with `headerRows: 1` — no column types, keys, groups or references; `@sanity/table` is string cells only.",
|
|
584
|
+
htmlSignals: [
|
|
585
|
+
"<table> with <caption>, <thead><th scope=col>, body <th scope=row>",
|
|
586
|
+
"Units repeated down a column (kg, min, $) and ✓/– icons with hidden words",
|
|
587
|
+
"Full-width rows (colspan) or several <tbody> acting as group headings",
|
|
588
|
+
"Footnote marks (sup, *) tied to a numbered list under the table"
|
|
589
|
+
],
|
|
590
|
+
mcp: "Run the table detector (docs/brain/tables.md): caption from <caption> or the heading above (set design.hideCaption when the heading says it); header cells → columns with keys and types inferred from every body cell; first-column th → headerColumn; colspan / tbody headings → rowGroups; ✓/✗ → Yes / no; links to known entries → Reference; footnote marks → footnotes + cell.note; text under the table → notes. JSON-LD only when the table is the page's offer.",
|
|
591
|
+
pitfalls: [
|
|
592
|
+
"Never merge body cells; repeat the value.",
|
|
593
|
+
"A blank cell in a yes/no column is 'Not included'; a blank cell anywhere else is 'No value'.",
|
|
594
|
+
"Don't retype units in cells — the column carries them."
|
|
595
|
+
]
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
id: "chart-attrition",
|
|
600
|
+
label: "Chart with annotations",
|
|
601
|
+
proves: "Chart type → series → data points (3 levels) · annotations · text alternative required · data from CSV or URL",
|
|
602
|
+
category: "Data & interactive",
|
|
603
|
+
tags: [
|
|
604
|
+
"repeater",
|
|
605
|
+
"conditional",
|
|
606
|
+
"a11y",
|
|
607
|
+
"validation"
|
|
608
|
+
],
|
|
609
|
+
brand: "Betterwork",
|
|
610
|
+
previewLayout: "media",
|
|
611
|
+
schema: {
|
|
612
|
+
name: "chartBlock",
|
|
613
|
+
title: "Chart",
|
|
614
|
+
kind: "block",
|
|
615
|
+
icon: "chart-line",
|
|
616
|
+
category: "Data & interactive",
|
|
617
|
+
description: "Line, bar, area or donut chart with a written takeaway",
|
|
618
|
+
variants: [{
|
|
619
|
+
id: "card",
|
|
620
|
+
name: "In a card"
|
|
621
|
+
}, {
|
|
622
|
+
id: "bare",
|
|
623
|
+
name: "Full width"
|
|
624
|
+
}],
|
|
625
|
+
preview: {
|
|
626
|
+
title: "heading",
|
|
627
|
+
subtitle: "summary",
|
|
628
|
+
icon: "chart-line"
|
|
629
|
+
},
|
|
630
|
+
groups: [
|
|
631
|
+
{
|
|
632
|
+
name: "content",
|
|
633
|
+
title: "Content",
|
|
634
|
+
default: true
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
name: "data",
|
|
638
|
+
title: "Data",
|
|
639
|
+
icon: "database"
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
name: "display",
|
|
643
|
+
title: "Display"
|
|
644
|
+
}
|
|
645
|
+
],
|
|
646
|
+
fields: [
|
|
647
|
+
{
|
|
648
|
+
name: "eyebrow",
|
|
649
|
+
title: "Eyebrow",
|
|
650
|
+
type: "string",
|
|
651
|
+
group: "content",
|
|
652
|
+
validation: { max: 32 }
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
name: "heading",
|
|
656
|
+
title: "Heading",
|
|
657
|
+
type: "string",
|
|
658
|
+
group: "content",
|
|
659
|
+
validation: {
|
|
660
|
+
required: true,
|
|
661
|
+
recommendedMax: 70
|
|
662
|
+
},
|
|
663
|
+
ai: true
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
name: "summary",
|
|
667
|
+
title: "Takeaway",
|
|
668
|
+
type: "text",
|
|
669
|
+
rows: 2,
|
|
670
|
+
group: "content",
|
|
671
|
+
validation: {
|
|
672
|
+
required: true,
|
|
673
|
+
max: 240
|
|
674
|
+
},
|
|
675
|
+
ai: true,
|
|
676
|
+
description: "Read by screen readers instead of the chart."
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
name: "chartType",
|
|
680
|
+
title: "Chart",
|
|
681
|
+
type: "select",
|
|
682
|
+
group: "content",
|
|
683
|
+
layout: "cards",
|
|
684
|
+
initialValue: "line",
|
|
685
|
+
options: [
|
|
686
|
+
{
|
|
687
|
+
value: "line",
|
|
688
|
+
label: "Line",
|
|
689
|
+
icon: "chart-line",
|
|
690
|
+
description: "Change over time"
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
value: "bar",
|
|
694
|
+
label: "Bar",
|
|
695
|
+
icon: "chart-bar",
|
|
696
|
+
description: "Compare amounts"
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
value: "area",
|
|
700
|
+
label: "Area",
|
|
701
|
+
icon: "chart-area",
|
|
702
|
+
description: "Totals over time"
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
value: "donut",
|
|
706
|
+
label: "Donut",
|
|
707
|
+
icon: "chart-donut",
|
|
708
|
+
description: "Parts of a whole, 2–5 slices"
|
|
709
|
+
}
|
|
710
|
+
]
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
name: "isStacked",
|
|
714
|
+
title: "Stack the series",
|
|
715
|
+
type: "boolean",
|
|
716
|
+
group: "content",
|
|
717
|
+
hidden: {
|
|
718
|
+
field: "chartType",
|
|
719
|
+
equals: ["bar", "area"]
|
|
720
|
+
}
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
name: "source",
|
|
724
|
+
title: "Data comes from",
|
|
725
|
+
type: "select",
|
|
726
|
+
group: "data",
|
|
727
|
+
layout: "segmented",
|
|
728
|
+
initialValue: "manual",
|
|
729
|
+
options: [
|
|
730
|
+
{
|
|
731
|
+
value: "manual",
|
|
732
|
+
label: "Typed here"
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
value: "csv",
|
|
736
|
+
label: "CSV file"
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
value: "url",
|
|
740
|
+
label: "JSON URL"
|
|
741
|
+
}
|
|
742
|
+
]
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
name: "series",
|
|
746
|
+
title: "Series",
|
|
747
|
+
type: "array",
|
|
748
|
+
group: "data",
|
|
749
|
+
itemNoun: "series",
|
|
750
|
+
hidden: {
|
|
751
|
+
field: "source",
|
|
752
|
+
equals: "manual"
|
|
753
|
+
},
|
|
754
|
+
validation: {
|
|
755
|
+
min: 1,
|
|
756
|
+
max: 5
|
|
757
|
+
},
|
|
758
|
+
of: [{
|
|
759
|
+
name: "seriesItem",
|
|
760
|
+
title: "Series",
|
|
761
|
+
type: "object",
|
|
762
|
+
icon: "chart-dots",
|
|
763
|
+
preview: {
|
|
764
|
+
title: "name",
|
|
765
|
+
subtitle: "style",
|
|
766
|
+
fallback: "Untitled series"
|
|
767
|
+
},
|
|
768
|
+
fields: [
|
|
769
|
+
{
|
|
770
|
+
name: "name",
|
|
771
|
+
title: "Name",
|
|
772
|
+
type: "string",
|
|
773
|
+
validation: {
|
|
774
|
+
required: true,
|
|
775
|
+
max: 40
|
|
776
|
+
}
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
name: "color",
|
|
780
|
+
title: "Color",
|
|
781
|
+
type: "color",
|
|
782
|
+
description: "Leave empty to use the brand palette in order."
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
name: "style",
|
|
786
|
+
title: "Line",
|
|
787
|
+
type: "select",
|
|
788
|
+
layout: "segmented",
|
|
789
|
+
initialValue: "solid",
|
|
790
|
+
options: [{
|
|
791
|
+
value: "solid",
|
|
792
|
+
label: "Solid"
|
|
793
|
+
}, {
|
|
794
|
+
value: "dashed",
|
|
795
|
+
label: "Dashed"
|
|
796
|
+
}]
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
name: "points",
|
|
800
|
+
title: "Data points",
|
|
801
|
+
type: "array",
|
|
802
|
+
itemNoun: "point",
|
|
803
|
+
openMode: "inline",
|
|
804
|
+
validation: {
|
|
805
|
+
min: 2,
|
|
806
|
+
max: 24
|
|
807
|
+
},
|
|
808
|
+
of: [{
|
|
809
|
+
name: "point",
|
|
810
|
+
title: "Point",
|
|
811
|
+
type: "object",
|
|
812
|
+
icon: "point",
|
|
813
|
+
preview: {
|
|
814
|
+
title: "x",
|
|
815
|
+
subtitle: "y",
|
|
816
|
+
fallback: "Untitled point"
|
|
817
|
+
},
|
|
818
|
+
fields: [
|
|
819
|
+
{
|
|
820
|
+
name: "x",
|
|
821
|
+
title: "Label",
|
|
822
|
+
type: "string",
|
|
823
|
+
validation: {
|
|
824
|
+
required: true,
|
|
825
|
+
max: 16
|
|
826
|
+
},
|
|
827
|
+
placeholder: "Q1 2026"
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
name: "y",
|
|
831
|
+
title: "Value",
|
|
832
|
+
type: "number",
|
|
833
|
+
validation: { required: true }
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
name: "label",
|
|
837
|
+
title: "Callout",
|
|
838
|
+
type: "string",
|
|
839
|
+
validation: { max: 24 }
|
|
840
|
+
}
|
|
841
|
+
]
|
|
842
|
+
}]
|
|
843
|
+
}
|
|
844
|
+
]
|
|
845
|
+
}]
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
name: "csvFile",
|
|
849
|
+
title: "CSV file",
|
|
850
|
+
type: "file",
|
|
851
|
+
group: "data",
|
|
852
|
+
hidden: {
|
|
853
|
+
field: "source",
|
|
854
|
+
equals: "csv"
|
|
855
|
+
},
|
|
856
|
+
validation: { required: true },
|
|
857
|
+
description: "First column = labels, one column per series."
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
name: "dataUrl",
|
|
861
|
+
title: "JSON URL",
|
|
862
|
+
type: "url",
|
|
863
|
+
group: "data",
|
|
864
|
+
hidden: {
|
|
865
|
+
field: "source",
|
|
866
|
+
equals: "url"
|
|
867
|
+
},
|
|
868
|
+
validation: {
|
|
869
|
+
required: true,
|
|
870
|
+
pattern: "^https://",
|
|
871
|
+
patternMessage: "Use an https:// address, like https://your-site.com."
|
|
872
|
+
},
|
|
873
|
+
access: { edit: "schema.manage" },
|
|
874
|
+
lockReason: "The endpoint and its shape are set by a developer."
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
name: "refresh",
|
|
878
|
+
title: "Refresh",
|
|
879
|
+
type: "select",
|
|
880
|
+
group: "data",
|
|
881
|
+
hidden: {
|
|
882
|
+
field: "source",
|
|
883
|
+
equals: "url"
|
|
884
|
+
},
|
|
885
|
+
options: [
|
|
886
|
+
{
|
|
887
|
+
value: "build",
|
|
888
|
+
label: "On publish"
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
value: "hourly",
|
|
892
|
+
label: "Every hour"
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
value: "live",
|
|
896
|
+
label: "Every page view"
|
|
897
|
+
}
|
|
898
|
+
]
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
name: "axes",
|
|
902
|
+
title: "Axes",
|
|
903
|
+
type: "object",
|
|
904
|
+
group: "display",
|
|
905
|
+
icon: "axis-x",
|
|
906
|
+
openMode: "drill",
|
|
907
|
+
fields: [
|
|
908
|
+
{
|
|
909
|
+
name: "xTitle",
|
|
910
|
+
title: "Horizontal axis",
|
|
911
|
+
type: "string",
|
|
912
|
+
validation: { max: 32 }
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
name: "yTitle",
|
|
916
|
+
title: "Vertical axis",
|
|
917
|
+
type: "string",
|
|
918
|
+
validation: { max: 32 }
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
name: "format",
|
|
922
|
+
title: "Number format",
|
|
923
|
+
type: "select",
|
|
924
|
+
initialValue: "number",
|
|
925
|
+
options: [
|
|
926
|
+
{
|
|
927
|
+
value: "number",
|
|
928
|
+
label: "1,234"
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
value: "percent",
|
|
932
|
+
label: "12%"
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
value: "currency",
|
|
936
|
+
label: "$1,234"
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
value: "compact",
|
|
940
|
+
label: "1.2k"
|
|
941
|
+
}
|
|
942
|
+
]
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
name: "yMin",
|
|
946
|
+
title: "Start at",
|
|
947
|
+
type: "number",
|
|
948
|
+
description: "Leave empty to start at zero."
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
name: "yMax",
|
|
952
|
+
title: "End at",
|
|
953
|
+
type: "number"
|
|
954
|
+
}
|
|
955
|
+
]
|
|
956
|
+
},
|
|
957
|
+
{
|
|
958
|
+
name: "annotations",
|
|
959
|
+
title: "Annotations",
|
|
960
|
+
type: "array",
|
|
961
|
+
group: "display",
|
|
962
|
+
itemNoun: "annotation",
|
|
963
|
+
validation: { max: 4 },
|
|
964
|
+
of: [{
|
|
965
|
+
name: "annotation",
|
|
966
|
+
title: "Annotation",
|
|
967
|
+
type: "object",
|
|
968
|
+
icon: "message-circle",
|
|
969
|
+
preview: {
|
|
970
|
+
title: "label",
|
|
971
|
+
subtitle: "at",
|
|
972
|
+
fallback: "Untitled note"
|
|
973
|
+
},
|
|
974
|
+
fields: [
|
|
975
|
+
{
|
|
976
|
+
name: "at",
|
|
977
|
+
title: "At label",
|
|
978
|
+
type: "string",
|
|
979
|
+
validation: {
|
|
980
|
+
required: true,
|
|
981
|
+
max: 16
|
|
982
|
+
},
|
|
983
|
+
description: "Must match a point label, e.g. Q3 2025."
|
|
984
|
+
},
|
|
985
|
+
{
|
|
986
|
+
name: "label",
|
|
987
|
+
title: "Note",
|
|
988
|
+
type: "string",
|
|
989
|
+
validation: {
|
|
990
|
+
required: true,
|
|
991
|
+
max: 40
|
|
992
|
+
}
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
name: "detail",
|
|
996
|
+
title: "Detail",
|
|
997
|
+
type: "text",
|
|
998
|
+
rows: 2,
|
|
999
|
+
validation: { max: 160 }
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
name: "tone",
|
|
1003
|
+
title: "Tone",
|
|
1004
|
+
type: "select",
|
|
1005
|
+
layout: "segmented",
|
|
1006
|
+
initialValue: "neutral",
|
|
1007
|
+
options: [
|
|
1008
|
+
{
|
|
1009
|
+
value: "neutral",
|
|
1010
|
+
label: "Neutral"
|
|
1011
|
+
},
|
|
1012
|
+
{
|
|
1013
|
+
value: "positive",
|
|
1014
|
+
label: "Good"
|
|
1015
|
+
},
|
|
1016
|
+
{
|
|
1017
|
+
value: "negative",
|
|
1018
|
+
label: "Bad"
|
|
1019
|
+
}
|
|
1020
|
+
]
|
|
1021
|
+
}
|
|
1022
|
+
]
|
|
1023
|
+
}]
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
name: "showTable",
|
|
1027
|
+
title: "Show the numbers as a table",
|
|
1028
|
+
type: "boolean",
|
|
1029
|
+
group: "display",
|
|
1030
|
+
initialValue: true
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
name: "sourceLabel",
|
|
1034
|
+
title: "Source",
|
|
1035
|
+
type: "string",
|
|
1036
|
+
group: "display",
|
|
1037
|
+
validation: { max: 80 },
|
|
1038
|
+
placeholder: "Betterwork benchmark, 1,240 companies"
|
|
1039
|
+
},
|
|
1040
|
+
{
|
|
1041
|
+
name: "sourceLink",
|
|
1042
|
+
title: "Source link",
|
|
1043
|
+
type: "link",
|
|
1044
|
+
group: "display"
|
|
1045
|
+
}
|
|
1046
|
+
]
|
|
1047
|
+
},
|
|
1048
|
+
value: {
|
|
1049
|
+
_variant: "card",
|
|
1050
|
+
eyebrow: "2026 benchmark",
|
|
1051
|
+
heading: "Teams on Betterwork lose fewer people every quarter.",
|
|
1052
|
+
summary: "Voluntary attrition at Betterwork customers fell from 4.1% to 2.6% a quarter over six quarters, while the industry median stayed near 4%.",
|
|
1053
|
+
chartType: "line",
|
|
1054
|
+
isStacked: false,
|
|
1055
|
+
source: "manual",
|
|
1056
|
+
series: [{
|
|
1057
|
+
_key: "s1",
|
|
1058
|
+
name: "Betterwork customers",
|
|
1059
|
+
color: "#4F46E5",
|
|
1060
|
+
style: "solid",
|
|
1061
|
+
points: [
|
|
1062
|
+
{
|
|
1063
|
+
_key: "p1",
|
|
1064
|
+
x: "Q1 2025",
|
|
1065
|
+
y: 4.1
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
_key: "p2",
|
|
1069
|
+
x: "Q2 2025",
|
|
1070
|
+
y: 3.8
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
_key: "p3",
|
|
1074
|
+
x: "Q3 2025",
|
|
1075
|
+
y: 3.4
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
_key: "p4",
|
|
1079
|
+
x: "Q4 2025",
|
|
1080
|
+
y: 3
|
|
1081
|
+
},
|
|
1082
|
+
{
|
|
1083
|
+
_key: "p5",
|
|
1084
|
+
x: "Q1 2026",
|
|
1085
|
+
y: 2.8
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
_key: "p6",
|
|
1089
|
+
x: "Q2 2026",
|
|
1090
|
+
y: 2.6,
|
|
1091
|
+
label: "2.6%"
|
|
1092
|
+
}
|
|
1093
|
+
]
|
|
1094
|
+
}, {
|
|
1095
|
+
_key: "s2",
|
|
1096
|
+
name: "Industry median",
|
|
1097
|
+
color: "#9CA3AF",
|
|
1098
|
+
style: "dashed",
|
|
1099
|
+
points: [
|
|
1100
|
+
{
|
|
1101
|
+
_key: "p7",
|
|
1102
|
+
x: "Q1 2025",
|
|
1103
|
+
y: 4.2
|
|
1104
|
+
},
|
|
1105
|
+
{
|
|
1106
|
+
_key: "p8",
|
|
1107
|
+
x: "Q2 2025",
|
|
1108
|
+
y: 4
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
_key: "p9",
|
|
1112
|
+
x: "Q3 2025",
|
|
1113
|
+
y: 4.1
|
|
1114
|
+
},
|
|
1115
|
+
{
|
|
1116
|
+
_key: "p10",
|
|
1117
|
+
x: "Q4 2025",
|
|
1118
|
+
y: 3.9
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
_key: "p11",
|
|
1122
|
+
x: "Q1 2026",
|
|
1123
|
+
y: 4
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
_key: "p12",
|
|
1127
|
+
x: "Q2 2026",
|
|
1128
|
+
y: 3.9
|
|
1129
|
+
}
|
|
1130
|
+
]
|
|
1131
|
+
}],
|
|
1132
|
+
refresh: "build",
|
|
1133
|
+
axes: {
|
|
1134
|
+
xTitle: "Quarter",
|
|
1135
|
+
yTitle: "Voluntary attrition",
|
|
1136
|
+
format: "percent",
|
|
1137
|
+
yMin: 0,
|
|
1138
|
+
yMax: 5
|
|
1139
|
+
},
|
|
1140
|
+
annotations: [{
|
|
1141
|
+
_key: "a1",
|
|
1142
|
+
at: "Q3 2025",
|
|
1143
|
+
label: "Stay interviews launched",
|
|
1144
|
+
detail: "Managers get a prompt to talk with anyone whose engagement score drops two weeks in a row.",
|
|
1145
|
+
tone: "positive"
|
|
1146
|
+
}],
|
|
1147
|
+
showTable: true,
|
|
1148
|
+
sourceLabel: "Betterwork benchmark, 1,240 companies, 2025–2026",
|
|
1149
|
+
sourceLink: {
|
|
1150
|
+
type: "page",
|
|
1151
|
+
href: "/research/attrition-2026",
|
|
1152
|
+
title: "Attrition benchmark 2026"
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1155
|
+
notes: {
|
|
1156
|
+
why: "A chart is three levels of data (chart → series → points) plus presentation (axes, annotations) — so data and display are separate groups and points open inline (two primitives each) to avoid a drill per number. The written takeaway is REQUIRED because the drawing is inaccessible; the optional data table is the second fallback. Annotations reference a point by its label, not by index, so reordering points never moves a note.",
|
|
1157
|
+
sanity: "Sanity: a 'chart' object with chartType + series[] { name, color, points[] { x, y } } and annotations[]; most teams give up and store a JSON blob or a Datawrapper embed. Our version keeps every number editable and validated.",
|
|
1158
|
+
htmlSignals: [
|
|
1159
|
+
"<svg> with many <path>/<rect> elements and axis tick <text> nodes, or a <canvas> with a chart library global (Chart.js, Recharts, Highcharts, ECharts)",
|
|
1160
|
+
"A legend list of colored swatches + series names",
|
|
1161
|
+
"Inline JSON in a <script> (data-chart, __NEXT_DATA__ series) or a data-src attribute pointing at a .csv/.json",
|
|
1162
|
+
"Figure + figcaption with 'Source:' text"
|
|
1163
|
+
],
|
|
1164
|
+
mcp: "Never rasterise the chart. Recover the data first: (1) inline JSON / data attributes / library config objects; (2) the hidden accessible table many chart libraries render; (3) only as a last resort read axis ticks + bar heights and mark the entry for human review. Map each dataset to a `series` item and each label/value pair to a `point`. If there are more than 24 points per series or the data comes from a URL, use `source: csv|url` and emit the file. Write `summary` from the figcaption or by stating the trend in one sentence — it is required. Map 'Source: …' to sourceLabel/sourceLink. Library-specific styling (tooltips, gridlines, animation) stays in code; only color per series is content.",
|
|
1165
|
+
pitfalls: [
|
|
1166
|
+
"SCHEMA GAP: no grid/table editor for numeric data — 2 series × 12 points is 24 inline rows. A `table` or `dataset` field type (paste from a spreadsheet) is needed.",
|
|
1167
|
+
"SCHEMA GAP: `annotation.at` must equal an existing point label (cross-array reference) — the language can't express 'one of the x values in series', so it is a free string.",
|
|
1168
|
+
"SCHEMA GAP: no constraint that donut charts have exactly one series with 2–5 points — validation is per field, never conditional on another field's value.",
|
|
1169
|
+
"Color picker accepts any hex; the series palette should be restricted to brand chart tokens for contrast (see section-theme-tokens).",
|
|
1170
|
+
"Bar charts with a non-zero `yMin` mislead; the help text warns but nothing prevents it."
|
|
1171
|
+
]
|
|
1172
|
+
}
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
id: "roi-calculator",
|
|
1176
|
+
label: "ROI calculator",
|
|
1177
|
+
proves: "Typed inputs (slider · number · select) → formula outputs · conditional control settings · locked formulas",
|
|
1178
|
+
category: "Data & interactive",
|
|
1179
|
+
tags: [
|
|
1180
|
+
"form",
|
|
1181
|
+
"repeater",
|
|
1182
|
+
"conditional",
|
|
1183
|
+
"validation"
|
|
1184
|
+
],
|
|
1185
|
+
brand: "Betterwork",
|
|
1186
|
+
previewLayout: "form",
|
|
1187
|
+
schema: {
|
|
1188
|
+
name: "roiCalculator",
|
|
1189
|
+
title: "ROI calculator",
|
|
1190
|
+
kind: "block",
|
|
1191
|
+
icon: "calculator",
|
|
1192
|
+
category: "Data & interactive",
|
|
1193
|
+
description: "Inputs on the left, live results on the right",
|
|
1194
|
+
variants: [{
|
|
1195
|
+
id: "split",
|
|
1196
|
+
name: "Side by side"
|
|
1197
|
+
}, {
|
|
1198
|
+
id: "stacked",
|
|
1199
|
+
name: "Stacked"
|
|
1200
|
+
}],
|
|
1201
|
+
preview: {
|
|
1202
|
+
title: "heading",
|
|
1203
|
+
subtitle: "intro",
|
|
1204
|
+
icon: "calculator"
|
|
1205
|
+
},
|
|
1206
|
+
groups: [
|
|
1207
|
+
{
|
|
1208
|
+
name: "content",
|
|
1209
|
+
title: "Content",
|
|
1210
|
+
default: true
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
name: "logic",
|
|
1214
|
+
title: "Inputs & formulas",
|
|
1215
|
+
icon: "math-function"
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
name: "settings",
|
|
1219
|
+
title: "Settings"
|
|
1220
|
+
}
|
|
1221
|
+
],
|
|
1222
|
+
fields: [
|
|
1223
|
+
{
|
|
1224
|
+
name: "heading",
|
|
1225
|
+
title: "Heading",
|
|
1226
|
+
type: "string",
|
|
1227
|
+
group: "content",
|
|
1228
|
+
validation: {
|
|
1229
|
+
required: true,
|
|
1230
|
+
recommendedMax: 60
|
|
1231
|
+
},
|
|
1232
|
+
ai: true
|
|
1233
|
+
},
|
|
1234
|
+
{
|
|
1235
|
+
name: "intro",
|
|
1236
|
+
title: "Intro",
|
|
1237
|
+
type: "text",
|
|
1238
|
+
rows: 2,
|
|
1239
|
+
group: "content",
|
|
1240
|
+
validation: { recommendedMax: 180 },
|
|
1241
|
+
ai: true
|
|
1242
|
+
},
|
|
1243
|
+
{
|
|
1244
|
+
name: "inputs",
|
|
1245
|
+
title: "Inputs",
|
|
1246
|
+
type: "array",
|
|
1247
|
+
group: "logic",
|
|
1248
|
+
itemNoun: "input",
|
|
1249
|
+
validation: {
|
|
1250
|
+
min: 1,
|
|
1251
|
+
max: 6
|
|
1252
|
+
},
|
|
1253
|
+
of: [{
|
|
1254
|
+
name: "input",
|
|
1255
|
+
title: "Input",
|
|
1256
|
+
type: "object",
|
|
1257
|
+
icon: "adjustments-horizontal",
|
|
1258
|
+
preview: {
|
|
1259
|
+
title: "label",
|
|
1260
|
+
subtitle: "key",
|
|
1261
|
+
fallback: "Untitled input"
|
|
1262
|
+
},
|
|
1263
|
+
fields: [
|
|
1264
|
+
{
|
|
1265
|
+
name: "label",
|
|
1266
|
+
title: "Question",
|
|
1267
|
+
type: "string",
|
|
1268
|
+
validation: {
|
|
1269
|
+
required: true,
|
|
1270
|
+
max: 60
|
|
1271
|
+
},
|
|
1272
|
+
placeholder: "How many people do you pay?"
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
name: "key",
|
|
1276
|
+
title: "Key",
|
|
1277
|
+
type: "string",
|
|
1278
|
+
validation: {
|
|
1279
|
+
required: true,
|
|
1280
|
+
pattern: KEY_PATTERN,
|
|
1281
|
+
patternMessage: KEY_MESSAGE,
|
|
1282
|
+
unique: true
|
|
1283
|
+
},
|
|
1284
|
+
description: "The name formulas use, e.g. employees."
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
name: "control",
|
|
1288
|
+
title: "Control",
|
|
1289
|
+
type: "select",
|
|
1290
|
+
layout: "segmented",
|
|
1291
|
+
initialValue: "slider",
|
|
1292
|
+
options: [
|
|
1293
|
+
{
|
|
1294
|
+
value: "slider",
|
|
1295
|
+
label: "Slider"
|
|
1296
|
+
},
|
|
1297
|
+
{
|
|
1298
|
+
value: "number",
|
|
1299
|
+
label: "Number"
|
|
1300
|
+
},
|
|
1301
|
+
{
|
|
1302
|
+
value: "select",
|
|
1303
|
+
label: "Choice"
|
|
1304
|
+
}
|
|
1305
|
+
]
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
name: "min",
|
|
1309
|
+
title: "Minimum",
|
|
1310
|
+
type: "number",
|
|
1311
|
+
hidden: {
|
|
1312
|
+
field: "control",
|
|
1313
|
+
equals: ["slider", "number"]
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
{
|
|
1317
|
+
name: "max",
|
|
1318
|
+
title: "Maximum",
|
|
1319
|
+
type: "number",
|
|
1320
|
+
hidden: {
|
|
1321
|
+
field: "control",
|
|
1322
|
+
equals: ["slider", "number"]
|
|
1323
|
+
}
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
name: "step",
|
|
1327
|
+
title: "Step",
|
|
1328
|
+
type: "number",
|
|
1329
|
+
hidden: {
|
|
1330
|
+
field: "control",
|
|
1331
|
+
equals: ["slider", "number"]
|
|
1332
|
+
}
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
name: "unit",
|
|
1336
|
+
title: "Unit",
|
|
1337
|
+
type: "string",
|
|
1338
|
+
validation: { max: 10 },
|
|
1339
|
+
hidden: {
|
|
1340
|
+
field: "control",
|
|
1341
|
+
equals: ["slider", "number"]
|
|
1342
|
+
},
|
|
1343
|
+
placeholder: "people"
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
name: "choices",
|
|
1347
|
+
title: "Choices",
|
|
1348
|
+
type: "array",
|
|
1349
|
+
itemNoun: "choice",
|
|
1350
|
+
openMode: "inline",
|
|
1351
|
+
hidden: {
|
|
1352
|
+
field: "control",
|
|
1353
|
+
equals: "select"
|
|
1354
|
+
},
|
|
1355
|
+
validation: {
|
|
1356
|
+
min: 2,
|
|
1357
|
+
max: 6
|
|
1358
|
+
},
|
|
1359
|
+
of: [{
|
|
1360
|
+
name: "choice",
|
|
1361
|
+
title: "Choice",
|
|
1362
|
+
type: "object",
|
|
1363
|
+
icon: "circle-dot",
|
|
1364
|
+
preview: {
|
|
1365
|
+
title: "label",
|
|
1366
|
+
subtitle: "value",
|
|
1367
|
+
fallback: "Untitled choice"
|
|
1368
|
+
},
|
|
1369
|
+
fields: [{
|
|
1370
|
+
name: "label",
|
|
1371
|
+
title: "Label",
|
|
1372
|
+
type: "string",
|
|
1373
|
+
validation: {
|
|
1374
|
+
required: true,
|
|
1375
|
+
max: 32
|
|
1376
|
+
}
|
|
1377
|
+
}, {
|
|
1378
|
+
name: "value",
|
|
1379
|
+
title: "Value",
|
|
1380
|
+
type: "number",
|
|
1381
|
+
validation: { required: true }
|
|
1382
|
+
}]
|
|
1383
|
+
}]
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
name: "defaultValue",
|
|
1387
|
+
title: "Starts at",
|
|
1388
|
+
type: "number",
|
|
1389
|
+
validation: { required: true }
|
|
1390
|
+
},
|
|
1391
|
+
{
|
|
1392
|
+
name: "help",
|
|
1393
|
+
title: "Help",
|
|
1394
|
+
type: "string",
|
|
1395
|
+
validation: { max: 90 }
|
|
1396
|
+
}
|
|
1397
|
+
]
|
|
1398
|
+
}]
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
name: "outputs",
|
|
1402
|
+
title: "Results",
|
|
1403
|
+
type: "array",
|
|
1404
|
+
group: "logic",
|
|
1405
|
+
itemNoun: "result",
|
|
1406
|
+
validation: {
|
|
1407
|
+
min: 1,
|
|
1408
|
+
max: 4
|
|
1409
|
+
},
|
|
1410
|
+
of: [{
|
|
1411
|
+
name: "output",
|
|
1412
|
+
title: "Result",
|
|
1413
|
+
type: "object",
|
|
1414
|
+
icon: "equal",
|
|
1415
|
+
preview: {
|
|
1416
|
+
title: "label",
|
|
1417
|
+
subtitle: "formula",
|
|
1418
|
+
fallback: "Untitled result"
|
|
1419
|
+
},
|
|
1420
|
+
fields: [
|
|
1421
|
+
{
|
|
1422
|
+
name: "label",
|
|
1423
|
+
title: "Label",
|
|
1424
|
+
type: "string",
|
|
1425
|
+
validation: {
|
|
1426
|
+
required: true,
|
|
1427
|
+
max: 48
|
|
1428
|
+
}
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
name: "key",
|
|
1432
|
+
title: "Key",
|
|
1433
|
+
type: "string",
|
|
1434
|
+
validation: {
|
|
1435
|
+
required: true,
|
|
1436
|
+
pattern: KEY_PATTERN,
|
|
1437
|
+
patternMessage: KEY_MESSAGE,
|
|
1438
|
+
unique: true
|
|
1439
|
+
},
|
|
1440
|
+
description: "Later results can use it, e.g. costSaved."
|
|
1441
|
+
},
|
|
1442
|
+
{
|
|
1443
|
+
name: "formula",
|
|
1444
|
+
title: "Formula",
|
|
1445
|
+
type: "code",
|
|
1446
|
+
validation: { required: true },
|
|
1447
|
+
access: { edit: "schema.manage" },
|
|
1448
|
+
lockReason: "Formulas are checked by finance before they ship.",
|
|
1449
|
+
description: "Arithmetic on input and result keys, e.g. runs * hoursPerRun * 0.72."
|
|
1450
|
+
},
|
|
1451
|
+
{
|
|
1452
|
+
name: "format",
|
|
1453
|
+
title: "Format",
|
|
1454
|
+
type: "select",
|
|
1455
|
+
layout: "segmented",
|
|
1456
|
+
initialValue: "number",
|
|
1457
|
+
options: [
|
|
1458
|
+
{
|
|
1459
|
+
value: "number",
|
|
1460
|
+
label: "1,234"
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
value: "currency",
|
|
1464
|
+
label: "$"
|
|
1465
|
+
},
|
|
1466
|
+
{
|
|
1467
|
+
value: "percent",
|
|
1468
|
+
label: "%"
|
|
1469
|
+
},
|
|
1470
|
+
{
|
|
1471
|
+
value: "hours",
|
|
1472
|
+
label: "Hours"
|
|
1473
|
+
}
|
|
1474
|
+
]
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
name: "decimals",
|
|
1478
|
+
title: "Decimals",
|
|
1479
|
+
type: "number",
|
|
1480
|
+
numeric: {
|
|
1481
|
+
min: 0,
|
|
1482
|
+
max: 2,
|
|
1483
|
+
step: 1
|
|
1484
|
+
},
|
|
1485
|
+
initialValue: 0
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
name: "isHighlighted",
|
|
1489
|
+
title: "Headline result",
|
|
1490
|
+
type: "boolean"
|
|
1491
|
+
}
|
|
1492
|
+
]
|
|
1493
|
+
}]
|
|
1494
|
+
},
|
|
1495
|
+
{
|
|
1496
|
+
name: "assumptions",
|
|
1497
|
+
title: "How we calculate",
|
|
1498
|
+
type: "richtext",
|
|
1499
|
+
group: "content",
|
|
1500
|
+
description: "Shown behind a \"How we calculate\" link."
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
name: "button",
|
|
1504
|
+
title: "Button",
|
|
1505
|
+
type: "button",
|
|
1506
|
+
group: "content"
|
|
1507
|
+
},
|
|
1508
|
+
{
|
|
1509
|
+
name: "disclaimer",
|
|
1510
|
+
title: "Small print",
|
|
1511
|
+
type: "text",
|
|
1512
|
+
rows: 2,
|
|
1513
|
+
group: "content",
|
|
1514
|
+
validation: { max: 240 }
|
|
1515
|
+
},
|
|
1516
|
+
{
|
|
1517
|
+
name: "allowEmailResults",
|
|
1518
|
+
title: "Offer to email the results",
|
|
1519
|
+
type: "boolean",
|
|
1520
|
+
group: "settings",
|
|
1521
|
+
description: "Submissions arrive in the chosen form."
|
|
1522
|
+
},
|
|
1523
|
+
{
|
|
1524
|
+
name: "form",
|
|
1525
|
+
title: "Send to form",
|
|
1526
|
+
type: "reference",
|
|
1527
|
+
group: "settings",
|
|
1528
|
+
reference: { to: ["form"] },
|
|
1529
|
+
hidden: {
|
|
1530
|
+
field: "allowEmailResults",
|
|
1531
|
+
equals: true
|
|
1532
|
+
},
|
|
1533
|
+
validation: { required: true }
|
|
1534
|
+
}
|
|
1535
|
+
]
|
|
1536
|
+
},
|
|
1537
|
+
value: {
|
|
1538
|
+
_variant: "split",
|
|
1539
|
+
heading: "See how many hours payroll gives back.",
|
|
1540
|
+
intro: "Move the sliders to match your team. Results update as you go and assume the average Betterwork customer's time savings.",
|
|
1541
|
+
inputs: [
|
|
1542
|
+
{
|
|
1543
|
+
_key: "i1",
|
|
1544
|
+
label: "How many people do you pay?",
|
|
1545
|
+
key: "employees",
|
|
1546
|
+
control: "slider",
|
|
1547
|
+
min: 10,
|
|
1548
|
+
max: 2e3,
|
|
1549
|
+
step: 10,
|
|
1550
|
+
unit: "people",
|
|
1551
|
+
defaultValue: 120
|
|
1552
|
+
},
|
|
1553
|
+
{
|
|
1554
|
+
_key: "i2",
|
|
1555
|
+
label: "How often do you run payroll?",
|
|
1556
|
+
key: "runs",
|
|
1557
|
+
control: "select",
|
|
1558
|
+
choices: [
|
|
1559
|
+
{
|
|
1560
|
+
_key: "ch1",
|
|
1561
|
+
label: "Monthly",
|
|
1562
|
+
value: 12
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
_key: "ch2",
|
|
1566
|
+
label: "Twice a month",
|
|
1567
|
+
value: 24
|
|
1568
|
+
},
|
|
1569
|
+
{
|
|
1570
|
+
_key: "ch3",
|
|
1571
|
+
label: "Every two weeks",
|
|
1572
|
+
value: 26
|
|
1573
|
+
},
|
|
1574
|
+
{
|
|
1575
|
+
_key: "ch4",
|
|
1576
|
+
label: "Weekly",
|
|
1577
|
+
value: 52
|
|
1578
|
+
}
|
|
1579
|
+
],
|
|
1580
|
+
defaultValue: 26
|
|
1581
|
+
},
|
|
1582
|
+
{
|
|
1583
|
+
_key: "i3",
|
|
1584
|
+
label: "Hours spent on each run today",
|
|
1585
|
+
key: "hoursPerRun",
|
|
1586
|
+
control: "number",
|
|
1587
|
+
min: 1,
|
|
1588
|
+
max: 40,
|
|
1589
|
+
step: .5,
|
|
1590
|
+
unit: "hours",
|
|
1591
|
+
defaultValue: 6,
|
|
1592
|
+
help: "Include approvals, fixes and the bank file."
|
|
1593
|
+
},
|
|
1594
|
+
{
|
|
1595
|
+
_key: "i4",
|
|
1596
|
+
label: "Loaded hourly cost of your payroll lead",
|
|
1597
|
+
key: "hourlyCost",
|
|
1598
|
+
control: "number",
|
|
1599
|
+
min: 15,
|
|
1600
|
+
max: 250,
|
|
1601
|
+
step: 1,
|
|
1602
|
+
unit: "USD",
|
|
1603
|
+
defaultValue: 48
|
|
1604
|
+
}
|
|
1605
|
+
],
|
|
1606
|
+
outputs: [
|
|
1607
|
+
{
|
|
1608
|
+
_key: "o1",
|
|
1609
|
+
label: "Hours back each year",
|
|
1610
|
+
key: "hoursSaved",
|
|
1611
|
+
formula: "runs * hoursPerRun * 0.72",
|
|
1612
|
+
format: "hours",
|
|
1613
|
+
decimals: 0,
|
|
1614
|
+
isHighlighted: true
|
|
1615
|
+
},
|
|
1616
|
+
{
|
|
1617
|
+
_key: "o2",
|
|
1618
|
+
label: "Saved each year",
|
|
1619
|
+
key: "costSaved",
|
|
1620
|
+
formula: "hoursSaved * hourlyCost",
|
|
1621
|
+
format: "currency",
|
|
1622
|
+
decimals: 0
|
|
1623
|
+
},
|
|
1624
|
+
{
|
|
1625
|
+
_key: "o3",
|
|
1626
|
+
label: "Saved per employee",
|
|
1627
|
+
key: "perEmployee",
|
|
1628
|
+
formula: "costSaved / employees",
|
|
1629
|
+
format: "currency",
|
|
1630
|
+
decimals: 2
|
|
1631
|
+
}
|
|
1632
|
+
],
|
|
1633
|
+
assumptions: "<p>We use a <strong>72% reduction</strong> in time per payroll run, the median across 1,240 Betterwork customers in 2025. Hourly cost should include benefits and overhead, usually 1.3× salary.</p>",
|
|
1634
|
+
button: {
|
|
1635
|
+
label: "Book a payroll demo",
|
|
1636
|
+
href: "/demo?topic=payroll",
|
|
1637
|
+
type: "page",
|
|
1638
|
+
title: "Book a demo",
|
|
1639
|
+
style: "primary",
|
|
1640
|
+
arrow: true
|
|
1641
|
+
},
|
|
1642
|
+
disclaimer: "Estimates only. Your results depend on pay complexity, number of states and approval steps.",
|
|
1643
|
+
allowEmailResults: true,
|
|
1644
|
+
form: "form_contact"
|
|
1645
|
+
},
|
|
1646
|
+
notes: {
|
|
1647
|
+
why: "A calculator is a tiny program: named inputs, formulas over those names, formatted outputs. Inputs and outputs are arrays with a stable `key` so formulas survive relabelling and reordering. Control-specific settings (range for sliders/numbers, choices for select) are conditional on `control`, so an editor never sees a 'Step' for a dropdown. Formulas are developer-locked: marketers can change copy and defaults, not maths. Email capture is a reference to a real form, never a second hidden form model.",
|
|
1648
|
+
sanity: "Sanity: 'calculator' object with inputs[] and outputs[] where formula is a string or a code field (@sanity/code-input); conditional fields via `hidden: ({parent}) => parent.control !== 'select'`. Same gaps: no expression type.",
|
|
1649
|
+
htmlSignals: [
|
|
1650
|
+
"<input type=\"range\"> / <input type=\"number\"> / <select> with JS listeners and no form submit",
|
|
1651
|
+
"Output elements (<output>, aria-live regions) whose text changes on input",
|
|
1652
|
+
"Inline JS with arithmetic on input values (e.g. value * 0.72) or a data-formula attribute",
|
|
1653
|
+
"'How we calculate' / 'Assumptions' disclosure link near the result"
|
|
1654
|
+
],
|
|
1655
|
+
mcp: "Each interactive control → an `input`: label from <label>, key from the input's name/id camel-cased, control from the element type (range → slider), min/max/step/value from attributes, <option>s → choices with numeric values. Read the bundled JS for the arithmetic: each computed output → an `output` with its formula rewritten in terms of input keys (constants inline, e.g. 0.72), format from the rendered text ($ → currency, % → percent). If the formula calls functions, lookup tables or tiered pricing you can't express as arithmetic, keep the calculator as a code component and expose ONLY copy fields — flag it, never approximate a formula. Assumptions text → richtext; a submit/email step → `allowEmailResults` + reference to a created form.",
|
|
1656
|
+
pitfalls: [
|
|
1657
|
+
"SCHEMA GAP: no formula/expression field type. `code` stores a string; nothing parses it, checks that it only references existing input/output keys, or detects cycles (costSaved → perEmployee → costSaved).",
|
|
1658
|
+
"SCHEMA GAP: `unique` on input/output keys is not enforced, and keys must be unique ACROSS two arrays (inputs and outputs share one namespace) — no cross-array uniqueness exists.",
|
|
1659
|
+
"SCHEMA GAP: cross-field numeric rules (min < defaultValue < max, step divides the range) can't be expressed; each number validates alone.",
|
|
1660
|
+
"SCHEMA GAP: `readOnly: \"developer\"` is the only field-level permission — no 'finance can edit formulas' role scope.",
|
|
1661
|
+
"A select's choices need numeric values; a text-only dropdown (\"Small team\") must be given a number or it breaks the maths."
|
|
1662
|
+
]
|
|
1663
|
+
}
|
|
1664
|
+
},
|
|
1665
|
+
{
|
|
1666
|
+
id: "product-configurator",
|
|
1667
|
+
label: "Product configurator",
|
|
1668
|
+
proves: "Steps → options → availability rules (4 levels) · price deltas · single vs multi pick · Shopify variant references",
|
|
1669
|
+
category: "Data & interactive",
|
|
1670
|
+
tags: [
|
|
1671
|
+
"commerce",
|
|
1672
|
+
"deep-nesting",
|
|
1673
|
+
"repeater",
|
|
1674
|
+
"conditional",
|
|
1675
|
+
"references",
|
|
1676
|
+
"validation"
|
|
1677
|
+
],
|
|
1678
|
+
brand: "Caldera",
|
|
1679
|
+
previewLayout: "split",
|
|
1680
|
+
schema: {
|
|
1681
|
+
name: "productConfigurator",
|
|
1682
|
+
title: "Product configurator",
|
|
1683
|
+
kind: "block",
|
|
1684
|
+
icon: "adjustments",
|
|
1685
|
+
category: "Data & interactive",
|
|
1686
|
+
description: "Build a kit step by step with a running total",
|
|
1687
|
+
variants: [{
|
|
1688
|
+
id: "stepper",
|
|
1689
|
+
name: "One step at a time"
|
|
1690
|
+
}, {
|
|
1691
|
+
id: "all",
|
|
1692
|
+
name: "All steps on one page"
|
|
1693
|
+
}],
|
|
1694
|
+
preview: {
|
|
1695
|
+
title: "heading",
|
|
1696
|
+
media: "image"
|
|
1697
|
+
},
|
|
1698
|
+
groups: [
|
|
1699
|
+
{
|
|
1700
|
+
name: "content",
|
|
1701
|
+
title: "Content",
|
|
1702
|
+
default: true
|
|
1703
|
+
},
|
|
1704
|
+
{
|
|
1705
|
+
name: "steps",
|
|
1706
|
+
title: "Steps",
|
|
1707
|
+
icon: "list-numbers"
|
|
1708
|
+
},
|
|
1709
|
+
{
|
|
1710
|
+
name: "checkout",
|
|
1711
|
+
title: "Checkout"
|
|
1712
|
+
}
|
|
1713
|
+
],
|
|
1714
|
+
fields: [
|
|
1715
|
+
{
|
|
1716
|
+
name: "heading",
|
|
1717
|
+
title: "Heading",
|
|
1718
|
+
type: "string",
|
|
1719
|
+
group: "content",
|
|
1720
|
+
validation: {
|
|
1721
|
+
required: true,
|
|
1722
|
+
recommendedMax: 50
|
|
1723
|
+
},
|
|
1724
|
+
ai: true
|
|
1725
|
+
},
|
|
1726
|
+
{
|
|
1727
|
+
name: "intro",
|
|
1728
|
+
title: "Intro",
|
|
1729
|
+
type: "text",
|
|
1730
|
+
rows: 2,
|
|
1731
|
+
group: "content",
|
|
1732
|
+
validation: { recommendedMax: 160 }
|
|
1733
|
+
},
|
|
1734
|
+
{
|
|
1735
|
+
name: "image",
|
|
1736
|
+
title: "Image",
|
|
1737
|
+
type: "image",
|
|
1738
|
+
group: "content",
|
|
1739
|
+
validation: { required: true }
|
|
1740
|
+
},
|
|
1741
|
+
{
|
|
1742
|
+
name: "baseProduct",
|
|
1743
|
+
title: "Base product",
|
|
1744
|
+
type: "reference",
|
|
1745
|
+
group: "checkout",
|
|
1746
|
+
reference: { to: ["shopify"] },
|
|
1747
|
+
validation: { required: true },
|
|
1748
|
+
description: "Its Shopify price is the starting total."
|
|
1749
|
+
},
|
|
1750
|
+
{
|
|
1751
|
+
name: "steps",
|
|
1752
|
+
title: "Steps",
|
|
1753
|
+
type: "array",
|
|
1754
|
+
group: "steps",
|
|
1755
|
+
itemNoun: "step",
|
|
1756
|
+
validation: {
|
|
1757
|
+
min: 1,
|
|
1758
|
+
max: 6
|
|
1759
|
+
},
|
|
1760
|
+
of: [{
|
|
1761
|
+
name: "step",
|
|
1762
|
+
title: "Step",
|
|
1763
|
+
type: "object",
|
|
1764
|
+
icon: "list-numbers",
|
|
1765
|
+
preview: {
|
|
1766
|
+
title: "title",
|
|
1767
|
+
subtitle: "selection",
|
|
1768
|
+
fallback: "Untitled step"
|
|
1769
|
+
},
|
|
1770
|
+
fields: [
|
|
1771
|
+
{
|
|
1772
|
+
name: "title",
|
|
1773
|
+
title: "Step title",
|
|
1774
|
+
type: "string",
|
|
1775
|
+
validation: {
|
|
1776
|
+
required: true,
|
|
1777
|
+
max: 40
|
|
1778
|
+
},
|
|
1779
|
+
placeholder: "Choose your grate"
|
|
1780
|
+
},
|
|
1781
|
+
{
|
|
1782
|
+
name: "key",
|
|
1783
|
+
title: "Key",
|
|
1784
|
+
type: "string",
|
|
1785
|
+
validation: {
|
|
1786
|
+
required: true,
|
|
1787
|
+
pattern: KEY_PATTERN,
|
|
1788
|
+
patternMessage: KEY_MESSAGE,
|
|
1789
|
+
unique: true
|
|
1790
|
+
},
|
|
1791
|
+
description: "Used by rules in other steps, e.g. grate."
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
name: "help",
|
|
1795
|
+
title: "Help",
|
|
1796
|
+
type: "string",
|
|
1797
|
+
validation: { max: 90 }
|
|
1798
|
+
},
|
|
1799
|
+
{
|
|
1800
|
+
name: "selection",
|
|
1801
|
+
title: "Customers pick",
|
|
1802
|
+
type: "select",
|
|
1803
|
+
layout: "segmented",
|
|
1804
|
+
initialValue: "single",
|
|
1805
|
+
options: [{
|
|
1806
|
+
value: "single",
|
|
1807
|
+
label: "One"
|
|
1808
|
+
}, {
|
|
1809
|
+
value: "multiple",
|
|
1810
|
+
label: "Several"
|
|
1811
|
+
}]
|
|
1812
|
+
},
|
|
1813
|
+
{
|
|
1814
|
+
name: "isRequired",
|
|
1815
|
+
title: "Required",
|
|
1816
|
+
type: "boolean",
|
|
1817
|
+
initialValue: true,
|
|
1818
|
+
description: "Checkout stays disabled until this step has a pick."
|
|
1819
|
+
},
|
|
1820
|
+
{
|
|
1821
|
+
name: "maxPicks",
|
|
1822
|
+
title: "At most",
|
|
1823
|
+
type: "number",
|
|
1824
|
+
hidden: {
|
|
1825
|
+
field: "selection",
|
|
1826
|
+
equals: "multiple"
|
|
1827
|
+
},
|
|
1828
|
+
numeric: {
|
|
1829
|
+
min: 2,
|
|
1830
|
+
max: 8,
|
|
1831
|
+
step: 1
|
|
1832
|
+
}
|
|
1833
|
+
},
|
|
1834
|
+
{
|
|
1835
|
+
name: "display",
|
|
1836
|
+
title: "Show options as",
|
|
1837
|
+
type: "select",
|
|
1838
|
+
layout: "segmented",
|
|
1839
|
+
initialValue: "cards",
|
|
1840
|
+
options: [
|
|
1841
|
+
{
|
|
1842
|
+
value: "cards",
|
|
1843
|
+
label: "Cards"
|
|
1844
|
+
},
|
|
1845
|
+
{
|
|
1846
|
+
value: "swatches",
|
|
1847
|
+
label: "Swatches"
|
|
1848
|
+
},
|
|
1849
|
+
{
|
|
1850
|
+
value: "list",
|
|
1851
|
+
label: "List"
|
|
1852
|
+
}
|
|
1853
|
+
]
|
|
1854
|
+
},
|
|
1855
|
+
{
|
|
1856
|
+
name: "options",
|
|
1857
|
+
title: "Options",
|
|
1858
|
+
type: "array",
|
|
1859
|
+
itemNoun: "option",
|
|
1860
|
+
validation: {
|
|
1861
|
+
min: 2,
|
|
1862
|
+
max: 8
|
|
1863
|
+
},
|
|
1864
|
+
of: [{
|
|
1865
|
+
name: "option",
|
|
1866
|
+
title: "Option",
|
|
1867
|
+
type: "object",
|
|
1868
|
+
icon: "circle-check",
|
|
1869
|
+
preview: {
|
|
1870
|
+
title: "label",
|
|
1871
|
+
subtitle: "priceDelta",
|
|
1872
|
+
media: "image",
|
|
1873
|
+
fallback: "Untitled option"
|
|
1874
|
+
},
|
|
1875
|
+
fields: [
|
|
1876
|
+
{
|
|
1877
|
+
name: "label",
|
|
1878
|
+
title: "Label",
|
|
1879
|
+
type: "string",
|
|
1880
|
+
validation: {
|
|
1881
|
+
required: true,
|
|
1882
|
+
max: 40
|
|
1883
|
+
}
|
|
1884
|
+
},
|
|
1885
|
+
{
|
|
1886
|
+
name: "key",
|
|
1887
|
+
title: "Key",
|
|
1888
|
+
type: "string",
|
|
1889
|
+
validation: {
|
|
1890
|
+
required: true,
|
|
1891
|
+
pattern: KEY_PATTERN,
|
|
1892
|
+
patternMessage: KEY_MESSAGE
|
|
1893
|
+
},
|
|
1894
|
+
description: "Unique within the step, e.g. xl."
|
|
1895
|
+
},
|
|
1896
|
+
{
|
|
1897
|
+
name: "detail",
|
|
1898
|
+
title: "Detail",
|
|
1899
|
+
type: "string",
|
|
1900
|
+
validation: { max: 80 }
|
|
1901
|
+
},
|
|
1902
|
+
{
|
|
1903
|
+
name: "image",
|
|
1904
|
+
title: "Image",
|
|
1905
|
+
type: "image"
|
|
1906
|
+
},
|
|
1907
|
+
{
|
|
1908
|
+
name: "swatch",
|
|
1909
|
+
title: "Swatch",
|
|
1910
|
+
type: "color"
|
|
1911
|
+
},
|
|
1912
|
+
{
|
|
1913
|
+
name: "priceDelta",
|
|
1914
|
+
title: "Price change",
|
|
1915
|
+
type: "number",
|
|
1916
|
+
numeric: {
|
|
1917
|
+
unit: "USD",
|
|
1918
|
+
step: 1
|
|
1919
|
+
},
|
|
1920
|
+
description: "Added to the total. Use a negative number for a discount."
|
|
1921
|
+
},
|
|
1922
|
+
{
|
|
1923
|
+
name: "variant",
|
|
1924
|
+
title: "Shopify product",
|
|
1925
|
+
type: "reference",
|
|
1926
|
+
reference: { to: ["shopify"] },
|
|
1927
|
+
description: "Added to the cart with the base product."
|
|
1928
|
+
},
|
|
1929
|
+
{
|
|
1930
|
+
name: "isDefault",
|
|
1931
|
+
title: "Picked by default",
|
|
1932
|
+
type: "boolean"
|
|
1933
|
+
},
|
|
1934
|
+
{
|
|
1935
|
+
name: "availability",
|
|
1936
|
+
title: "Availability",
|
|
1937
|
+
type: "object",
|
|
1938
|
+
icon: "git-branch",
|
|
1939
|
+
openMode: "drill",
|
|
1940
|
+
fields: [
|
|
1941
|
+
{
|
|
1942
|
+
name: "mode",
|
|
1943
|
+
title: "Available",
|
|
1944
|
+
type: "select",
|
|
1945
|
+
layout: "radio",
|
|
1946
|
+
initialValue: "always",
|
|
1947
|
+
options: [
|
|
1948
|
+
{
|
|
1949
|
+
value: "always",
|
|
1950
|
+
label: "Always"
|
|
1951
|
+
},
|
|
1952
|
+
{
|
|
1953
|
+
value: "requires",
|
|
1954
|
+
label: "Only with another pick",
|
|
1955
|
+
description: "e.g. the griddle needs the XL grate"
|
|
1956
|
+
},
|
|
1957
|
+
{
|
|
1958
|
+
value: "excludes",
|
|
1959
|
+
label: "Not with another pick"
|
|
1960
|
+
}
|
|
1961
|
+
]
|
|
1962
|
+
},
|
|
1963
|
+
{
|
|
1964
|
+
name: "rule",
|
|
1965
|
+
title: "Other pick",
|
|
1966
|
+
type: "string",
|
|
1967
|
+
hidden: {
|
|
1968
|
+
field: "mode",
|
|
1969
|
+
equals: ["requires", "excludes"]
|
|
1970
|
+
},
|
|
1971
|
+
validation: {
|
|
1972
|
+
required: true,
|
|
1973
|
+
pattern: "^[a-z][a-zA-Z0-9]*\\.[a-z][a-zA-Z0-9]*$",
|
|
1974
|
+
patternMessage: "Use the step key, a dot, then the option key, like grate.xl."
|
|
1975
|
+
}
|
|
1976
|
+
},
|
|
1977
|
+
{
|
|
1978
|
+
name: "unavailableNote",
|
|
1979
|
+
title: "Note when unavailable",
|
|
1980
|
+
type: "string",
|
|
1981
|
+
hidden: {
|
|
1982
|
+
field: "mode",
|
|
1983
|
+
equals: ["requires", "excludes"]
|
|
1984
|
+
},
|
|
1985
|
+
validation: { max: 60 }
|
|
1986
|
+
},
|
|
1987
|
+
{
|
|
1988
|
+
name: "isSoldOut",
|
|
1989
|
+
title: "Sold out",
|
|
1990
|
+
type: "boolean"
|
|
1991
|
+
}
|
|
1992
|
+
]
|
|
1993
|
+
}
|
|
1994
|
+
]
|
|
1995
|
+
}]
|
|
1996
|
+
}
|
|
1997
|
+
]
|
|
1998
|
+
}]
|
|
1999
|
+
},
|
|
2000
|
+
{
|
|
2001
|
+
name: "showTotal",
|
|
2002
|
+
title: "Show running total",
|
|
2003
|
+
type: "boolean",
|
|
2004
|
+
group: "checkout",
|
|
2005
|
+
initialValue: true
|
|
2006
|
+
},
|
|
2007
|
+
{
|
|
2008
|
+
name: "button",
|
|
2009
|
+
title: "Add to cart button",
|
|
2010
|
+
type: "button",
|
|
2011
|
+
group: "checkout"
|
|
2012
|
+
},
|
|
2013
|
+
{
|
|
2014
|
+
name: "shippingNote",
|
|
2015
|
+
title: "Under the button",
|
|
2016
|
+
type: "string",
|
|
2017
|
+
group: "checkout",
|
|
2018
|
+
validation: { max: 80 },
|
|
2019
|
+
placeholder: "Free shipping over $150"
|
|
2020
|
+
}
|
|
2021
|
+
]
|
|
2022
|
+
},
|
|
2023
|
+
value: {
|
|
2024
|
+
_variant: "stepper",
|
|
2025
|
+
heading: "Build your Cable Fold kit.",
|
|
2026
|
+
intro: "Start with the grate, add legs, then the pans you'll actually cook in. Everything packs flat into one sleeve.",
|
|
2027
|
+
image: pic("cable-fold-kit", "The Cable Fold Grill folded flat beside its canvas sleeve"),
|
|
2028
|
+
baseProduct: "gid://shopify/Product/8812",
|
|
2029
|
+
steps: [
|
|
2030
|
+
{
|
|
2031
|
+
_key: "st1",
|
|
2032
|
+
title: "Choose your grate",
|
|
2033
|
+
key: "grate",
|
|
2034
|
+
selection: "single",
|
|
2035
|
+
isRequired: true,
|
|
2036
|
+
display: "cards",
|
|
2037
|
+
options: [{
|
|
2038
|
+
_key: "op1",
|
|
2039
|
+
label: "Cable Fold 16\"",
|
|
2040
|
+
key: "standard",
|
|
2041
|
+
detail: "Feeds four. Folds to 2 cm.",
|
|
2042
|
+
image: pic("grate-16", "The 16 inch Cable Fold grate over coals"),
|
|
2043
|
+
priceDelta: 0,
|
|
2044
|
+
isDefault: true,
|
|
2045
|
+
availability: { mode: "always" }
|
|
2046
|
+
}, {
|
|
2047
|
+
_key: "op2",
|
|
2048
|
+
label: "Cable Fold XL 22\"",
|
|
2049
|
+
key: "xl",
|
|
2050
|
+
detail: "Feeds eight. Takes the griddle plate.",
|
|
2051
|
+
image: pic("grate-22", "The 22 inch XL grate with two pans on it"),
|
|
2052
|
+
priceDelta: 60,
|
|
2053
|
+
variant: "gid://shopify/Product/8812",
|
|
2054
|
+
availability: { mode: "always" }
|
|
2055
|
+
}]
|
|
2056
|
+
},
|
|
2057
|
+
{
|
|
2058
|
+
_key: "st2",
|
|
2059
|
+
title: "Pick the legs",
|
|
2060
|
+
key: "legs",
|
|
2061
|
+
selection: "single",
|
|
2062
|
+
isRequired: true,
|
|
2063
|
+
display: "swatches",
|
|
2064
|
+
options: [{
|
|
2065
|
+
_key: "op3",
|
|
2066
|
+
label: "Folding steel",
|
|
2067
|
+
key: "steel",
|
|
2068
|
+
swatch: "#4B5563",
|
|
2069
|
+
priceDelta: 0,
|
|
2070
|
+
isDefault: true,
|
|
2071
|
+
availability: { mode: "always" }
|
|
2072
|
+
}, {
|
|
2073
|
+
_key: "op4",
|
|
2074
|
+
label: "Titanium",
|
|
2075
|
+
key: "titanium",
|
|
2076
|
+
detail: "Saves 480 g",
|
|
2077
|
+
swatch: "#9CA3AF",
|
|
2078
|
+
priceDelta: 85,
|
|
2079
|
+
availability: {
|
|
2080
|
+
mode: "always",
|
|
2081
|
+
isSoldOut: true
|
|
2082
|
+
}
|
|
2083
|
+
}]
|
|
2084
|
+
},
|
|
2085
|
+
{
|
|
2086
|
+
_key: "st3",
|
|
2087
|
+
title: "Add pans",
|
|
2088
|
+
key: "pans",
|
|
2089
|
+
help: "Pick up to three. Bundles save 15%.",
|
|
2090
|
+
selection: "multiple",
|
|
2091
|
+
isRequired: false,
|
|
2092
|
+
maxPicks: 3,
|
|
2093
|
+
display: "cards",
|
|
2094
|
+
options: [
|
|
2095
|
+
{
|
|
2096
|
+
_key: "op5",
|
|
2097
|
+
label: "Ridge Skillet 10\"",
|
|
2098
|
+
key: "skillet",
|
|
2099
|
+
image: pic("ridge-skillet", "Ridge Skillet with two fried eggs"),
|
|
2100
|
+
priceDelta: 55,
|
|
2101
|
+
variant: "gid://shopify/Product/8814",
|
|
2102
|
+
availability: { mode: "always" }
|
|
2103
|
+
},
|
|
2104
|
+
{
|
|
2105
|
+
_key: "op6",
|
|
2106
|
+
label: "Griddle plate",
|
|
2107
|
+
key: "griddle",
|
|
2108
|
+
detail: "Smash burgers, flatbreads, pancakes",
|
|
2109
|
+
image: pic("griddle-plate", "Flatbreads blistering on the griddle plate"),
|
|
2110
|
+
priceDelta: 45,
|
|
2111
|
+
availability: {
|
|
2112
|
+
mode: "requires",
|
|
2113
|
+
rule: "grate.xl",
|
|
2114
|
+
unavailableNote: "Fits the XL grate only"
|
|
2115
|
+
}
|
|
2116
|
+
},
|
|
2117
|
+
{
|
|
2118
|
+
_key: "op7",
|
|
2119
|
+
label: "Trail Kettle 1L",
|
|
2120
|
+
key: "kettle",
|
|
2121
|
+
image: pic("trail-kettle", "Trail Kettle steaming on the grate"),
|
|
2122
|
+
priceDelta: 39,
|
|
2123
|
+
variant: "gid://shopify/Product/8815",
|
|
2124
|
+
availability: { mode: "always" }
|
|
2125
|
+
}
|
|
2126
|
+
]
|
|
2127
|
+
}
|
|
2128
|
+
],
|
|
2129
|
+
showTotal: true,
|
|
2130
|
+
button: {
|
|
2131
|
+
label: "Add kit to cart",
|
|
2132
|
+
href: "/cart",
|
|
2133
|
+
type: "page",
|
|
2134
|
+
title: "Cart",
|
|
2135
|
+
style: "primary",
|
|
2136
|
+
arrow: true
|
|
2137
|
+
},
|
|
2138
|
+
shippingNote: "Free shipping over $150 · Lifetime warranty"
|
|
2139
|
+
},
|
|
2140
|
+
notes: {
|
|
2141
|
+
why: "Steps → options is a two-level repeater; each option carries its own price delta, image and Shopify product so the cart can be built without a lookup table. Dependencies ('griddle needs the XL grate') live ON the dependent option as an availability object — four levels deep (Configurator › Step › Option › Availability) — referencing another pick by 'stepKey.optionKey' so rules survive reordering. Inventory and base price come from Shopify (reference), never retyped.",
|
|
2142
|
+
sanity: "Sanity: configurator { steps[] { options[] { priceDelta, variant: reference→shopify.productVariant (Sanity Connect), availability { mode, rule } } } }. Sanity's conditional `hidden` callbacks can read `document` and `parent`, so the rule could be a dynamic list — ours is a patterned string.",
|
|
2143
|
+
htmlSignals: [
|
|
2144
|
+
"Numbered steps (Step 1 of 3) with radio groups or toggle cards per step",
|
|
2145
|
+
"Option labels with '+$60' / '−$10' price adjustments",
|
|
2146
|
+
"A running total / sticky summary that updates on change",
|
|
2147
|
+
"Options disabled with notes like 'Requires XL' or 'Sold out' (aria-disabled, data-requires)",
|
|
2148
|
+
"Shopify variant ids in data attributes or a cart/add.js form"
|
|
2149
|
+
],
|
|
2150
|
+
mcp: "Each step container (fieldset/legend or numbered heading) → a `step`; radio groups → selection single, checkbox groups → multiple + maxPicks from any limit text. Each option → label, detail, image (the swatch color if it's a color chip), priceDelta parsed from '+$60' (signed number, no currency symbol), variant from data-variant-id/gid. Detect dependencies from disabled-state logic or text ('Fits the XL grate only') and write `availability.rule` as stepKey.optionKey using the keys you generated — never by index. If rules involve more than one other pick (A and B, A or B), or quantities, keep the logic in code and only model the copy, and add a pitfall note. Depth check: if options have their own sub-options (a third level of choice), split each step into its own block/component rather than nesting a fifth level.",
|
|
2151
|
+
pitfalls: [
|
|
2152
|
+
"SCHEMA GAP: conditions can only read SIBLING fields — an option's `swatch` should show only when its PARENT step's `display` is swatches, which can't be expressed, so the field is always visible.",
|
|
2153
|
+
"SCHEMA GAP: availability.rule is a string pattern, not a reference to a real step/option; renaming a key breaks rules silently. Needs an same-entry reference type ('pick a sibling item').",
|
|
2154
|
+
"SCHEMA GAP: no boolean logic in rules (requires A AND B, excludes any of …) and no cross-step validation (two defaults in a single-pick step, `maxPicks` < number of defaults).",
|
|
2155
|
+
"SCHEMA GAP: option keys need to be unique within their step only (scoped uniqueness) — `unique` is neither scoped nor enforced.",
|
|
2156
|
+
"Price deltas drift from Shopify variant prices; prefer reading the variant price and keeping priceDelta only for non-Shopify add-ons."
|
|
2157
|
+
]
|
|
2158
|
+
}
|
|
2159
|
+
},
|
|
2160
|
+
{
|
|
2161
|
+
id: "stockist-map",
|
|
2162
|
+
label: "Map with markers",
|
|
2163
|
+
proves: "Categories → markers → popup → button · geopoints · manual or collection source · list fallback for keyboard users",
|
|
2164
|
+
category: "Data & interactive",
|
|
2165
|
+
tags: [
|
|
2166
|
+
"repeater",
|
|
2167
|
+
"conditional",
|
|
2168
|
+
"embed",
|
|
2169
|
+
"a11y",
|
|
2170
|
+
"references",
|
|
2171
|
+
"seo-structured"
|
|
2172
|
+
],
|
|
2173
|
+
brand: "Caldera",
|
|
2174
|
+
previewLayout: "media",
|
|
2175
|
+
schema: {
|
|
2176
|
+
name: "markerMap",
|
|
2177
|
+
title: "Map",
|
|
2178
|
+
kind: "block",
|
|
2179
|
+
icon: "map-pin",
|
|
2180
|
+
category: "Data & interactive",
|
|
2181
|
+
description: "Pins on a map with popups, filters and a list beside it",
|
|
2182
|
+
variants: [{
|
|
2183
|
+
id: "list-left",
|
|
2184
|
+
name: "List on the left"
|
|
2185
|
+
}, {
|
|
2186
|
+
id: "full",
|
|
2187
|
+
name: "Full-width map"
|
|
2188
|
+
}],
|
|
2189
|
+
preview: {
|
|
2190
|
+
title: "heading",
|
|
2191
|
+
subtitle: "intro",
|
|
2192
|
+
icon: "map-pin"
|
|
2193
|
+
},
|
|
2194
|
+
groups: [
|
|
2195
|
+
{
|
|
2196
|
+
name: "content",
|
|
2197
|
+
title: "Content",
|
|
2198
|
+
default: true
|
|
2199
|
+
},
|
|
2200
|
+
{
|
|
2201
|
+
name: "places",
|
|
2202
|
+
title: "Places",
|
|
2203
|
+
icon: "map-pins"
|
|
2204
|
+
},
|
|
2205
|
+
{
|
|
2206
|
+
name: "map",
|
|
2207
|
+
title: "Map"
|
|
2208
|
+
}
|
|
2209
|
+
],
|
|
2210
|
+
fields: [
|
|
2211
|
+
{
|
|
2212
|
+
name: "heading",
|
|
2213
|
+
title: "Heading",
|
|
2214
|
+
type: "string",
|
|
2215
|
+
group: "content",
|
|
2216
|
+
validation: {
|
|
2217
|
+
required: true,
|
|
2218
|
+
recommendedMax: 50
|
|
2219
|
+
},
|
|
2220
|
+
ai: true
|
|
2221
|
+
},
|
|
2222
|
+
{
|
|
2223
|
+
name: "intro",
|
|
2224
|
+
title: "Intro",
|
|
2225
|
+
type: "text",
|
|
2226
|
+
rows: 2,
|
|
2227
|
+
group: "content",
|
|
2228
|
+
validation: { recommendedMax: 160 }
|
|
2229
|
+
},
|
|
2230
|
+
{
|
|
2231
|
+
name: "categories",
|
|
2232
|
+
title: "Categories",
|
|
2233
|
+
type: "array",
|
|
2234
|
+
group: "places",
|
|
2235
|
+
itemNoun: "category",
|
|
2236
|
+
validation: { max: 6 },
|
|
2237
|
+
of: [{
|
|
2238
|
+
name: "category",
|
|
2239
|
+
title: "Category",
|
|
2240
|
+
type: "object",
|
|
2241
|
+
icon: "tag",
|
|
2242
|
+
preview: {
|
|
2243
|
+
title: "label",
|
|
2244
|
+
subtitle: "key",
|
|
2245
|
+
fallback: "Untitled category"
|
|
2246
|
+
},
|
|
2247
|
+
fields: [
|
|
2248
|
+
{
|
|
2249
|
+
name: "label",
|
|
2250
|
+
title: "Label",
|
|
2251
|
+
type: "string",
|
|
2252
|
+
validation: {
|
|
2253
|
+
required: true,
|
|
2254
|
+
max: 24
|
|
2255
|
+
}
|
|
2256
|
+
},
|
|
2257
|
+
{
|
|
2258
|
+
name: "key",
|
|
2259
|
+
title: "Key",
|
|
2260
|
+
type: "string",
|
|
2261
|
+
validation: {
|
|
2262
|
+
required: true,
|
|
2263
|
+
pattern: KEY_PATTERN,
|
|
2264
|
+
patternMessage: KEY_MESSAGE,
|
|
2265
|
+
unique: true
|
|
2266
|
+
}
|
|
2267
|
+
},
|
|
2268
|
+
{
|
|
2269
|
+
name: "color",
|
|
2270
|
+
title: "Pin color",
|
|
2271
|
+
type: "color"
|
|
2272
|
+
},
|
|
2273
|
+
{
|
|
2274
|
+
name: "icon",
|
|
2275
|
+
title: "Pin icon",
|
|
2276
|
+
type: "icon"
|
|
2277
|
+
}
|
|
2278
|
+
]
|
|
2279
|
+
}]
|
|
2280
|
+
},
|
|
2281
|
+
{
|
|
2282
|
+
name: "source",
|
|
2283
|
+
title: "Places come from",
|
|
2284
|
+
type: "select",
|
|
2285
|
+
group: "places",
|
|
2286
|
+
layout: "segmented",
|
|
2287
|
+
initialValue: "manual",
|
|
2288
|
+
options: [{
|
|
2289
|
+
value: "manual",
|
|
2290
|
+
label: "Added here"
|
|
2291
|
+
}, {
|
|
2292
|
+
value: "collection",
|
|
2293
|
+
label: "Locations collection"
|
|
2294
|
+
}]
|
|
2295
|
+
},
|
|
2296
|
+
{
|
|
2297
|
+
name: "markers",
|
|
2298
|
+
title: "Places",
|
|
2299
|
+
type: "array",
|
|
2300
|
+
group: "places",
|
|
2301
|
+
itemNoun: "place",
|
|
2302
|
+
hidden: {
|
|
2303
|
+
field: "source",
|
|
2304
|
+
equals: "manual"
|
|
2305
|
+
},
|
|
2306
|
+
validation: {
|
|
2307
|
+
min: 1,
|
|
2308
|
+
max: 50
|
|
2309
|
+
},
|
|
2310
|
+
of: [{
|
|
2311
|
+
name: "marker",
|
|
2312
|
+
title: "Place",
|
|
2313
|
+
type: "object",
|
|
2314
|
+
icon: "map-pin",
|
|
2315
|
+
preview: {
|
|
2316
|
+
title: "name",
|
|
2317
|
+
subtitle: "address",
|
|
2318
|
+
fallback: "Untitled place"
|
|
2319
|
+
},
|
|
2320
|
+
fields: [
|
|
2321
|
+
{
|
|
2322
|
+
name: "name",
|
|
2323
|
+
title: "Name",
|
|
2324
|
+
type: "string",
|
|
2325
|
+
validation: {
|
|
2326
|
+
required: true,
|
|
2327
|
+
max: 60
|
|
2328
|
+
}
|
|
2329
|
+
},
|
|
2330
|
+
{
|
|
2331
|
+
name: "category",
|
|
2332
|
+
title: "Category key",
|
|
2333
|
+
type: "string",
|
|
2334
|
+
validation: {
|
|
2335
|
+
required: true,
|
|
2336
|
+
pattern: KEY_PATTERN,
|
|
2337
|
+
patternMessage: KEY_MESSAGE
|
|
2338
|
+
},
|
|
2339
|
+
description: "One of the category keys above, e.g. retailer."
|
|
2340
|
+
},
|
|
2341
|
+
{
|
|
2342
|
+
name: "location",
|
|
2343
|
+
title: "Pin",
|
|
2344
|
+
type: "geopoint",
|
|
2345
|
+
validation: { required: true }
|
|
2346
|
+
},
|
|
2347
|
+
{
|
|
2348
|
+
name: "address",
|
|
2349
|
+
title: "Address",
|
|
2350
|
+
type: "text",
|
|
2351
|
+
rows: 2,
|
|
2352
|
+
validation: {
|
|
2353
|
+
required: true,
|
|
2354
|
+
max: 160
|
|
2355
|
+
}
|
|
2356
|
+
},
|
|
2357
|
+
{
|
|
2358
|
+
name: "phone",
|
|
2359
|
+
title: "Phone",
|
|
2360
|
+
type: "string",
|
|
2361
|
+
validation: {
|
|
2362
|
+
pattern: "^\\+?[0-9 ()-]{7,20}$",
|
|
2363
|
+
patternMessage: "Use digits, spaces and an optional +, like +1 541 555 0182."
|
|
2364
|
+
}
|
|
2365
|
+
},
|
|
2366
|
+
{
|
|
2367
|
+
name: "hours",
|
|
2368
|
+
title: "Opening hours",
|
|
2369
|
+
type: "array",
|
|
2370
|
+
itemNoun: "line",
|
|
2371
|
+
openMode: "inline",
|
|
2372
|
+
validation: { max: 7 },
|
|
2373
|
+
of: [{
|
|
2374
|
+
name: "hoursLine",
|
|
2375
|
+
title: "Line",
|
|
2376
|
+
type: "object",
|
|
2377
|
+
icon: "clock",
|
|
2378
|
+
preview: {
|
|
2379
|
+
title: "days",
|
|
2380
|
+
subtitle: "times",
|
|
2381
|
+
fallback: "Untitled line"
|
|
2382
|
+
},
|
|
2383
|
+
fields: [{
|
|
2384
|
+
name: "days",
|
|
2385
|
+
title: "Days",
|
|
2386
|
+
type: "string",
|
|
2387
|
+
validation: {
|
|
2388
|
+
required: true,
|
|
2389
|
+
max: 20
|
|
2390
|
+
},
|
|
2391
|
+
placeholder: "Mon–Fri"
|
|
2392
|
+
}, {
|
|
2393
|
+
name: "times",
|
|
2394
|
+
title: "Hours",
|
|
2395
|
+
type: "string",
|
|
2396
|
+
validation: {
|
|
2397
|
+
required: true,
|
|
2398
|
+
pattern: "^(Closed|\\d{1,2}:\\d{2}–\\d{1,2}:\\d{2})$",
|
|
2399
|
+
patternMessage: "Use opening hours, like 09:00–18:00 or Closed."
|
|
2400
|
+
}
|
|
2401
|
+
}]
|
|
2402
|
+
}]
|
|
2403
|
+
},
|
|
2404
|
+
{
|
|
2405
|
+
name: "popup",
|
|
2406
|
+
title: "Popup",
|
|
2407
|
+
type: "object",
|
|
2408
|
+
icon: "message-2",
|
|
2409
|
+
openMode: "drill",
|
|
2410
|
+
fields: [
|
|
2411
|
+
{
|
|
2412
|
+
name: "image",
|
|
2413
|
+
title: "Image",
|
|
2414
|
+
type: "image"
|
|
2415
|
+
},
|
|
2416
|
+
{
|
|
2417
|
+
name: "body",
|
|
2418
|
+
title: "Body",
|
|
2419
|
+
type: "text",
|
|
2420
|
+
rows: 2,
|
|
2421
|
+
validation: { recommendedMax: 120 }
|
|
2422
|
+
},
|
|
2423
|
+
{
|
|
2424
|
+
name: "button",
|
|
2425
|
+
title: "Button",
|
|
2426
|
+
type: "button"
|
|
2427
|
+
}
|
|
2428
|
+
]
|
|
2429
|
+
}
|
|
2430
|
+
]
|
|
2431
|
+
}]
|
|
2432
|
+
},
|
|
2433
|
+
{
|
|
2434
|
+
name: "collection",
|
|
2435
|
+
title: "Collection",
|
|
2436
|
+
type: "reference",
|
|
2437
|
+
group: "places",
|
|
2438
|
+
reference: { to: ["collection"] },
|
|
2439
|
+
hidden: {
|
|
2440
|
+
field: "source",
|
|
2441
|
+
equals: "collection"
|
|
2442
|
+
},
|
|
2443
|
+
validation: { required: true }
|
|
2444
|
+
},
|
|
2445
|
+
{
|
|
2446
|
+
name: "provider",
|
|
2447
|
+
title: "Map provider",
|
|
2448
|
+
type: "select",
|
|
2449
|
+
group: "map",
|
|
2450
|
+
access: { edit: "schema.manage" },
|
|
2451
|
+
lockReason: "The provider key is set in the site's environment.",
|
|
2452
|
+
options: [
|
|
2453
|
+
{
|
|
2454
|
+
value: "maplibre",
|
|
2455
|
+
label: "MapLibre"
|
|
2456
|
+
},
|
|
2457
|
+
{
|
|
2458
|
+
value: "mapbox",
|
|
2459
|
+
label: "Mapbox"
|
|
2460
|
+
},
|
|
2461
|
+
{
|
|
2462
|
+
value: "google",
|
|
2463
|
+
label: "Google Maps"
|
|
2464
|
+
}
|
|
2465
|
+
]
|
|
2466
|
+
},
|
|
2467
|
+
{
|
|
2468
|
+
name: "style",
|
|
2469
|
+
title: "Map style",
|
|
2470
|
+
type: "select",
|
|
2471
|
+
group: "map",
|
|
2472
|
+
layout: "segmented",
|
|
2473
|
+
options: [
|
|
2474
|
+
{
|
|
2475
|
+
value: "outdoors",
|
|
2476
|
+
label: "Outdoors"
|
|
2477
|
+
},
|
|
2478
|
+
{
|
|
2479
|
+
value: "light",
|
|
2480
|
+
label: "Light"
|
|
2481
|
+
},
|
|
2482
|
+
{
|
|
2483
|
+
value: "satellite",
|
|
2484
|
+
label: "Satellite"
|
|
2485
|
+
}
|
|
2486
|
+
]
|
|
2487
|
+
},
|
|
2488
|
+
{
|
|
2489
|
+
name: "center",
|
|
2490
|
+
title: "Start centered on",
|
|
2491
|
+
type: "geopoint",
|
|
2492
|
+
group: "map",
|
|
2493
|
+
description: "Leave empty to fit every pin."
|
|
2494
|
+
},
|
|
2495
|
+
{
|
|
2496
|
+
name: "zoom",
|
|
2497
|
+
title: "Start zoom",
|
|
2498
|
+
type: "number",
|
|
2499
|
+
group: "map",
|
|
2500
|
+
layout: "slider",
|
|
2501
|
+
numeric: {
|
|
2502
|
+
min: 2,
|
|
2503
|
+
max: 16,
|
|
2504
|
+
step: 1
|
|
2505
|
+
}
|
|
2506
|
+
},
|
|
2507
|
+
{
|
|
2508
|
+
name: "useClustering",
|
|
2509
|
+
title: "Group nearby pins",
|
|
2510
|
+
type: "boolean",
|
|
2511
|
+
group: "map",
|
|
2512
|
+
initialValue: true
|
|
2513
|
+
},
|
|
2514
|
+
{
|
|
2515
|
+
name: "showList",
|
|
2516
|
+
title: "List beside the map",
|
|
2517
|
+
type: "boolean",
|
|
2518
|
+
group: "map",
|
|
2519
|
+
initialValue: true
|
|
2520
|
+
},
|
|
2521
|
+
{
|
|
2522
|
+
name: "showNearMe",
|
|
2523
|
+
title: "Show Near me button",
|
|
2524
|
+
type: "boolean",
|
|
2525
|
+
group: "map"
|
|
2526
|
+
},
|
|
2527
|
+
{
|
|
2528
|
+
name: "useConsentGate",
|
|
2529
|
+
title: "Ask before loading the map",
|
|
2530
|
+
type: "boolean",
|
|
2531
|
+
group: "map",
|
|
2532
|
+
description: "Needed in the EU for Google Maps."
|
|
2533
|
+
}
|
|
2534
|
+
]
|
|
2535
|
+
},
|
|
2536
|
+
value: {
|
|
2537
|
+
_variant: "list-left",
|
|
2538
|
+
heading: "Hold one before you buy one.",
|
|
2539
|
+
intro: "Our pans are in 40 shops across the West. Find the nearest, or visit the workshop in Bend.",
|
|
2540
|
+
categories: [
|
|
2541
|
+
{
|
|
2542
|
+
_key: "k1",
|
|
2543
|
+
label: "Workshop",
|
|
2544
|
+
key: "workshop",
|
|
2545
|
+
color: "#E0663B",
|
|
2546
|
+
icon: "hammer"
|
|
2547
|
+
},
|
|
2548
|
+
{
|
|
2549
|
+
_key: "k2",
|
|
2550
|
+
label: "Retailers",
|
|
2551
|
+
key: "retailer",
|
|
2552
|
+
color: "#1E1B18",
|
|
2553
|
+
icon: "building-store"
|
|
2554
|
+
},
|
|
2555
|
+
{
|
|
2556
|
+
_key: "k3",
|
|
2557
|
+
label: "Restaurants",
|
|
2558
|
+
key: "restaurant",
|
|
2559
|
+
color: "#4F6B4A",
|
|
2560
|
+
icon: "tools-kitchen-2"
|
|
2561
|
+
}
|
|
2562
|
+
],
|
|
2563
|
+
source: "manual",
|
|
2564
|
+
markers: [
|
|
2565
|
+
{
|
|
2566
|
+
_key: "mk1",
|
|
2567
|
+
name: "Caldera Workshop",
|
|
2568
|
+
category: "workshop",
|
|
2569
|
+
location: {
|
|
2570
|
+
label: "Bend, Oregon",
|
|
2571
|
+
lat: 44.0582,
|
|
2572
|
+
lng: -121.3153
|
|
2573
|
+
},
|
|
2574
|
+
address: "61 NW Oregon Ave\nBend, OR 97703",
|
|
2575
|
+
phone: "+1 541 555 0182",
|
|
2576
|
+
hours: [{
|
|
2577
|
+
_key: "h1",
|
|
2578
|
+
days: "Tue–Sat",
|
|
2579
|
+
times: "10:00–18:00"
|
|
2580
|
+
}, {
|
|
2581
|
+
_key: "h2",
|
|
2582
|
+
days: "Sun–Mon",
|
|
2583
|
+
times: "Closed"
|
|
2584
|
+
}],
|
|
2585
|
+
popup: {
|
|
2586
|
+
image: pic("bend-workshop", "The Caldera workshop floor with skillets cooling on racks"),
|
|
2587
|
+
body: "Watch a pour on Saturdays at 11. Seconds sale on the first weekend of each month.",
|
|
2588
|
+
button: {
|
|
2589
|
+
label: "Get directions",
|
|
2590
|
+
href: "https://maps.google.com/?q=61+NW+Oregon+Ave+Bend",
|
|
2591
|
+
type: "url",
|
|
2592
|
+
style: "primary",
|
|
2593
|
+
newTab: true
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
},
|
|
2597
|
+
{
|
|
2598
|
+
_key: "mk2",
|
|
2599
|
+
name: "Next Adventure",
|
|
2600
|
+
category: "retailer",
|
|
2601
|
+
location: {
|
|
2602
|
+
label: "Portland, Oregon",
|
|
2603
|
+
lat: 45.5191,
|
|
2604
|
+
lng: -122.6627
|
|
2605
|
+
},
|
|
2606
|
+
address: "426 SE Grand Ave\nPortland, OR 97214",
|
|
2607
|
+
phone: "+1 503 555 0144",
|
|
2608
|
+
hours: [{
|
|
2609
|
+
_key: "h3",
|
|
2610
|
+
days: "Daily",
|
|
2611
|
+
times: "10:00–19:00"
|
|
2612
|
+
}],
|
|
2613
|
+
popup: {
|
|
2614
|
+
body: "Full cookware range and the Cable Fold on display.",
|
|
2615
|
+
button: {
|
|
2616
|
+
label: "Visit store site",
|
|
2617
|
+
href: "https://www.nextadventure.net",
|
|
2618
|
+
type: "url",
|
|
2619
|
+
style: "outline",
|
|
2620
|
+
newTab: true
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
},
|
|
2624
|
+
{
|
|
2625
|
+
_key: "mk3",
|
|
2626
|
+
name: "Neptune Mountaineering",
|
|
2627
|
+
category: "retailer",
|
|
2628
|
+
location: {
|
|
2629
|
+
label: "Boulder, Colorado",
|
|
2630
|
+
lat: 39.9865,
|
|
2631
|
+
lng: -105.2509
|
|
2632
|
+
},
|
|
2633
|
+
address: "633 S Broadway\nBoulder, CO 80305",
|
|
2634
|
+
hours: [{
|
|
2635
|
+
_key: "h4",
|
|
2636
|
+
days: "Daily",
|
|
2637
|
+
times: "09:00–20:00"
|
|
2638
|
+
}],
|
|
2639
|
+
popup: { body: "Skillets and kettles. Ask for the camp kitchen wall." }
|
|
2640
|
+
},
|
|
2641
|
+
{
|
|
2642
|
+
_key: "mk4",
|
|
2643
|
+
name: "Hearth & Ash",
|
|
2644
|
+
category: "restaurant",
|
|
2645
|
+
location: {
|
|
2646
|
+
label: "Portland, Oregon",
|
|
2647
|
+
lat: 45.5231,
|
|
2648
|
+
lng: -122.6765
|
|
2649
|
+
},
|
|
2650
|
+
address: "1120 SW Morrison St\nPortland, OR 97205",
|
|
2651
|
+
popup: {
|
|
2652
|
+
image: pic("hearth-ash", "Four Ember Dutch ovens over the live fire at Hearth & Ash"),
|
|
2653
|
+
body: "Chef Amara Osei cooks over four Ember ovens every night.",
|
|
2654
|
+
button: {
|
|
2655
|
+
label: "Read the story",
|
|
2656
|
+
href: "/journal/hearth-and-ash",
|
|
2657
|
+
type: "entry",
|
|
2658
|
+
title: "Cooking for 90 over live fire",
|
|
2659
|
+
style: "link"
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
],
|
|
2664
|
+
provider: "maplibre",
|
|
2665
|
+
style: "outdoors",
|
|
2666
|
+
zoom: 6,
|
|
2667
|
+
useClustering: true,
|
|
2668
|
+
showList: true,
|
|
2669
|
+
showNearMe: true,
|
|
2670
|
+
useConsentGate: false
|
|
2671
|
+
},
|
|
2672
|
+
notes: {
|
|
2673
|
+
why: "Categories are an array (editors add 'Pop-ups' without a developer) and drive both filter chips and pin styling; each marker stores a geopoint, a readable address (the list fallback reads it, not the map) and a popup object that drills in to a button — Map › Place › Popup › Button. A collection source exists because stockist lists outgrow a block fast. Provider is developer-locked because it depends on an API key in the environment; consent is a content decision.",
|
|
2674
|
+
sanity: "Sanity: map { categories[], markers[] { location: geopoint, popup { image, body, cta } } } or markers as references to a 'location' document type. Sanity's geopoint input is equivalent.",
|
|
2675
|
+
htmlSignals: [
|
|
2676
|
+
"A Leaflet/MapLibre/Mapbox/Google Maps container (div.leaflet-container, .mapboxgl-map, iframe to maps.google.com/embed)",
|
|
2677
|
+
"Marker data in JSON (data-markers, window.locations) or repeated <li> with address + lat/lng data attributes",
|
|
2678
|
+
"Filter chips above the map toggling categories",
|
|
2679
|
+
"Popups with an image, a line of text and 'Directions' links",
|
|
2680
|
+
"LocalBusiness / Store JSON-LD per location"
|
|
2681
|
+
],
|
|
2682
|
+
mcp: "Read marker data from the JSON the map is initialised with (or the visible list); each → `marker` with geopoint {lat,lng,label}. Derive `categories` from distinct marker types/filters; set each marker's `category` to the generated key. Popup HTML → popup.image/body/button. If there are more than 50 places or the list is a CMS feed, create a Locations collection and set `source: collection` — each location becomes an entry (it has its own page, hours and JSON-LD). Never extract the provider API key; set `provider` only. A static map image with no interactivity is NOT this entry — model it as an image.",
|
|
2683
|
+
pitfalls: [
|
|
2684
|
+
"SCHEMA GAP: a marker's category should be a select whose options come from the sibling `categories` array (dynamic options); it is a patterned string with no integrity check.",
|
|
2685
|
+
"SCHEMA GAP: opening hours want a structured type (day-of-week ranges, exceptions/holidays) to emit LocalBusiness openingHoursSpecification; strings can't be validated against real times.",
|
|
2686
|
+
"SCHEMA GAP: no address type (street, locality, region, postcode, country) — needed for JSON-LD PostalAddress and geocoding; a multi-line text loses structure.",
|
|
2687
|
+
"Turning off `showList` leaves keyboard and screen-reader users with no way to reach the places."
|
|
2688
|
+
]
|
|
2689
|
+
}
|
|
2690
|
+
},
|
|
2691
|
+
{
|
|
2692
|
+
id: "product-compare-slider",
|
|
2693
|
+
label: "Product comparison slider",
|
|
2694
|
+
proves: "References to products · attributes pulled from referenced fields or typed per product · swipe on phones",
|
|
2695
|
+
category: "Data & interactive",
|
|
2696
|
+
tags: [
|
|
2697
|
+
"references",
|
|
2698
|
+
"repeater",
|
|
2699
|
+
"conditional",
|
|
2700
|
+
"validation",
|
|
2701
|
+
"commerce",
|
|
2702
|
+
"responsive-layout"
|
|
2703
|
+
],
|
|
2704
|
+
brand: "Caldera",
|
|
2705
|
+
previewLayout: "table",
|
|
2706
|
+
schema: {
|
|
2707
|
+
name: "comparisonSlider",
|
|
2708
|
+
title: "Comparison slider",
|
|
2709
|
+
kind: "block",
|
|
2710
|
+
icon: "arrows-left-right",
|
|
2711
|
+
category: "Data & interactive",
|
|
2712
|
+
description: "Two to four products side by side, swipeable on phones",
|
|
2713
|
+
variants: [{
|
|
2714
|
+
id: "columns",
|
|
2715
|
+
name: "Columns"
|
|
2716
|
+
}, {
|
|
2717
|
+
id: "slider",
|
|
2718
|
+
name: "Slider"
|
|
2719
|
+
}],
|
|
2720
|
+
preview: {
|
|
2721
|
+
title: "heading",
|
|
2722
|
+
subtitle: "intro"
|
|
2723
|
+
},
|
|
2724
|
+
groups: [
|
|
2725
|
+
{
|
|
2726
|
+
name: "content",
|
|
2727
|
+
title: "Content",
|
|
2728
|
+
default: true
|
|
2729
|
+
},
|
|
2730
|
+
{
|
|
2731
|
+
name: "rows",
|
|
2732
|
+
title: "Rows",
|
|
2733
|
+
icon: "list-details"
|
|
2734
|
+
},
|
|
2735
|
+
{
|
|
2736
|
+
name: "display",
|
|
2737
|
+
title: "Display"
|
|
2738
|
+
}
|
|
2739
|
+
],
|
|
2740
|
+
fields: [
|
|
2741
|
+
{
|
|
2742
|
+
name: "heading",
|
|
2743
|
+
title: "Heading",
|
|
2744
|
+
type: "string",
|
|
2745
|
+
group: "content",
|
|
2746
|
+
validation: {
|
|
2747
|
+
required: true,
|
|
2748
|
+
recommendedMax: 50
|
|
2749
|
+
},
|
|
2750
|
+
ai: true
|
|
2751
|
+
},
|
|
2752
|
+
{
|
|
2753
|
+
name: "intro",
|
|
2754
|
+
title: "Intro",
|
|
2755
|
+
type: "text",
|
|
2756
|
+
rows: 2,
|
|
2757
|
+
group: "content"
|
|
2758
|
+
},
|
|
2759
|
+
{
|
|
2760
|
+
name: "products",
|
|
2761
|
+
title: "Products",
|
|
2762
|
+
type: "array",
|
|
2763
|
+
group: "content",
|
|
2764
|
+
itemNoun: "product",
|
|
2765
|
+
validation: {
|
|
2766
|
+
min: 2,
|
|
2767
|
+
max: 4,
|
|
2768
|
+
unique: true
|
|
2769
|
+
},
|
|
2770
|
+
of: [{
|
|
2771
|
+
name: "product",
|
|
2772
|
+
title: "Product",
|
|
2773
|
+
type: "reference",
|
|
2774
|
+
reference: { to: ["col_products"] }
|
|
2775
|
+
}]
|
|
2776
|
+
},
|
|
2777
|
+
{
|
|
2778
|
+
name: "recommended",
|
|
2779
|
+
title: "Recommended",
|
|
2780
|
+
type: "reference",
|
|
2781
|
+
group: "content",
|
|
2782
|
+
reference: { to: ["col_products"] },
|
|
2783
|
+
description: "Must be one of the products above."
|
|
2784
|
+
},
|
|
2785
|
+
{
|
|
2786
|
+
name: "attributes",
|
|
2787
|
+
title: "Rows",
|
|
2788
|
+
type: "array",
|
|
2789
|
+
group: "rows",
|
|
2790
|
+
itemNoun: "row",
|
|
2791
|
+
validation: {
|
|
2792
|
+
min: 1,
|
|
2793
|
+
max: 14
|
|
2794
|
+
},
|
|
2795
|
+
of: [{
|
|
2796
|
+
name: "attribute",
|
|
2797
|
+
title: "Row",
|
|
2798
|
+
type: "object",
|
|
2799
|
+
icon: "list-details",
|
|
2800
|
+
preview: {
|
|
2801
|
+
title: "label",
|
|
2802
|
+
subtitle: "field",
|
|
2803
|
+
fallback: "Untitled row"
|
|
2804
|
+
},
|
|
2805
|
+
fields: [
|
|
2806
|
+
{
|
|
2807
|
+
name: "source",
|
|
2808
|
+
title: "Values come from",
|
|
2809
|
+
type: "select",
|
|
2810
|
+
layout: "segmented",
|
|
2811
|
+
initialValue: "field",
|
|
2812
|
+
options: [{
|
|
2813
|
+
value: "field",
|
|
2814
|
+
label: "Product field"
|
|
2815
|
+
}, {
|
|
2816
|
+
value: "custom",
|
|
2817
|
+
label: "Typed here"
|
|
2818
|
+
}]
|
|
2819
|
+
},
|
|
2820
|
+
{
|
|
2821
|
+
name: "field",
|
|
2822
|
+
title: "Product field",
|
|
2823
|
+
type: "select",
|
|
2824
|
+
hidden: {
|
|
2825
|
+
field: "source",
|
|
2826
|
+
equals: "field"
|
|
2827
|
+
},
|
|
2828
|
+
validation: { required: true },
|
|
2829
|
+
options: [
|
|
2830
|
+
{
|
|
2831
|
+
value: "capacity",
|
|
2832
|
+
label: "Capacity"
|
|
2833
|
+
},
|
|
2834
|
+
{
|
|
2835
|
+
value: "weight",
|
|
2836
|
+
label: "Weight"
|
|
2837
|
+
},
|
|
2838
|
+
{
|
|
2839
|
+
value: "material",
|
|
2840
|
+
label: "Material"
|
|
2841
|
+
},
|
|
2842
|
+
{
|
|
2843
|
+
value: "heatRating",
|
|
2844
|
+
label: "Heat rating"
|
|
2845
|
+
},
|
|
2846
|
+
{
|
|
2847
|
+
value: "induction",
|
|
2848
|
+
label: "Induction"
|
|
2849
|
+
},
|
|
2850
|
+
{
|
|
2851
|
+
value: "packedSize",
|
|
2852
|
+
label: "Packed size"
|
|
2853
|
+
},
|
|
2854
|
+
{
|
|
2855
|
+
value: "warranty",
|
|
2856
|
+
label: "Warranty"
|
|
2857
|
+
},
|
|
2858
|
+
{
|
|
2859
|
+
value: "price",
|
|
2860
|
+
label: "Price"
|
|
2861
|
+
}
|
|
2862
|
+
]
|
|
2863
|
+
},
|
|
2864
|
+
{
|
|
2865
|
+
name: "label",
|
|
2866
|
+
title: "Row label",
|
|
2867
|
+
type: "string",
|
|
2868
|
+
validation: { max: 32 },
|
|
2869
|
+
description: "Leave empty to use the field's name."
|
|
2870
|
+
},
|
|
2871
|
+
{
|
|
2872
|
+
name: "tooltip",
|
|
2873
|
+
title: "Explainer",
|
|
2874
|
+
type: "string",
|
|
2875
|
+
validation: { max: 90 }
|
|
2876
|
+
},
|
|
2877
|
+
{
|
|
2878
|
+
name: "values",
|
|
2879
|
+
title: "Values",
|
|
2880
|
+
type: "array",
|
|
2881
|
+
itemNoun: "value",
|
|
2882
|
+
openMode: "inline",
|
|
2883
|
+
hidden: {
|
|
2884
|
+
field: "source",
|
|
2885
|
+
equals: "custom"
|
|
2886
|
+
},
|
|
2887
|
+
validation: { max: 4 },
|
|
2888
|
+
of: [{
|
|
2889
|
+
name: "cellValue",
|
|
2890
|
+
title: "Value",
|
|
2891
|
+
type: "object",
|
|
2892
|
+
icon: "square",
|
|
2893
|
+
preview: {
|
|
2894
|
+
title: "value",
|
|
2895
|
+
fallback: "Untitled value"
|
|
2896
|
+
},
|
|
2897
|
+
fields: [{
|
|
2898
|
+
name: "product",
|
|
2899
|
+
title: "Product",
|
|
2900
|
+
type: "reference",
|
|
2901
|
+
reference: { to: ["col_products"] }
|
|
2902
|
+
}, {
|
|
2903
|
+
name: "value",
|
|
2904
|
+
title: "Value",
|
|
2905
|
+
type: "string",
|
|
2906
|
+
validation: {
|
|
2907
|
+
required: true,
|
|
2908
|
+
max: 40
|
|
2909
|
+
}
|
|
2910
|
+
}]
|
|
2911
|
+
}]
|
|
2912
|
+
},
|
|
2913
|
+
{
|
|
2914
|
+
name: "showDifferences",
|
|
2915
|
+
title: "Highlight differences",
|
|
2916
|
+
type: "boolean"
|
|
2917
|
+
}
|
|
2918
|
+
]
|
|
2919
|
+
}]
|
|
2920
|
+
},
|
|
2921
|
+
{
|
|
2922
|
+
name: "mobile",
|
|
2923
|
+
title: "On phones",
|
|
2924
|
+
type: "select",
|
|
2925
|
+
group: "display",
|
|
2926
|
+
layout: "radio",
|
|
2927
|
+
initialValue: "swipe",
|
|
2928
|
+
options: [
|
|
2929
|
+
{
|
|
2930
|
+
value: "swipe",
|
|
2931
|
+
label: "Swipe",
|
|
2932
|
+
description: "One product at a time, row labels stay put"
|
|
2933
|
+
},
|
|
2934
|
+
{
|
|
2935
|
+
value: "pair",
|
|
2936
|
+
label: "Pick two",
|
|
2937
|
+
description: "Two dropdowns choose which pair to compare"
|
|
2938
|
+
},
|
|
2939
|
+
{
|
|
2940
|
+
value: "stack",
|
|
2941
|
+
label: "Stack",
|
|
2942
|
+
description: "Each product as its own card"
|
|
2943
|
+
}
|
|
2944
|
+
]
|
|
2945
|
+
},
|
|
2946
|
+
{
|
|
2947
|
+
name: "isHeaderSticky",
|
|
2948
|
+
title: "Keep the header row in view",
|
|
2949
|
+
type: "boolean",
|
|
2950
|
+
group: "display",
|
|
2951
|
+
initialValue: true
|
|
2952
|
+
},
|
|
2953
|
+
{
|
|
2954
|
+
name: "showAddToCart",
|
|
2955
|
+
title: "Show add to cart buttons",
|
|
2956
|
+
type: "boolean",
|
|
2957
|
+
group: "display",
|
|
2958
|
+
initialValue: true
|
|
2959
|
+
}
|
|
2960
|
+
]
|
|
2961
|
+
},
|
|
2962
|
+
value: {
|
|
2963
|
+
_variant: "slider",
|
|
2964
|
+
heading: "Which one comes camping?",
|
|
2965
|
+
intro: "Three pieces, three kinds of trip. Here's how they stack up.",
|
|
2966
|
+
products: [
|
|
2967
|
+
"en_p1",
|
|
2968
|
+
"en_p2",
|
|
2969
|
+
"en_p3"
|
|
2970
|
+
],
|
|
2971
|
+
recommended: "en_p1",
|
|
2972
|
+
attributes: [
|
|
2973
|
+
{
|
|
2974
|
+
_key: "at1",
|
|
2975
|
+
source: "field",
|
|
2976
|
+
field: "weight",
|
|
2977
|
+
showDifferences: true
|
|
2978
|
+
},
|
|
2979
|
+
{
|
|
2980
|
+
_key: "at2",
|
|
2981
|
+
source: "field",
|
|
2982
|
+
field: "packedSize",
|
|
2983
|
+
label: "Packed",
|
|
2984
|
+
tooltip: "Measured folded or nested, without the sleeve."
|
|
2985
|
+
},
|
|
2986
|
+
{
|
|
2987
|
+
_key: "at3",
|
|
2988
|
+
source: "field",
|
|
2989
|
+
field: "heatRating",
|
|
2990
|
+
showDifferences: true
|
|
2991
|
+
},
|
|
2992
|
+
{
|
|
2993
|
+
_key: "at4",
|
|
2994
|
+
source: "field",
|
|
2995
|
+
field: "induction"
|
|
2996
|
+
},
|
|
2997
|
+
{
|
|
2998
|
+
_key: "at5",
|
|
2999
|
+
source: "custom",
|
|
3000
|
+
label: "Best for",
|
|
3001
|
+
values: [
|
|
3002
|
+
{
|
|
3003
|
+
_key: "v1",
|
|
3004
|
+
product: "en_p1",
|
|
3005
|
+
value: "Group trips, car camping"
|
|
3006
|
+
},
|
|
3007
|
+
{
|
|
3008
|
+
_key: "v2",
|
|
3009
|
+
product: "en_p2",
|
|
3010
|
+
value: "Stews, bread, base camp"
|
|
3011
|
+
},
|
|
3012
|
+
{
|
|
3013
|
+
_key: "v3",
|
|
3014
|
+
product: "en_p3",
|
|
3015
|
+
value: "Everyday, stove to fire"
|
|
3016
|
+
}
|
|
3017
|
+
]
|
|
3018
|
+
},
|
|
3019
|
+
{
|
|
3020
|
+
_key: "at6",
|
|
3021
|
+
source: "field",
|
|
3022
|
+
field: "warranty",
|
|
3023
|
+
label: "Warranty"
|
|
3024
|
+
},
|
|
3025
|
+
{
|
|
3026
|
+
_key: "at7",
|
|
3027
|
+
source: "field",
|
|
3028
|
+
field: "price"
|
|
3029
|
+
}
|
|
3030
|
+
],
|
|
3031
|
+
mobile: "swipe",
|
|
3032
|
+
isHeaderSticky: true,
|
|
3033
|
+
showAddToCart: true
|
|
3034
|
+
},
|
|
3035
|
+
notes: {
|
|
3036
|
+
why: "Comparison values belong to the PRODUCTS, not the section — so rows default to 'pull this field from each referenced product' and only rows that aren't product data ('Best for') are typed here, keyed by product reference. That keeps prices and weights in one place. Products are an array of references with min 2 / max 4 and uniqueness; the phone behavior is an explicit choice because a 4-column table is unreadable at 390px.",
|
|
3037
|
+
sanity: "Sanity: compare { products: array of reference→product (validation unique), rows[] { source, field (string list), values[] { product: reference, value } } }. Sanity can build `field` options from the referenced type's fields only in a custom input — same gap.",
|
|
3038
|
+
htmlSignals: [
|
|
3039
|
+
"A table or CSS grid whose column headers are product cards (image, name, price, button)",
|
|
3040
|
+
"Row labels for specs (Weight, Capacity, Warranty) repeated across products",
|
|
3041
|
+
"Horizontal scroll-snap on mobile (overflow-x: auto; scroll-snap-type: x) or 'swipe to compare' hints",
|
|
3042
|
+
"Ticks/crosses or 'Best value' highlighting in cells"
|
|
3043
|
+
],
|
|
3044
|
+
mcp: "Resolve each column header to an existing product entry (match by URL/handle/SKU) and add it to `products`; if a product doesn't exist, create it first — never type product names into the section. For each row: if the value matches a field on the product model (weight, price…), emit `source: field` with that field; otherwise `source: custom` with one value per product reference. Ticks → 'Yes'/'No' values or a boolean product field. Scroll-snap / swipe UI → `mobile: swipe`. If the page compares more than 4 products or more than 14 rows, it's a spec database: build a Products-table entry (data-table with collection source) instead.",
|
|
3045
|
+
pitfalls: [
|
|
3046
|
+
"SCHEMA GAP: `field` options should be generated from the referenced collection's fields (dynamic options); here they're a hard-coded list that drifts when the Products model changes.",
|
|
3047
|
+
"SCHEMA GAP: `recommended` must be one of `products` (constrained reference) — not expressible.",
|
|
3048
|
+
"SCHEMA GAP: array `unique: true` (no duplicate product) is declared but not enforced by the validator.",
|
|
3049
|
+
"SCHEMA GAP: custom `values` should have exactly one entry per product (a matrix keyed by another array); adding a fourth product leaves rows with three values and nothing warns."
|
|
3050
|
+
]
|
|
3051
|
+
}
|
|
3052
|
+
},
|
|
3053
|
+
{
|
|
3054
|
+
id: "countdown-live-counter",
|
|
3055
|
+
label: "Countdown and live counter",
|
|
3056
|
+
proves: "Datetime + time zone · what happens after zero (conditional) · live counter from a static rate or an API · aria-live",
|
|
3057
|
+
category: "Data & interactive",
|
|
3058
|
+
tags: [
|
|
3059
|
+
"conditional",
|
|
3060
|
+
"validation",
|
|
3061
|
+
"localized",
|
|
3062
|
+
"a11y"
|
|
3063
|
+
],
|
|
3064
|
+
brand: "Caldera",
|
|
3065
|
+
previewLayout: "centered",
|
|
3066
|
+
schema: {
|
|
3067
|
+
name: "countdown",
|
|
3068
|
+
title: "Countdown",
|
|
3069
|
+
kind: "block",
|
|
3070
|
+
icon: "hourglass",
|
|
3071
|
+
category: "Data & interactive",
|
|
3072
|
+
description: "Counts down to a launch, then swaps to a message or button",
|
|
3073
|
+
variants: [{
|
|
3074
|
+
id: "banner",
|
|
3075
|
+
name: "Banner"
|
|
3076
|
+
}, {
|
|
3077
|
+
id: "hero",
|
|
3078
|
+
name: "Large"
|
|
3079
|
+
}],
|
|
3080
|
+
preview: {
|
|
3081
|
+
title: "heading",
|
|
3082
|
+
subtitle: "target"
|
|
3083
|
+
},
|
|
3084
|
+
groups: [
|
|
3085
|
+
{
|
|
3086
|
+
name: "content",
|
|
3087
|
+
title: "Content",
|
|
3088
|
+
default: true
|
|
3089
|
+
},
|
|
3090
|
+
{
|
|
3091
|
+
name: "timer",
|
|
3092
|
+
title: "Timer",
|
|
3093
|
+
icon: "clock"
|
|
3094
|
+
},
|
|
3095
|
+
{
|
|
3096
|
+
name: "counter",
|
|
3097
|
+
title: "Live counter",
|
|
3098
|
+
icon: "activity"
|
|
3099
|
+
}
|
|
3100
|
+
],
|
|
3101
|
+
fields: [
|
|
3102
|
+
{
|
|
3103
|
+
name: "eyebrow",
|
|
3104
|
+
title: "Eyebrow",
|
|
3105
|
+
type: "string",
|
|
3106
|
+
group: "content",
|
|
3107
|
+
validation: { max: 32 }
|
|
3108
|
+
},
|
|
3109
|
+
{
|
|
3110
|
+
name: "heading",
|
|
3111
|
+
title: "Heading",
|
|
3112
|
+
type: "string",
|
|
3113
|
+
group: "content",
|
|
3114
|
+
validation: {
|
|
3115
|
+
required: true,
|
|
3116
|
+
recommendedMax: 50
|
|
3117
|
+
},
|
|
3118
|
+
ai: true,
|
|
3119
|
+
localized: true
|
|
3120
|
+
},
|
|
3121
|
+
{
|
|
3122
|
+
name: "body",
|
|
3123
|
+
title: "Body",
|
|
3124
|
+
type: "text",
|
|
3125
|
+
rows: 2,
|
|
3126
|
+
group: "content",
|
|
3127
|
+
localized: true
|
|
3128
|
+
},
|
|
3129
|
+
{
|
|
3130
|
+
name: "button",
|
|
3131
|
+
title: "Button",
|
|
3132
|
+
type: "button",
|
|
3133
|
+
group: "content"
|
|
3134
|
+
},
|
|
3135
|
+
{
|
|
3136
|
+
name: "target",
|
|
3137
|
+
title: "Counts down to",
|
|
3138
|
+
type: "datetime",
|
|
3139
|
+
group: "timer",
|
|
3140
|
+
validation: { required: true }
|
|
3141
|
+
},
|
|
3142
|
+
{
|
|
3143
|
+
name: "timezone",
|
|
3144
|
+
title: "Time zone",
|
|
3145
|
+
type: "select",
|
|
3146
|
+
group: "timer",
|
|
3147
|
+
initialValue: "America/Los_Angeles",
|
|
3148
|
+
description: "The launch happens at this zone's time for everyone.",
|
|
3149
|
+
options: [
|
|
3150
|
+
{
|
|
3151
|
+
value: "America/Los_Angeles",
|
|
3152
|
+
label: "Pacific (Bend)"
|
|
3153
|
+
},
|
|
3154
|
+
{
|
|
3155
|
+
value: "America/New_York",
|
|
3156
|
+
label: "Eastern"
|
|
3157
|
+
},
|
|
3158
|
+
{
|
|
3159
|
+
value: "Europe/London",
|
|
3160
|
+
label: "London"
|
|
3161
|
+
},
|
|
3162
|
+
{
|
|
3163
|
+
value: "UTC",
|
|
3164
|
+
label: "UTC"
|
|
3165
|
+
}
|
|
3166
|
+
]
|
|
3167
|
+
},
|
|
3168
|
+
{
|
|
3169
|
+
name: "units",
|
|
3170
|
+
title: "Show",
|
|
3171
|
+
type: "multiselect",
|
|
3172
|
+
group: "timer",
|
|
3173
|
+
initialValue: [
|
|
3174
|
+
"days",
|
|
3175
|
+
"hours",
|
|
3176
|
+
"minutes",
|
|
3177
|
+
"seconds"
|
|
3178
|
+
],
|
|
3179
|
+
options: [
|
|
3180
|
+
{
|
|
3181
|
+
value: "days",
|
|
3182
|
+
label: "Days"
|
|
3183
|
+
},
|
|
3184
|
+
{
|
|
3185
|
+
value: "hours",
|
|
3186
|
+
label: "Hours"
|
|
3187
|
+
},
|
|
3188
|
+
{
|
|
3189
|
+
value: "minutes",
|
|
3190
|
+
label: "Minutes"
|
|
3191
|
+
},
|
|
3192
|
+
{
|
|
3193
|
+
value: "seconds",
|
|
3194
|
+
label: "Seconds"
|
|
3195
|
+
}
|
|
3196
|
+
]
|
|
3197
|
+
},
|
|
3198
|
+
{
|
|
3199
|
+
name: "labels",
|
|
3200
|
+
title: "Unit labels",
|
|
3201
|
+
type: "object",
|
|
3202
|
+
group: "timer",
|
|
3203
|
+
icon: "language",
|
|
3204
|
+
openMode: "drill",
|
|
3205
|
+
fields: [
|
|
3206
|
+
{
|
|
3207
|
+
name: "days",
|
|
3208
|
+
title: "Days",
|
|
3209
|
+
type: "string",
|
|
3210
|
+
validation: { max: 12 },
|
|
3211
|
+
localized: true,
|
|
3212
|
+
placeholder: "days"
|
|
3213
|
+
},
|
|
3214
|
+
{
|
|
3215
|
+
name: "hours",
|
|
3216
|
+
title: "Hours",
|
|
3217
|
+
type: "string",
|
|
3218
|
+
validation: { max: 12 },
|
|
3219
|
+
localized: true,
|
|
3220
|
+
placeholder: "hrs"
|
|
3221
|
+
},
|
|
3222
|
+
{
|
|
3223
|
+
name: "minutes",
|
|
3224
|
+
title: "Minutes",
|
|
3225
|
+
type: "string",
|
|
3226
|
+
validation: { max: 12 },
|
|
3227
|
+
localized: true,
|
|
3228
|
+
placeholder: "min"
|
|
3229
|
+
},
|
|
3230
|
+
{
|
|
3231
|
+
name: "seconds",
|
|
3232
|
+
title: "Seconds",
|
|
3233
|
+
type: "string",
|
|
3234
|
+
validation: { max: 12 },
|
|
3235
|
+
localized: true,
|
|
3236
|
+
placeholder: "sec"
|
|
3237
|
+
}
|
|
3238
|
+
]
|
|
3239
|
+
},
|
|
3240
|
+
{
|
|
3241
|
+
name: "afterEnd",
|
|
3242
|
+
title: "When it reaches zero",
|
|
3243
|
+
type: "select",
|
|
3244
|
+
group: "timer",
|
|
3245
|
+
layout: "radio",
|
|
3246
|
+
initialValue: "message",
|
|
3247
|
+
options: [
|
|
3248
|
+
{
|
|
3249
|
+
value: "hide",
|
|
3250
|
+
label: "Hide the block"
|
|
3251
|
+
},
|
|
3252
|
+
{
|
|
3253
|
+
value: "message",
|
|
3254
|
+
label: "Show a message",
|
|
3255
|
+
description: "Swap the timer for new copy and a button"
|
|
3256
|
+
},
|
|
3257
|
+
{
|
|
3258
|
+
value: "keep",
|
|
3259
|
+
label: "Keep it at zero"
|
|
3260
|
+
}
|
|
3261
|
+
]
|
|
3262
|
+
},
|
|
3263
|
+
{
|
|
3264
|
+
name: "endedHeading",
|
|
3265
|
+
title: "Heading after launch",
|
|
3266
|
+
type: "string",
|
|
3267
|
+
group: "timer",
|
|
3268
|
+
hidden: {
|
|
3269
|
+
field: "afterEnd",
|
|
3270
|
+
equals: "message"
|
|
3271
|
+
},
|
|
3272
|
+
validation: {
|
|
3273
|
+
required: true,
|
|
3274
|
+
max: 60
|
|
3275
|
+
},
|
|
3276
|
+
localized: true
|
|
3277
|
+
},
|
|
3278
|
+
{
|
|
3279
|
+
name: "endedButton",
|
|
3280
|
+
title: "Button after launch",
|
|
3281
|
+
type: "button",
|
|
3282
|
+
group: "timer",
|
|
3283
|
+
hidden: {
|
|
3284
|
+
field: "afterEnd",
|
|
3285
|
+
equals: "message"
|
|
3286
|
+
}
|
|
3287
|
+
},
|
|
3288
|
+
{
|
|
3289
|
+
name: "counter",
|
|
3290
|
+
title: "Live counter",
|
|
3291
|
+
type: "object",
|
|
3292
|
+
group: "counter",
|
|
3293
|
+
icon: "activity",
|
|
3294
|
+
openMode: "drill",
|
|
3295
|
+
fields: [
|
|
3296
|
+
{
|
|
3297
|
+
name: "showLiveCounter",
|
|
3298
|
+
title: "Show a live counter",
|
|
3299
|
+
type: "boolean"
|
|
3300
|
+
},
|
|
3301
|
+
{
|
|
3302
|
+
name: "label",
|
|
3303
|
+
title: "Label",
|
|
3304
|
+
type: "string",
|
|
3305
|
+
hidden: {
|
|
3306
|
+
field: "showLiveCounter",
|
|
3307
|
+
equals: true
|
|
3308
|
+
},
|
|
3309
|
+
validation: { max: 40 },
|
|
3310
|
+
localized: true
|
|
3311
|
+
},
|
|
3312
|
+
{
|
|
3313
|
+
name: "source",
|
|
3314
|
+
title: "Number comes from",
|
|
3315
|
+
type: "select",
|
|
3316
|
+
layout: "segmented",
|
|
3317
|
+
hidden: {
|
|
3318
|
+
field: "showLiveCounter",
|
|
3319
|
+
equals: true
|
|
3320
|
+
},
|
|
3321
|
+
initialValue: "rate",
|
|
3322
|
+
options: [{
|
|
3323
|
+
value: "rate",
|
|
3324
|
+
label: "Start + rate"
|
|
3325
|
+
}, {
|
|
3326
|
+
value: "api",
|
|
3327
|
+
label: "Live API"
|
|
3328
|
+
}]
|
|
3329
|
+
},
|
|
3330
|
+
{
|
|
3331
|
+
name: "startValue",
|
|
3332
|
+
title: "Starts at",
|
|
3333
|
+
type: "number",
|
|
3334
|
+
hidden: {
|
|
3335
|
+
field: "source",
|
|
3336
|
+
equals: "rate"
|
|
3337
|
+
}
|
|
3338
|
+
},
|
|
3339
|
+
{
|
|
3340
|
+
name: "startedAt",
|
|
3341
|
+
title: "Counted from",
|
|
3342
|
+
type: "datetime",
|
|
3343
|
+
hidden: {
|
|
3344
|
+
field: "source",
|
|
3345
|
+
equals: "rate"
|
|
3346
|
+
}
|
|
3347
|
+
},
|
|
3348
|
+
{
|
|
3349
|
+
name: "perHour",
|
|
3350
|
+
title: "Adds per hour",
|
|
3351
|
+
type: "number",
|
|
3352
|
+
hidden: {
|
|
3353
|
+
field: "source",
|
|
3354
|
+
equals: "rate"
|
|
3355
|
+
},
|
|
3356
|
+
numeric: {
|
|
3357
|
+
min: 0,
|
|
3358
|
+
step: 1
|
|
3359
|
+
},
|
|
3360
|
+
description: "Only a real average. Fake urgency breaks EU law."
|
|
3361
|
+
},
|
|
3362
|
+
{
|
|
3363
|
+
name: "endpoint",
|
|
3364
|
+
title: "Endpoint",
|
|
3365
|
+
type: "url",
|
|
3366
|
+
hidden: {
|
|
3367
|
+
field: "source",
|
|
3368
|
+
equals: "api"
|
|
3369
|
+
},
|
|
3370
|
+
validation: {
|
|
3371
|
+
required: true,
|
|
3372
|
+
pattern: "^https://",
|
|
3373
|
+
patternMessage: "Use an https:// address, like https://your-site.com."
|
|
3374
|
+
},
|
|
3375
|
+
access: {
|
|
3376
|
+
view: "schema.view",
|
|
3377
|
+
edit: "schema.manage"
|
|
3378
|
+
},
|
|
3379
|
+
lockReason: "Set by a developer. It must return { \"value\": number }."
|
|
3380
|
+
},
|
|
3381
|
+
{
|
|
3382
|
+
name: "refreshSeconds",
|
|
3383
|
+
title: "Refresh every",
|
|
3384
|
+
type: "number",
|
|
3385
|
+
hidden: {
|
|
3386
|
+
field: "source",
|
|
3387
|
+
equals: "api"
|
|
3388
|
+
},
|
|
3389
|
+
numeric: {
|
|
3390
|
+
min: 10,
|
|
3391
|
+
max: 600,
|
|
3392
|
+
step: 5,
|
|
3393
|
+
unit: "s"
|
|
3394
|
+
}
|
|
3395
|
+
},
|
|
3396
|
+
{
|
|
3397
|
+
name: "format",
|
|
3398
|
+
title: "Format",
|
|
3399
|
+
type: "select",
|
|
3400
|
+
layout: "segmented",
|
|
3401
|
+
hidden: {
|
|
3402
|
+
field: "showLiveCounter",
|
|
3403
|
+
equals: true
|
|
3404
|
+
},
|
|
3405
|
+
initialValue: "full",
|
|
3406
|
+
options: [{
|
|
3407
|
+
value: "full",
|
|
3408
|
+
label: "12,431"
|
|
3409
|
+
}, {
|
|
3410
|
+
value: "compact",
|
|
3411
|
+
label: "12.4k"
|
|
3412
|
+
}]
|
|
3413
|
+
}
|
|
3414
|
+
]
|
|
3415
|
+
},
|
|
3416
|
+
{
|
|
3417
|
+
name: "announce",
|
|
3418
|
+
title: "Screen reader updates",
|
|
3419
|
+
type: "select",
|
|
3420
|
+
group: "timer",
|
|
3421
|
+
advanced: true,
|
|
3422
|
+
initialValue: "off",
|
|
3423
|
+
options: [{
|
|
3424
|
+
value: "off",
|
|
3425
|
+
label: "Off",
|
|
3426
|
+
description: "Recommended: a ticking timer announced every second is noise"
|
|
3427
|
+
}, {
|
|
3428
|
+
value: "minute",
|
|
3429
|
+
label: "Every minute"
|
|
3430
|
+
}]
|
|
3431
|
+
}
|
|
3432
|
+
]
|
|
3433
|
+
},
|
|
3434
|
+
value: {
|
|
3435
|
+
_variant: "banner",
|
|
3436
|
+
eyebrow: "Fall drop",
|
|
3437
|
+
heading: "The Ember 7qt lands Friday.",
|
|
3438
|
+
body: "Our biggest Dutch oven, enamelled in Ash Grey. The first run is 600 pieces.",
|
|
3439
|
+
button: {
|
|
3440
|
+
label: "Get a launch reminder",
|
|
3441
|
+
href: "#reminder",
|
|
3442
|
+
type: "section",
|
|
3443
|
+
style: "outline"
|
|
3444
|
+
},
|
|
3445
|
+
target: "2026-10-16T09:00:00-07:00[America/Los_Angeles]",
|
|
3446
|
+
timezone: "America/Los_Angeles",
|
|
3447
|
+
units: [
|
|
3448
|
+
"days",
|
|
3449
|
+
"hours",
|
|
3450
|
+
"minutes",
|
|
3451
|
+
"seconds"
|
|
3452
|
+
],
|
|
3453
|
+
labels: {
|
|
3454
|
+
days: "days",
|
|
3455
|
+
hours: "hrs",
|
|
3456
|
+
minutes: "min",
|
|
3457
|
+
seconds: "sec"
|
|
3458
|
+
},
|
|
3459
|
+
afterEnd: "message",
|
|
3460
|
+
endedHeading: "The Ember 7qt is here.",
|
|
3461
|
+
endedButton: {
|
|
3462
|
+
label: "Shop the Ember 7qt",
|
|
3463
|
+
href: "/products/ember-dutch-oven-7qt",
|
|
3464
|
+
type: "page",
|
|
3465
|
+
style: "primary",
|
|
3466
|
+
arrow: true
|
|
3467
|
+
},
|
|
3468
|
+
counter: {
|
|
3469
|
+
showLiveCounter: true,
|
|
3470
|
+
label: "people on the reminder list",
|
|
3471
|
+
source: "api",
|
|
3472
|
+
endpoint: "https://api.caldera.com/v1/launch/ember-7qt/subscribers",
|
|
3473
|
+
refreshSeconds: 60,
|
|
3474
|
+
format: "full"
|
|
3475
|
+
},
|
|
3476
|
+
announce: "off"
|
|
3477
|
+
},
|
|
3478
|
+
notes: {
|
|
3479
|
+
why: "The timer is a datetime plus an explicit time zone (a launch is 9am Pacific for everyone), and the zero moment is a real content state: the after-launch heading and button are conditional fields so the section never shows 00:00:00 for a week. The live counter is a drill-in object with its own nested conditionals (enabled → source → rate fields or endpoint), which is the pattern for any self-contained sub-feature. Unit labels are localized strings because 'hrs' is copy.",
|
|
3480
|
+
sanity: "Sanity: countdown { target: datetime, timezone: string list, afterEnd, endedHeading (hidden callback), counter { source, endpoint } }. Sanity's datetime is also zone-less; teams add a timezone field the same way.",
|
|
3481
|
+
htmlSignals: [
|
|
3482
|
+
"Digits grouped as dd:hh:mm:ss updated by setInterval, often with data-countdown / data-end attributes holding an ISO date",
|
|
3483
|
+
"Labels like 'days hrs min sec' under the digits",
|
|
3484
|
+
"'X people waiting' / 'Y sold' numbers polling an endpoint (fetch in a loop)",
|
|
3485
|
+
"Launch banner copy: 'Drops Friday', 'Ends in'"
|
|
3486
|
+
],
|
|
3487
|
+
mcp: "Read the target from data attributes or the JS (new Date('…')); if it has no offset, ask for or default the brand's zone and set `timezone` — never store a local time without one. Units shown → `units`; their labels → `labels`. Find what the script does at zero (hide/replace text) and map to `afterEnd`, extracting the replacement copy. A polling number → counter.source api (copy the endpoint, developer-locked); a number computed from a start + rate → source rate. If the counter is fabricated (random increments with no data), do not migrate it — flag it.",
|
|
3488
|
+
pitfalls: [
|
|
3489
|
+
"SCHEMA GAP: datetime has no time zone component; the zone is a separate select and nothing forces them to be read together (and DST is the renderer's job).",
|
|
3490
|
+
"SCHEMA GAP: conditions don't chain — `startValue` is shown when source = rate even if the parent `showLiveCounter` toggle is off, because a field can only test one sibling. Needs `hidden` with AND/OR or nested condition groups.",
|
|
3491
|
+
"SCHEMA GAP: no 'scheduled content' primitive — after-launch copy should be a scheduled change (release) rather than a second set of fields.",
|
|
3492
|
+
"Countdowns that reset per visitor are dark patterns (and illegal under EU UCPD guidance); the schema has no per-visitor mode on purpose."
|
|
3493
|
+
]
|
|
3494
|
+
}
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
id: "reader-poll",
|
|
3498
|
+
label: "Poll",
|
|
3499
|
+
proves: "Question type switches options vs scale vs open answer · results visibility · close date · one vote per reader",
|
|
3500
|
+
category: "Data & interactive",
|
|
3501
|
+
tags: [
|
|
3502
|
+
"form",
|
|
3503
|
+
"conditional",
|
|
3504
|
+
"repeater",
|
|
3505
|
+
"validation",
|
|
3506
|
+
"references"
|
|
3507
|
+
],
|
|
3508
|
+
brand: "Atlas Journal",
|
|
3509
|
+
previewLayout: "form",
|
|
3510
|
+
schema: {
|
|
3511
|
+
name: "poll",
|
|
3512
|
+
title: "Poll",
|
|
3513
|
+
kind: "block",
|
|
3514
|
+
icon: "chart-bar-popular",
|
|
3515
|
+
category: "Data & interactive",
|
|
3516
|
+
description: "One question with live results",
|
|
3517
|
+
variants: [{
|
|
3518
|
+
id: "card",
|
|
3519
|
+
name: "Card"
|
|
3520
|
+
}, {
|
|
3521
|
+
id: "inline",
|
|
3522
|
+
name: "In the story"
|
|
3523
|
+
}],
|
|
3524
|
+
preview: {
|
|
3525
|
+
title: "question",
|
|
3526
|
+
subtitle: "type",
|
|
3527
|
+
icon: "chart-bar-popular"
|
|
3528
|
+
},
|
|
3529
|
+
groups: [{
|
|
3530
|
+
name: "content",
|
|
3531
|
+
title: "Question",
|
|
3532
|
+
default: true
|
|
3533
|
+
}, {
|
|
3534
|
+
name: "rules",
|
|
3535
|
+
title: "Rules"
|
|
3536
|
+
}],
|
|
3537
|
+
fields: [
|
|
3538
|
+
{
|
|
3539
|
+
name: "question",
|
|
3540
|
+
title: "Question",
|
|
3541
|
+
type: "string",
|
|
3542
|
+
group: "content",
|
|
3543
|
+
validation: {
|
|
3544
|
+
required: true,
|
|
3545
|
+
max: 140
|
|
3546
|
+
},
|
|
3547
|
+
ai: true
|
|
3548
|
+
},
|
|
3549
|
+
{
|
|
3550
|
+
name: "context",
|
|
3551
|
+
title: "Context",
|
|
3552
|
+
type: "text",
|
|
3553
|
+
rows: 2,
|
|
3554
|
+
group: "content",
|
|
3555
|
+
validation: { recommendedMax: 200 }
|
|
3556
|
+
},
|
|
3557
|
+
{
|
|
3558
|
+
name: "type",
|
|
3559
|
+
title: "Answer type",
|
|
3560
|
+
type: "select",
|
|
3561
|
+
group: "content",
|
|
3562
|
+
layout: "cards",
|
|
3563
|
+
initialValue: "single",
|
|
3564
|
+
options: [
|
|
3565
|
+
{
|
|
3566
|
+
value: "single",
|
|
3567
|
+
label: "Pick one",
|
|
3568
|
+
icon: "circle-dot"
|
|
3569
|
+
},
|
|
3570
|
+
{
|
|
3571
|
+
value: "multiple",
|
|
3572
|
+
label: "Pick several",
|
|
3573
|
+
icon: "checkbox"
|
|
3574
|
+
},
|
|
3575
|
+
{
|
|
3576
|
+
value: "scale",
|
|
3577
|
+
label: "Scale",
|
|
3578
|
+
icon: "adjustments-horizontal",
|
|
3579
|
+
description: "1 to 5, 0 to 10"
|
|
3580
|
+
},
|
|
3581
|
+
{
|
|
3582
|
+
value: "open",
|
|
3583
|
+
label: "Written answer",
|
|
3584
|
+
icon: "message"
|
|
3585
|
+
}
|
|
3586
|
+
]
|
|
3587
|
+
},
|
|
3588
|
+
{
|
|
3589
|
+
name: "options",
|
|
3590
|
+
title: "Options",
|
|
3591
|
+
type: "array",
|
|
3592
|
+
group: "content",
|
|
3593
|
+
itemNoun: "option",
|
|
3594
|
+
hidden: {
|
|
3595
|
+
field: "type",
|
|
3596
|
+
equals: ["single", "multiple"]
|
|
3597
|
+
},
|
|
3598
|
+
validation: {
|
|
3599
|
+
min: 2,
|
|
3600
|
+
max: 8
|
|
3601
|
+
},
|
|
3602
|
+
of: [{
|
|
3603
|
+
name: "pollOption",
|
|
3604
|
+
title: "Option",
|
|
3605
|
+
type: "object",
|
|
3606
|
+
icon: "circle-dot",
|
|
3607
|
+
preview: {
|
|
3608
|
+
title: "label",
|
|
3609
|
+
media: "image",
|
|
3610
|
+
fallback: "Untitled option"
|
|
3611
|
+
},
|
|
3612
|
+
fields: [{
|
|
3613
|
+
name: "label",
|
|
3614
|
+
title: "Label",
|
|
3615
|
+
type: "string",
|
|
3616
|
+
validation: {
|
|
3617
|
+
required: true,
|
|
3618
|
+
max: 60
|
|
3619
|
+
}
|
|
3620
|
+
}, {
|
|
3621
|
+
name: "image",
|
|
3622
|
+
title: "Image",
|
|
3623
|
+
type: "image"
|
|
3624
|
+
}]
|
|
3625
|
+
}]
|
|
3626
|
+
},
|
|
3627
|
+
{
|
|
3628
|
+
name: "maxPicks",
|
|
3629
|
+
title: "At most",
|
|
3630
|
+
type: "number",
|
|
3631
|
+
group: "content",
|
|
3632
|
+
hidden: {
|
|
3633
|
+
field: "type",
|
|
3634
|
+
equals: "multiple"
|
|
3635
|
+
},
|
|
3636
|
+
numeric: {
|
|
3637
|
+
min: 2,
|
|
3638
|
+
max: 8,
|
|
3639
|
+
step: 1
|
|
3640
|
+
}
|
|
3641
|
+
},
|
|
3642
|
+
{
|
|
3643
|
+
name: "allowOther",
|
|
3644
|
+
title: "\"Other\" with a text box",
|
|
3645
|
+
type: "boolean",
|
|
3646
|
+
group: "content",
|
|
3647
|
+
hidden: {
|
|
3648
|
+
field: "type",
|
|
3649
|
+
equals: ["single", "multiple"]
|
|
3650
|
+
}
|
|
3651
|
+
},
|
|
3652
|
+
{
|
|
3653
|
+
name: "scale",
|
|
3654
|
+
title: "Scale",
|
|
3655
|
+
type: "object",
|
|
3656
|
+
group: "content",
|
|
3657
|
+
icon: "adjustments-horizontal",
|
|
3658
|
+
openMode: "drill",
|
|
3659
|
+
hidden: {
|
|
3660
|
+
field: "type",
|
|
3661
|
+
equals: "scale"
|
|
3662
|
+
},
|
|
3663
|
+
fields: [
|
|
3664
|
+
{
|
|
3665
|
+
name: "min",
|
|
3666
|
+
title: "From",
|
|
3667
|
+
type: "number",
|
|
3668
|
+
initialValue: 1,
|
|
3669
|
+
numeric: {
|
|
3670
|
+
min: 0,
|
|
3671
|
+
max: 1,
|
|
3672
|
+
step: 1
|
|
3673
|
+
}
|
|
3674
|
+
},
|
|
3675
|
+
{
|
|
3676
|
+
name: "max",
|
|
3677
|
+
title: "To",
|
|
3678
|
+
type: "number",
|
|
3679
|
+
initialValue: 5,
|
|
3680
|
+
numeric: {
|
|
3681
|
+
min: 3,
|
|
3682
|
+
max: 10,
|
|
3683
|
+
step: 1
|
|
3684
|
+
}
|
|
3685
|
+
},
|
|
3686
|
+
{
|
|
3687
|
+
name: "minLabel",
|
|
3688
|
+
title: "Low label",
|
|
3689
|
+
type: "string",
|
|
3690
|
+
validation: { max: 24 },
|
|
3691
|
+
placeholder: "Not at all"
|
|
3692
|
+
},
|
|
3693
|
+
{
|
|
3694
|
+
name: "maxLabel",
|
|
3695
|
+
title: "High label",
|
|
3696
|
+
type: "string",
|
|
3697
|
+
validation: { max: 24 },
|
|
3698
|
+
placeholder: "Every day"
|
|
3699
|
+
}
|
|
3700
|
+
]
|
|
3701
|
+
},
|
|
3702
|
+
{
|
|
3703
|
+
name: "placeholder",
|
|
3704
|
+
title: "Answer placeholder",
|
|
3705
|
+
type: "string",
|
|
3706
|
+
group: "content",
|
|
3707
|
+
hidden: {
|
|
3708
|
+
field: "type",
|
|
3709
|
+
equals: "open"
|
|
3710
|
+
},
|
|
3711
|
+
validation: { max: 60 }
|
|
3712
|
+
},
|
|
3713
|
+
{
|
|
3714
|
+
name: "useModeration",
|
|
3715
|
+
title: "Review answers before showing them",
|
|
3716
|
+
type: "boolean",
|
|
3717
|
+
group: "content",
|
|
3718
|
+
hidden: {
|
|
3719
|
+
field: "type",
|
|
3720
|
+
equals: "open"
|
|
3721
|
+
},
|
|
3722
|
+
initialValue: true
|
|
3723
|
+
},
|
|
3724
|
+
{
|
|
3725
|
+
name: "showResults",
|
|
3726
|
+
title: "Show results",
|
|
3727
|
+
type: "select",
|
|
3728
|
+
group: "rules",
|
|
3729
|
+
layout: "radio",
|
|
3730
|
+
initialValue: "afterVote",
|
|
3731
|
+
options: [
|
|
3732
|
+
{
|
|
3733
|
+
value: "afterVote",
|
|
3734
|
+
label: "After someone votes"
|
|
3735
|
+
},
|
|
3736
|
+
{
|
|
3737
|
+
value: "afterClose",
|
|
3738
|
+
label: "When the poll closes"
|
|
3739
|
+
},
|
|
3740
|
+
{
|
|
3741
|
+
value: "never",
|
|
3742
|
+
label: "Never",
|
|
3743
|
+
description: "Results stay in Forms › Submissions"
|
|
3744
|
+
}
|
|
3745
|
+
]
|
|
3746
|
+
},
|
|
3747
|
+
{
|
|
3748
|
+
name: "closesAt",
|
|
3749
|
+
title: "Closes",
|
|
3750
|
+
type: "datetime",
|
|
3751
|
+
group: "rules",
|
|
3752
|
+
description: "Leave empty to keep it open."
|
|
3753
|
+
},
|
|
3754
|
+
{
|
|
3755
|
+
name: "voters",
|
|
3756
|
+
title: "Who can vote",
|
|
3757
|
+
type: "select",
|
|
3758
|
+
group: "rules",
|
|
3759
|
+
layout: "segmented",
|
|
3760
|
+
initialValue: "anyone",
|
|
3761
|
+
options: [
|
|
3762
|
+
{
|
|
3763
|
+
value: "anyone",
|
|
3764
|
+
label: "Anyone"
|
|
3765
|
+
},
|
|
3766
|
+
{
|
|
3767
|
+
value: "members",
|
|
3768
|
+
label: "Signed in"
|
|
3769
|
+
},
|
|
3770
|
+
{
|
|
3771
|
+
value: "subscribers",
|
|
3772
|
+
label: "Subscribers"
|
|
3773
|
+
}
|
|
3774
|
+
]
|
|
3775
|
+
},
|
|
3776
|
+
{
|
|
3777
|
+
name: "useOneVote",
|
|
3778
|
+
title: "One vote per reader",
|
|
3779
|
+
type: "boolean",
|
|
3780
|
+
group: "rules",
|
|
3781
|
+
initialValue: true
|
|
3782
|
+
},
|
|
3783
|
+
{
|
|
3784
|
+
name: "thankYou",
|
|
3785
|
+
title: "Thank-you line",
|
|
3786
|
+
type: "string",
|
|
3787
|
+
group: "rules",
|
|
3788
|
+
validation: { max: 100 }
|
|
3789
|
+
},
|
|
3790
|
+
{
|
|
3791
|
+
name: "relatedStory",
|
|
3792
|
+
title: "Results story",
|
|
3793
|
+
type: "reference",
|
|
3794
|
+
group: "rules",
|
|
3795
|
+
reference: { to: ["col_journal"] },
|
|
3796
|
+
description: "Linked under the results once it's published."
|
|
3797
|
+
}
|
|
3798
|
+
]
|
|
3799
|
+
},
|
|
3800
|
+
value: {
|
|
3801
|
+
_variant: "inline",
|
|
3802
|
+
question: "How do you read Atlas most days?",
|
|
3803
|
+
context: "We're redesigning the morning edition. Your answer decides what we build first.",
|
|
3804
|
+
type: "single",
|
|
3805
|
+
options: [
|
|
3806
|
+
{
|
|
3807
|
+
_key: "o1",
|
|
3808
|
+
label: "The morning newsletter"
|
|
3809
|
+
},
|
|
3810
|
+
{
|
|
3811
|
+
_key: "o2",
|
|
3812
|
+
label: "The app, on the commute"
|
|
3813
|
+
},
|
|
3814
|
+
{
|
|
3815
|
+
_key: "o3",
|
|
3816
|
+
label: "The website, at my desk"
|
|
3817
|
+
},
|
|
3818
|
+
{
|
|
3819
|
+
_key: "o4",
|
|
3820
|
+
label: "The weekend print edition"
|
|
3821
|
+
}
|
|
3822
|
+
],
|
|
3823
|
+
allowOther: true,
|
|
3824
|
+
scale: {
|
|
3825
|
+
min: 1,
|
|
3826
|
+
max: 5
|
|
3827
|
+
},
|
|
3828
|
+
useModeration: true,
|
|
3829
|
+
showResults: "afterVote",
|
|
3830
|
+
closesAt: "2026-11-01T23:59:00-04:00[America/New_York]",
|
|
3831
|
+
voters: "anyone",
|
|
3832
|
+
useOneVote: true,
|
|
3833
|
+
thankYou: "Thanks. We'll publish what we learn on November 4.",
|
|
3834
|
+
relatedStory: "en_2"
|
|
3835
|
+
},
|
|
3836
|
+
notes: {
|
|
3837
|
+
why: "The answer type drives everything below it, so options, scale and the open-answer settings are mutually exclusive conditional branches of one `type` select. Rules (results visibility, close date, eligibility) are a separate tab because they change behavior, not copy. Votes are SUBMISSIONS — runtime data in Forms, never stored on the block — so the schema has no results fields at all.",
|
|
3838
|
+
sanity: "Sanity: poll { question, type, options[] (hidden unless single/multiple), scale { min, max } } with votes in an external store (Sanity is not a write-heavy database). Same split.",
|
|
3839
|
+
htmlSignals: [
|
|
3840
|
+
"A <form> with one question and radio/checkbox inputs, submitting via fetch, often with 'Vote' as the button",
|
|
3841
|
+
"Results rendered as horizontal percentage bars after submit",
|
|
3842
|
+
"'Poll closes …' text or a disabled state with 'Final results'",
|
|
3843
|
+
"Third-party poll scripts (Crowdsignal, Typeform embed) — map those to the embed entry instead"
|
|
3844
|
+
],
|
|
3845
|
+
mcp: "Question text → `question`; radio inputs → type single, checkboxes → multiple (+ maxPicks if a limit is stated), a range/star/NPS row → scale with min/max and end labels, textarea → open. Each option label (+ image) → `pollOption`. Never import vote counts. If the page has several questions in sequence, it's a SURVEY: create a form (Forms) with those fields and place a form block — do not nest questions in this poll block.",
|
|
3846
|
+
pitfalls: [
|
|
3847
|
+
"SCHEMA GAP: cross-field rules (scale.min < scale.max, maxPicks ≤ number of options, closesAt in the future) can't be written as validation.",
|
|
3848
|
+
"SCHEMA GAP: 'images on every option or none' is a whole-array rule; validation is per item.",
|
|
3849
|
+
"SCHEMA GAP: no read-only, platform-computed fields — editors can't see live tallies inside the schema form; results live in another surface.",
|
|
3850
|
+
"Multiple-question surveys modelled as nested arrays in a block become an unversioned form builder — use Forms."
|
|
3851
|
+
]
|
|
3852
|
+
}
|
|
3853
|
+
},
|
|
3854
|
+
{
|
|
3855
|
+
id: "safe-embed",
|
|
3856
|
+
label: "Embed (iframe, HTML, script)",
|
|
3857
|
+
proves: "Kind switches URL vs code · sandbox + permissions developer-locked · consent placeholder · required iframe title",
|
|
3858
|
+
category: "Data & interactive",
|
|
3859
|
+
tags: [
|
|
3860
|
+
"embed",
|
|
3861
|
+
"conditional",
|
|
3862
|
+
"a11y",
|
|
3863
|
+
"validation"
|
|
3864
|
+
],
|
|
3865
|
+
brand: "Northwind",
|
|
3866
|
+
previewLayout: "media",
|
|
3867
|
+
schema: {
|
|
3868
|
+
name: "embed",
|
|
3869
|
+
title: "Embed",
|
|
3870
|
+
kind: "block",
|
|
3871
|
+
icon: "frame",
|
|
3872
|
+
category: "Data & interactive",
|
|
3873
|
+
description: "Third-party content in a safe frame, with a consent placeholder",
|
|
3874
|
+
variants: [{
|
|
3875
|
+
id: "framed",
|
|
3876
|
+
name: "Framed"
|
|
3877
|
+
}, {
|
|
3878
|
+
id: "bare",
|
|
3879
|
+
name: "No frame"
|
|
3880
|
+
}],
|
|
3881
|
+
preview: {
|
|
3882
|
+
title: "title",
|
|
3883
|
+
subtitle: "kind",
|
|
3884
|
+
icon: "code"
|
|
3885
|
+
},
|
|
3886
|
+
groups: [
|
|
3887
|
+
{
|
|
3888
|
+
name: "content",
|
|
3889
|
+
title: "Content",
|
|
3890
|
+
default: true
|
|
3891
|
+
},
|
|
3892
|
+
{
|
|
3893
|
+
name: "security",
|
|
3894
|
+
title: "Security",
|
|
3895
|
+
icon: "shield-lock"
|
|
3896
|
+
},
|
|
3897
|
+
{
|
|
3898
|
+
name: "privacy",
|
|
3899
|
+
title: "Privacy",
|
|
3900
|
+
icon: "cookie"
|
|
3901
|
+
}
|
|
3902
|
+
],
|
|
3903
|
+
fields: [
|
|
3904
|
+
{
|
|
3905
|
+
name: "title",
|
|
3906
|
+
title: "Title",
|
|
3907
|
+
type: "string",
|
|
3908
|
+
group: "content",
|
|
3909
|
+
validation: {
|
|
3910
|
+
required: true,
|
|
3911
|
+
max: 80
|
|
3912
|
+
},
|
|
3913
|
+
description: "Not shown. Read by screen readers."
|
|
3914
|
+
},
|
|
3915
|
+
{
|
|
3916
|
+
name: "kind",
|
|
3917
|
+
title: "Type",
|
|
3918
|
+
type: "select",
|
|
3919
|
+
group: "content",
|
|
3920
|
+
layout: "cards",
|
|
3921
|
+
initialValue: "iframe",
|
|
3922
|
+
options: [
|
|
3923
|
+
{
|
|
3924
|
+
value: "iframe",
|
|
3925
|
+
label: "Page in a frame",
|
|
3926
|
+
icon: "app-window",
|
|
3927
|
+
description: "Paste the embed URL"
|
|
3928
|
+
},
|
|
3929
|
+
{
|
|
3930
|
+
value: "oembed",
|
|
3931
|
+
label: "Link preview",
|
|
3932
|
+
icon: "link",
|
|
3933
|
+
description: "YouTube, Figma, CodePen…"
|
|
3934
|
+
},
|
|
3935
|
+
{
|
|
3936
|
+
value: "html",
|
|
3937
|
+
label: "HTML",
|
|
3938
|
+
icon: "code",
|
|
3939
|
+
description: "Snippet from a provider"
|
|
3940
|
+
},
|
|
3941
|
+
{
|
|
3942
|
+
value: "script",
|
|
3943
|
+
label: "Script",
|
|
3944
|
+
icon: "file-code",
|
|
3945
|
+
description: "A widget loaded from a URL"
|
|
3946
|
+
}
|
|
3947
|
+
]
|
|
3948
|
+
},
|
|
3949
|
+
{
|
|
3950
|
+
name: "url",
|
|
3951
|
+
title: "Embed URL",
|
|
3952
|
+
type: "url",
|
|
3953
|
+
group: "content",
|
|
3954
|
+
hidden: {
|
|
3955
|
+
field: "kind",
|
|
3956
|
+
equals: ["iframe", "oembed"]
|
|
3957
|
+
},
|
|
3958
|
+
validation: {
|
|
3959
|
+
required: true,
|
|
3960
|
+
pattern: "^https://",
|
|
3961
|
+
patternMessage: "Use an https:// address, like https://your-site.com/embed."
|
|
3962
|
+
}
|
|
3963
|
+
},
|
|
3964
|
+
{
|
|
3965
|
+
name: "html",
|
|
3966
|
+
title: "HTML",
|
|
3967
|
+
type: "code",
|
|
3968
|
+
group: "content",
|
|
3969
|
+
hidden: {
|
|
3970
|
+
field: "kind",
|
|
3971
|
+
equals: "html"
|
|
3972
|
+
},
|
|
3973
|
+
validation: { required: true },
|
|
3974
|
+
access: { edit: "schema.manage" },
|
|
3975
|
+
lockReason: "Raw HTML runs on your site. A developer reviews it."
|
|
3976
|
+
},
|
|
3977
|
+
{
|
|
3978
|
+
name: "scriptSrc",
|
|
3979
|
+
title: "Script URL",
|
|
3980
|
+
type: "url",
|
|
3981
|
+
group: "content",
|
|
3982
|
+
hidden: {
|
|
3983
|
+
field: "kind",
|
|
3984
|
+
equals: "script"
|
|
3985
|
+
},
|
|
3986
|
+
validation: {
|
|
3987
|
+
required: true,
|
|
3988
|
+
pattern: "^https://",
|
|
3989
|
+
patternMessage: "Use an https:// script, like https://your-site.com/widget.js."
|
|
3990
|
+
},
|
|
3991
|
+
access: { edit: "schema.manage" },
|
|
3992
|
+
lockReason: "Scripts run with full access to the page. A developer reviews them."
|
|
3993
|
+
},
|
|
3994
|
+
{
|
|
3995
|
+
name: "size",
|
|
3996
|
+
title: "Size",
|
|
3997
|
+
type: "object",
|
|
3998
|
+
group: "content",
|
|
3999
|
+
icon: "aspect-ratio",
|
|
4000
|
+
fields: [{
|
|
4001
|
+
name: "ratio",
|
|
4002
|
+
title: "Shape",
|
|
4003
|
+
type: "select",
|
|
4004
|
+
layout: "segmented",
|
|
4005
|
+
initialValue: "16:9",
|
|
4006
|
+
options: [
|
|
4007
|
+
{
|
|
4008
|
+
value: "16:9",
|
|
4009
|
+
label: "16:9"
|
|
4010
|
+
},
|
|
4011
|
+
{
|
|
4012
|
+
value: "4:3",
|
|
4013
|
+
label: "4:3"
|
|
4014
|
+
},
|
|
4015
|
+
{
|
|
4016
|
+
value: "1:1",
|
|
4017
|
+
label: "1:1"
|
|
4018
|
+
},
|
|
4019
|
+
{
|
|
4020
|
+
value: "fixed",
|
|
4021
|
+
label: "Fixed"
|
|
4022
|
+
}
|
|
4023
|
+
]
|
|
4024
|
+
}, {
|
|
4025
|
+
name: "height",
|
|
4026
|
+
title: "Height",
|
|
4027
|
+
type: "number",
|
|
4028
|
+
hidden: {
|
|
4029
|
+
field: "ratio",
|
|
4030
|
+
equals: "fixed"
|
|
4031
|
+
},
|
|
4032
|
+
numeric: {
|
|
4033
|
+
unit: "px",
|
|
4034
|
+
min: 120,
|
|
4035
|
+
max: 1200,
|
|
4036
|
+
step: 10
|
|
4037
|
+
}
|
|
4038
|
+
}]
|
|
4039
|
+
},
|
|
4040
|
+
{
|
|
4041
|
+
name: "caption",
|
|
4042
|
+
title: "Caption",
|
|
4043
|
+
type: "string",
|
|
4044
|
+
group: "content",
|
|
4045
|
+
validation: { max: 140 }
|
|
4046
|
+
},
|
|
4047
|
+
{
|
|
4048
|
+
name: "fallback",
|
|
4049
|
+
title: "Fallback text",
|
|
4050
|
+
type: "text",
|
|
4051
|
+
rows: 2,
|
|
4052
|
+
group: "content",
|
|
4053
|
+
description: "Shown if the embed can't load or consent is declined."
|
|
4054
|
+
},
|
|
4055
|
+
{
|
|
4056
|
+
name: "sandbox",
|
|
4057
|
+
title: "Allow inside the frame",
|
|
4058
|
+
type: "multiselect",
|
|
4059
|
+
group: "security",
|
|
4060
|
+
hidden: {
|
|
4061
|
+
field: "kind",
|
|
4062
|
+
equals: ["iframe", "oembed"]
|
|
4063
|
+
},
|
|
4064
|
+
access: {
|
|
4065
|
+
view: "schema.view",
|
|
4066
|
+
edit: "schema.manage"
|
|
4067
|
+
},
|
|
4068
|
+
lockReason: "Loosening the sandbox is a security decision.",
|
|
4069
|
+
initialValue: ["allow-scripts", "allow-same-origin"],
|
|
4070
|
+
options: [
|
|
4071
|
+
{
|
|
4072
|
+
value: "allow-scripts",
|
|
4073
|
+
label: "Scripts"
|
|
4074
|
+
},
|
|
4075
|
+
{
|
|
4076
|
+
value: "allow-same-origin",
|
|
4077
|
+
label: "Same origin (cookies, storage)"
|
|
4078
|
+
},
|
|
4079
|
+
{
|
|
4080
|
+
value: "allow-forms",
|
|
4081
|
+
label: "Forms"
|
|
4082
|
+
},
|
|
4083
|
+
{
|
|
4084
|
+
value: "allow-popups",
|
|
4085
|
+
label: "Pop-ups"
|
|
4086
|
+
},
|
|
4087
|
+
{
|
|
4088
|
+
value: "allow-popups-to-escape-sandbox",
|
|
4089
|
+
label: "Pop-ups without the sandbox"
|
|
4090
|
+
},
|
|
4091
|
+
{
|
|
4092
|
+
value: "allow-downloads",
|
|
4093
|
+
label: "Downloads"
|
|
4094
|
+
},
|
|
4095
|
+
{
|
|
4096
|
+
value: "allow-presentation",
|
|
4097
|
+
label: "Presentation"
|
|
4098
|
+
}
|
|
4099
|
+
]
|
|
4100
|
+
},
|
|
4101
|
+
{
|
|
4102
|
+
name: "permissions",
|
|
4103
|
+
title: "Browser features",
|
|
4104
|
+
type: "multiselect",
|
|
4105
|
+
group: "security",
|
|
4106
|
+
hidden: {
|
|
4107
|
+
field: "kind",
|
|
4108
|
+
equals: ["iframe", "oembed"]
|
|
4109
|
+
},
|
|
4110
|
+
access: {
|
|
4111
|
+
view: "schema.view",
|
|
4112
|
+
edit: "schema.manage"
|
|
4113
|
+
},
|
|
4114
|
+
options: [
|
|
4115
|
+
{
|
|
4116
|
+
value: "fullscreen",
|
|
4117
|
+
label: "Full screen"
|
|
4118
|
+
},
|
|
4119
|
+
{
|
|
4120
|
+
value: "clipboard-write",
|
|
4121
|
+
label: "Copy to clipboard"
|
|
4122
|
+
},
|
|
4123
|
+
{
|
|
4124
|
+
value: "autoplay",
|
|
4125
|
+
label: "Autoplay"
|
|
4126
|
+
},
|
|
4127
|
+
{
|
|
4128
|
+
value: "camera",
|
|
4129
|
+
label: "Camera"
|
|
4130
|
+
},
|
|
4131
|
+
{
|
|
4132
|
+
value: "microphone",
|
|
4133
|
+
label: "Microphone"
|
|
4134
|
+
},
|
|
4135
|
+
{
|
|
4136
|
+
value: "payment",
|
|
4137
|
+
label: "Payment"
|
|
4138
|
+
}
|
|
4139
|
+
]
|
|
4140
|
+
},
|
|
4141
|
+
{
|
|
4142
|
+
name: "useLazyLoad",
|
|
4143
|
+
title: "Load when scrolled near",
|
|
4144
|
+
type: "boolean",
|
|
4145
|
+
group: "security",
|
|
4146
|
+
initialValue: true
|
|
4147
|
+
},
|
|
4148
|
+
{
|
|
4149
|
+
name: "consent",
|
|
4150
|
+
title: "Consent",
|
|
4151
|
+
type: "object",
|
|
4152
|
+
group: "privacy",
|
|
4153
|
+
icon: "cookie",
|
|
4154
|
+
openMode: "drill",
|
|
4155
|
+
fields: [
|
|
4156
|
+
{
|
|
4157
|
+
name: "isRequired",
|
|
4158
|
+
title: "Ask first",
|
|
4159
|
+
type: "boolean",
|
|
4160
|
+
initialValue: true
|
|
4161
|
+
},
|
|
4162
|
+
{
|
|
4163
|
+
name: "category",
|
|
4164
|
+
title: "Cookie category",
|
|
4165
|
+
type: "select",
|
|
4166
|
+
hidden: {
|
|
4167
|
+
field: "isRequired",
|
|
4168
|
+
equals: true
|
|
4169
|
+
},
|
|
4170
|
+
initialValue: "functional",
|
|
4171
|
+
options: [
|
|
4172
|
+
{
|
|
4173
|
+
value: "functional",
|
|
4174
|
+
label: "Functional"
|
|
4175
|
+
},
|
|
4176
|
+
{
|
|
4177
|
+
value: "analytics",
|
|
4178
|
+
label: "Analytics"
|
|
4179
|
+
},
|
|
4180
|
+
{
|
|
4181
|
+
value: "marketing",
|
|
4182
|
+
label: "Marketing"
|
|
4183
|
+
}
|
|
4184
|
+
]
|
|
4185
|
+
},
|
|
4186
|
+
{
|
|
4187
|
+
name: "provider",
|
|
4188
|
+
title: "Provider name",
|
|
4189
|
+
type: "string",
|
|
4190
|
+
hidden: {
|
|
4191
|
+
field: "isRequired",
|
|
4192
|
+
equals: true
|
|
4193
|
+
},
|
|
4194
|
+
validation: {
|
|
4195
|
+
required: true,
|
|
4196
|
+
max: 40
|
|
4197
|
+
},
|
|
4198
|
+
placeholder: "StackBlitz"
|
|
4199
|
+
},
|
|
4200
|
+
{
|
|
4201
|
+
name: "message",
|
|
4202
|
+
title: "Placeholder text",
|
|
4203
|
+
type: "text",
|
|
4204
|
+
rows: 2,
|
|
4205
|
+
hidden: {
|
|
4206
|
+
field: "isRequired",
|
|
4207
|
+
equals: true
|
|
4208
|
+
},
|
|
4209
|
+
validation: { max: 200 }
|
|
4210
|
+
},
|
|
4211
|
+
{
|
|
4212
|
+
name: "privacyLink",
|
|
4213
|
+
title: "Provider's privacy policy",
|
|
4214
|
+
type: "link",
|
|
4215
|
+
hidden: {
|
|
4216
|
+
field: "isRequired",
|
|
4217
|
+
equals: true
|
|
4218
|
+
}
|
|
4219
|
+
},
|
|
4220
|
+
{
|
|
4221
|
+
name: "previewImage",
|
|
4222
|
+
title: "Placeholder image",
|
|
4223
|
+
type: "image",
|
|
4224
|
+
hidden: {
|
|
4225
|
+
field: "isRequired",
|
|
4226
|
+
equals: true
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
]
|
|
4230
|
+
}
|
|
4231
|
+
]
|
|
4232
|
+
},
|
|
4233
|
+
value: {
|
|
4234
|
+
_variant: "framed",
|
|
4235
|
+
title: "Northwind SDK quickstart running in StackBlitz",
|
|
4236
|
+
kind: "iframe",
|
|
4237
|
+
url: "https://stackblitz.com/edit/northwind-sdk-quickstart?embed=1&file=src%2Findex.ts&view=editor",
|
|
4238
|
+
size: {
|
|
4239
|
+
ratio: "fixed",
|
|
4240
|
+
height: 560
|
|
4241
|
+
},
|
|
4242
|
+
caption: "Edit the API key line and press Run. Nothing leaves the sandbox.",
|
|
4243
|
+
fallback: "Open the quickstart on StackBlitz: https://stackblitz.com/edit/northwind-sdk-quickstart",
|
|
4244
|
+
sandbox: [
|
|
4245
|
+
"allow-scripts",
|
|
4246
|
+
"allow-same-origin",
|
|
4247
|
+
"allow-forms",
|
|
4248
|
+
"allow-popups"
|
|
4249
|
+
],
|
|
4250
|
+
permissions: ["clipboard-write", "fullscreen"],
|
|
4251
|
+
useLazyLoad: true,
|
|
4252
|
+
consent: {
|
|
4253
|
+
isRequired: true,
|
|
4254
|
+
category: "functional",
|
|
4255
|
+
provider: "StackBlitz",
|
|
4256
|
+
message: "This demo runs on StackBlitz, which sets cookies. Load it to try the SDK in your browser.",
|
|
4257
|
+
privacyLink: {
|
|
4258
|
+
type: "url",
|
|
4259
|
+
href: "https://stackblitz.com/privacy-policy",
|
|
4260
|
+
newTab: true
|
|
4261
|
+
},
|
|
4262
|
+
previewImage: pic("northwind-stackblitz", "Screenshot of the quickstart code editor with index.ts open", 1400, 800)
|
|
4263
|
+
}
|
|
4264
|
+
},
|
|
4265
|
+
notes: {
|
|
4266
|
+
why: "Embeds are the only block that runs someone else's code, so security choices (sandbox, permissions, raw HTML, script URLs) are a developer-locked tab and content choices (title, size, caption, fallback) stay editable. The kind select makes URL vs code branches exclusive. Consent is a drill-in object with its own conditionals: a block can stay compliant per embed instead of relying on a site-wide script blocker. The iframe title is required — without it the frame is unlabelled for screen readers.",
|
|
4267
|
+
sanity: "Sanity: embed { kind, url, html: code (@sanity/code-input), sandbox: array of strings with list options, consent { … } } with a field-level readOnly callback by role (`readOnly: ({currentUser}) => !isDev(currentUser)`). Ours is the same idea with readOnly: 'developer'.",
|
|
4268
|
+
htmlSignals: [
|
|
4269
|
+
"<iframe> with src on a third-party domain (stackblitz, codepen, figma, calendly, typeform, airtable, google maps)",
|
|
4270
|
+
"<script src> to a widget host next to a placeholder <div id=…> (Calendly, HubSpot forms, Intercom)",
|
|
4271
|
+
"<blockquote class=\"twitter-tweet|instagram-media\"> + a platform script (oEmbed providers)",
|
|
4272
|
+
"Consent wrappers: data-cookieconsent, data-category, class=\"cmplz-placeholder\", 'Load content' buttons"
|
|
4273
|
+
],
|
|
4274
|
+
mcp: "<iframe> → kind iframe: copy src (force https), title (if missing, write one from the provider + context — required), width/height → size (aspect if responsive wrapper padding-top %, else fixed px), sandbox and allow attributes → sandbox/permissions verbatim. Known oEmbed URLs (YouTube, Vimeo, Figma, CodePen, Tweets) → kind oembed with the canonical URL; video specifically → prefer the video block. Script widgets → kind script with the src; the inline config goes into `html` only if it's static. Never migrate tracking pixels, analytics tags or chat widgets as embeds — they belong to site settings (scripts). If a consent wrapper exists, map its category and text to `consent`.",
|
|
4275
|
+
pitfalls: [
|
|
4276
|
+
"SCHEMA GAP: no host allowlist validation — `pattern` checks https only; a real policy (allowed domains per site, CSP frame-src sync) has no schema expression.",
|
|
4277
|
+
"SCHEMA GAP: `readOnly: \"developer\"` locks whole fields; there's no 'marketers can REMOVE a sandbox flag but not ADD one' (monotonic permissions).",
|
|
4278
|
+
"SCHEMA GAP: the code field has no language setting in the schema (`html` vs `js`), so the editor guesses; see code-snippet-tabs.",
|
|
4279
|
+
"oEmbed providers change markup; store the URL, never the provider's HTML snapshot.",
|
|
4280
|
+
"allow-scripts + allow-same-origin on a same-origin URL removes the sandbox entirely — the help warns, nothing blocks it."
|
|
4281
|
+
]
|
|
4282
|
+
}
|
|
4283
|
+
},
|
|
4284
|
+
{
|
|
4285
|
+
id: "code-snippet-tabs",
|
|
4286
|
+
label: "Code snippet with tabs",
|
|
4287
|
+
proves: "Tabs per language/package manager · line highlights by pattern · synced tab groups · optional runnable playground",
|
|
4288
|
+
category: "Data & interactive",
|
|
4289
|
+
tags: [
|
|
4290
|
+
"repeater",
|
|
4291
|
+
"validation",
|
|
4292
|
+
"conditional",
|
|
4293
|
+
"embed"
|
|
4294
|
+
],
|
|
4295
|
+
brand: "Northwind",
|
|
4296
|
+
previewLayout: "tabs",
|
|
4297
|
+
schema: {
|
|
4298
|
+
name: "codeSnippet",
|
|
4299
|
+
title: "Code snippet",
|
|
4300
|
+
kind: "block",
|
|
4301
|
+
icon: "terminal-2",
|
|
4302
|
+
category: "Data & interactive",
|
|
4303
|
+
description: "One snippet in several languages, with copy and run",
|
|
4304
|
+
variants: [{
|
|
4305
|
+
id: "dark",
|
|
4306
|
+
name: "Dark"
|
|
4307
|
+
}, {
|
|
4308
|
+
id: "light",
|
|
4309
|
+
name: "Light"
|
|
4310
|
+
}],
|
|
4311
|
+
preview: {
|
|
4312
|
+
title: "heading",
|
|
4313
|
+
subtitle: "intro",
|
|
4314
|
+
icon: "terminal-2"
|
|
4315
|
+
},
|
|
4316
|
+
groups: [{
|
|
4317
|
+
name: "content",
|
|
4318
|
+
title: "Content",
|
|
4319
|
+
default: true
|
|
4320
|
+
}, {
|
|
4321
|
+
name: "behavior",
|
|
4322
|
+
title: "Behavior"
|
|
4323
|
+
}],
|
|
4324
|
+
fields: [
|
|
4325
|
+
{
|
|
4326
|
+
name: "heading",
|
|
4327
|
+
title: "Heading",
|
|
4328
|
+
type: "string",
|
|
4329
|
+
group: "content",
|
|
4330
|
+
validation: { recommendedMax: 60 }
|
|
4331
|
+
},
|
|
4332
|
+
{
|
|
4333
|
+
name: "intro",
|
|
4334
|
+
title: "Intro",
|
|
4335
|
+
type: "text",
|
|
4336
|
+
rows: 2,
|
|
4337
|
+
group: "content"
|
|
4338
|
+
},
|
|
4339
|
+
{
|
|
4340
|
+
name: "tabs",
|
|
4341
|
+
title: "Tabs",
|
|
4342
|
+
type: "array",
|
|
4343
|
+
group: "content",
|
|
4344
|
+
itemNoun: "tab",
|
|
4345
|
+
validation: {
|
|
4346
|
+
min: 1,
|
|
4347
|
+
max: 6
|
|
4348
|
+
},
|
|
4349
|
+
of: [{
|
|
4350
|
+
name: "codeTab",
|
|
4351
|
+
title: "Tab",
|
|
4352
|
+
type: "object",
|
|
4353
|
+
icon: "file-code",
|
|
4354
|
+
preview: {
|
|
4355
|
+
title: "label",
|
|
4356
|
+
subtitle: "filename",
|
|
4357
|
+
fallback: "Untitled tab"
|
|
4358
|
+
},
|
|
4359
|
+
fields: [
|
|
4360
|
+
{
|
|
4361
|
+
name: "label",
|
|
4362
|
+
title: "Tab label",
|
|
4363
|
+
type: "string",
|
|
4364
|
+
validation: {
|
|
4365
|
+
required: true,
|
|
4366
|
+
max: 16
|
|
4367
|
+
},
|
|
4368
|
+
placeholder: "npm"
|
|
4369
|
+
},
|
|
4370
|
+
{
|
|
4371
|
+
name: "language",
|
|
4372
|
+
title: "Language",
|
|
4373
|
+
type: "select",
|
|
4374
|
+
validation: { required: true },
|
|
4375
|
+
options: [
|
|
4376
|
+
{
|
|
4377
|
+
value: "bash",
|
|
4378
|
+
label: "Shell"
|
|
4379
|
+
},
|
|
4380
|
+
{
|
|
4381
|
+
value: "ts",
|
|
4382
|
+
label: "TypeScript"
|
|
4383
|
+
},
|
|
4384
|
+
{
|
|
4385
|
+
value: "js",
|
|
4386
|
+
label: "JavaScript"
|
|
4387
|
+
},
|
|
4388
|
+
{
|
|
4389
|
+
value: "python",
|
|
4390
|
+
label: "Python"
|
|
4391
|
+
},
|
|
4392
|
+
{
|
|
4393
|
+
value: "go",
|
|
4394
|
+
label: "Go"
|
|
4395
|
+
},
|
|
4396
|
+
{
|
|
4397
|
+
value: "json",
|
|
4398
|
+
label: "JSON"
|
|
4399
|
+
},
|
|
4400
|
+
{
|
|
4401
|
+
value: "html",
|
|
4402
|
+
label: "HTML"
|
|
4403
|
+
},
|
|
4404
|
+
{
|
|
4405
|
+
value: "css",
|
|
4406
|
+
label: "CSS"
|
|
4407
|
+
}
|
|
4408
|
+
]
|
|
4409
|
+
},
|
|
4410
|
+
{
|
|
4411
|
+
name: "filename",
|
|
4412
|
+
title: "File name",
|
|
4413
|
+
type: "string",
|
|
4414
|
+
validation: {
|
|
4415
|
+
max: 48,
|
|
4416
|
+
pattern: "^[\\w./-]+$",
|
|
4417
|
+
patternMessage: "Use letters, numbers, dots, slashes and hyphens, like src/index.ts."
|
|
4418
|
+
}
|
|
4419
|
+
},
|
|
4420
|
+
{
|
|
4421
|
+
name: "code",
|
|
4422
|
+
title: "Code",
|
|
4423
|
+
type: "code",
|
|
4424
|
+
validation: { required: true }
|
|
4425
|
+
},
|
|
4426
|
+
{
|
|
4427
|
+
name: "highlight",
|
|
4428
|
+
title: "Highlight lines",
|
|
4429
|
+
type: "string",
|
|
4430
|
+
validation: {
|
|
4431
|
+
pattern: "^\\d+(-\\d+)?(,\\s*\\d+(-\\d+)?)*$",
|
|
4432
|
+
patternMessage: "Use line numbers and ranges, like 1, 4-6."
|
|
4433
|
+
},
|
|
4434
|
+
placeholder: "3, 7-9"
|
|
4435
|
+
}
|
|
4436
|
+
]
|
|
4437
|
+
}]
|
|
4438
|
+
},
|
|
4439
|
+
{
|
|
4440
|
+
name: "syncGroup",
|
|
4441
|
+
title: "Sync group",
|
|
4442
|
+
type: "string",
|
|
4443
|
+
group: "behavior",
|
|
4444
|
+
advanced: true,
|
|
4445
|
+
validation: {
|
|
4446
|
+
pattern: "^[a-z0-9-]+$",
|
|
4447
|
+
patternMessage: "Use lowercase letters, numbers and hyphens, like camp-kitchen."
|
|
4448
|
+
},
|
|
4449
|
+
description: "Snippets with the same group switch tabs together."
|
|
4450
|
+
},
|
|
4451
|
+
{
|
|
4452
|
+
name: "showLineNumbers",
|
|
4453
|
+
title: "Show line numbers",
|
|
4454
|
+
type: "boolean",
|
|
4455
|
+
group: "behavior"
|
|
4456
|
+
},
|
|
4457
|
+
{
|
|
4458
|
+
name: "showCopyButton",
|
|
4459
|
+
title: "Show copy button",
|
|
4460
|
+
type: "boolean",
|
|
4461
|
+
group: "behavior",
|
|
4462
|
+
initialValue: true
|
|
4463
|
+
},
|
|
4464
|
+
{
|
|
4465
|
+
name: "useLineWrap",
|
|
4466
|
+
title: "Wrap long lines",
|
|
4467
|
+
type: "boolean",
|
|
4468
|
+
group: "behavior"
|
|
4469
|
+
},
|
|
4470
|
+
{
|
|
4471
|
+
name: "isRunnable",
|
|
4472
|
+
title: "Open in a playground",
|
|
4473
|
+
type: "boolean",
|
|
4474
|
+
group: "behavior"
|
|
4475
|
+
},
|
|
4476
|
+
{
|
|
4477
|
+
name: "playground",
|
|
4478
|
+
title: "Playground",
|
|
4479
|
+
type: "object",
|
|
4480
|
+
group: "behavior",
|
|
4481
|
+
icon: "player-play",
|
|
4482
|
+
openMode: "drill",
|
|
4483
|
+
hidden: {
|
|
4484
|
+
field: "isRunnable",
|
|
4485
|
+
equals: true
|
|
4486
|
+
},
|
|
4487
|
+
fields: [
|
|
4488
|
+
{
|
|
4489
|
+
name: "provider",
|
|
4490
|
+
title: "Provider",
|
|
4491
|
+
type: "select",
|
|
4492
|
+
layout: "segmented",
|
|
4493
|
+
initialValue: "stackblitz",
|
|
4494
|
+
options: [{
|
|
4495
|
+
value: "stackblitz",
|
|
4496
|
+
label: "StackBlitz"
|
|
4497
|
+
}, {
|
|
4498
|
+
value: "codesandbox",
|
|
4499
|
+
label: "CodeSandbox"
|
|
4500
|
+
}]
|
|
4501
|
+
},
|
|
4502
|
+
{
|
|
4503
|
+
name: "template",
|
|
4504
|
+
title: "Template",
|
|
4505
|
+
type: "select",
|
|
4506
|
+
initialValue: "node",
|
|
4507
|
+
options: [
|
|
4508
|
+
{
|
|
4509
|
+
value: "node",
|
|
4510
|
+
label: "Node"
|
|
4511
|
+
},
|
|
4512
|
+
{
|
|
4513
|
+
value: "vite-react",
|
|
4514
|
+
label: "Vite + React"
|
|
4515
|
+
},
|
|
4516
|
+
{
|
|
4517
|
+
value: "next",
|
|
4518
|
+
label: "Next.js"
|
|
4519
|
+
}
|
|
4520
|
+
]
|
|
4521
|
+
},
|
|
4522
|
+
{
|
|
4523
|
+
name: "openFile",
|
|
4524
|
+
title: "Open file",
|
|
4525
|
+
type: "string",
|
|
4526
|
+
validation: {
|
|
4527
|
+
pattern: "^[\\w./-]+$",
|
|
4528
|
+
patternMessage: "Use a file path, like src/index.ts."
|
|
4529
|
+
}
|
|
4530
|
+
},
|
|
4531
|
+
{
|
|
4532
|
+
name: "dependencies",
|
|
4533
|
+
title: "Dependencies",
|
|
4534
|
+
type: "array",
|
|
4535
|
+
itemNoun: "package",
|
|
4536
|
+
of: [{
|
|
4537
|
+
name: "dependency",
|
|
4538
|
+
title: "Package",
|
|
4539
|
+
type: "string"
|
|
4540
|
+
}],
|
|
4541
|
+
description: "Package names to install, e.g. @northwind/sdk."
|
|
4542
|
+
}
|
|
4543
|
+
]
|
|
4544
|
+
}
|
|
4545
|
+
]
|
|
4546
|
+
},
|
|
4547
|
+
value: {
|
|
4548
|
+
_variant: "dark",
|
|
4549
|
+
heading: "Install the SDK",
|
|
4550
|
+
intro: "Node 20 or newer. The SDK has no runtime dependencies.",
|
|
4551
|
+
tabs: [
|
|
4552
|
+
{
|
|
4553
|
+
_key: "t1",
|
|
4554
|
+
label: "npm",
|
|
4555
|
+
language: "bash",
|
|
4556
|
+
code: "npm install @northwind/sdk"
|
|
4557
|
+
},
|
|
4558
|
+
{
|
|
4559
|
+
_key: "t2",
|
|
4560
|
+
label: "pnpm",
|
|
4561
|
+
language: "bash",
|
|
4562
|
+
code: "pnpm add @northwind/sdk"
|
|
4563
|
+
},
|
|
4564
|
+
{
|
|
4565
|
+
_key: "t3",
|
|
4566
|
+
label: "bun",
|
|
4567
|
+
language: "bash",
|
|
4568
|
+
code: "bun add @northwind/sdk"
|
|
4569
|
+
},
|
|
4570
|
+
{
|
|
4571
|
+
_key: "t4",
|
|
4572
|
+
label: "First call",
|
|
4573
|
+
language: "ts",
|
|
4574
|
+
filename: "src/index.ts",
|
|
4575
|
+
code: "import { Northwind } from \"@northwind/sdk\";\n\nconst nw = new Northwind({ apiKey: process.env.NORTHWIND_KEY });\n\nconst orders = await nw.orders.list({ status: \"open\", limit: 20 });\nfor (const order of orders.data) {\n console.log(order.id, order.total);\n}",
|
|
4576
|
+
highlight: "3, 5"
|
|
4577
|
+
}
|
|
4578
|
+
],
|
|
4579
|
+
syncGroup: "package-manager",
|
|
4580
|
+
showLineNumbers: true,
|
|
4581
|
+
showCopyButton: true,
|
|
4582
|
+
useLineWrap: false,
|
|
4583
|
+
isRunnable: true,
|
|
4584
|
+
playground: {
|
|
4585
|
+
provider: "stackblitz",
|
|
4586
|
+
template: "node",
|
|
4587
|
+
openFile: "src/index.ts",
|
|
4588
|
+
dependencies: ["@northwind/sdk"]
|
|
4589
|
+
}
|
|
4590
|
+
},
|
|
4591
|
+
notes: {
|
|
4592
|
+
why: "Each tab is one file in one language, so language and filename sit with the code; highlights are a validated range string because that's how authors think about lines. Package-manager tabs across a docs page should switch together — a sync group key, not a global setting. The playground is a conditional drill-in object: most snippets aren't runnable and shouldn't show its settings.",
|
|
4593
|
+
sanity: "Sanity: codeSnippet { tabs[] { label, code: code (@sanity/code-input gives language + filename + highlightedLines natively) } }. Sanity's code input stores language and highlighted lines inside the code value — ours needs separate fields.",
|
|
4594
|
+
htmlSignals: [
|
|
4595
|
+
"<pre><code class=\"language-ts\"> blocks (Prism, Shiki, highlight.js markup: span.token, data-line)",
|
|
4596
|
+
"A tablist (role=\"tablist\") switching between <pre> panels labeled npm / pnpm / yarn / bun",
|
|
4597
|
+
"Copy buttons (navigator.clipboard) and filename headers above the code",
|
|
4598
|
+
"'Open in StackBlitz/CodeSandbox' buttons or forms posting to those hosts"
|
|
4599
|
+
],
|
|
4600
|
+
mcp: "Each tab panel → `codeTab`: label from the tab button, language from the class (language-xxx / data-language), filename from the header, code from the TEXT content (strip highlighting spans, keep whitespace exactly, decode entities). Highlighted lines (data-line, .highlighted) → `highlight` as ranges. Tabs labeled with package managers → `syncGroup: package-manager`. A single <pre> with no tabs is still this block with one tab — unless it's inside a long-form article, where it belongs in the rich-text body as a code block.",
|
|
4601
|
+
pitfalls: [
|
|
4602
|
+
"SCHEMA GAP: the `code` field type has no `language` (or filename / highlightedLines) option in the schema; the control lets the user pick HTML/CSS/JS/Liquid only. Languages like Shell, TS, Python, Go need a schema-level `code: { language, languages[] }` setting.",
|
|
4603
|
+
"SCHEMA GAP: tab labels should be unique within a snippet; `unique` isn't enforced.",
|
|
4604
|
+
"Highlight ranges aren't checked against the code's line count.",
|
|
4605
|
+
"Never store highlighted HTML (token spans) — store plain text and highlight at render."
|
|
4606
|
+
]
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
];
|
|
4610
|
+
//#endregion
|
|
4611
|
+
export { ENTRIES };
|
|
4612
|
+
|
|
4613
|
+
//# sourceMappingURL=data.entries.js.map
|