@sanity/themer 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle.css +745 -1
- package/dist/presets-CqBgS3A-.js.map +1 -1
- package/dist/tool.js +59 -18
- package/dist/tool.js.map +1 -1
- package/package.json +6 -2
package/dist/tool.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool.js","names":["() => lastGutter","() => {\n const element = ref.current\n\n if (!element) return undefined\n\n // The reserved gutter is the part of the box the content cannot use\n const measure = () => {\n lastGutter = element.offsetWidth - element.clientWidth\n setGutter(lastGutter)\n }\n\n measure()\n\n const observer = new ResizeObserver(measure)\n\n observer.observe(element)\n\n return () => observer.disconnect()\n }","[]","{\n left: space[padding],\n right: Math.max(0, space[padding] - gutter),\n bottom: space[padding],\n }","space[padding]","Math.max(0, space[padding] - gutter)","useMemo<ScrollAreaInset>(\n () => ({\n left: space[padding],\n right: Math.max(0, space[padding] - gutter),\n bottom: space[padding],\n }),\n [gutter, padding, space],\n )","{paddingRight: inset.right}","right","<ScrollAreaInsetContext.Provider value={inset}>{children}</ScrollAreaInsetContext.Provider>","ScrollAreaInsetContext.Provider","<Box\n className={root}\n flex={1}\n overflow=\"auto\"\n padding={padding}\n ref={ref}\n style={{paddingRight: inset.right}}\n >\n <ScrollAreaInsetContext.Provider value={inset}>{children}</ScrollAreaInsetContext.Provider>\n </Box>","{marginLeft: -left, marginRight: -right, marginBottom: -bottom}","-left","-right","-bottom","<div style={{marginLeft: -left, marginRight: -right, marginBottom: -bottom}}>\n {props.children}\n </div>","children","buildTheme(options)","useMemo(() => buildTheme(options), [options])","<MockStudio scheme=\"light\" />","<ThemeProvider scheme=\"light\" theme={theme} tone=\"default\">\n <MockStudio scheme=\"light\" />\n </ThemeProvider>","<MockStudio scheme=\"dark\" />","<ThemeProvider scheme=\"dark\" theme={theme} tone=\"default\">\n <MockStudio scheme=\"dark\" />\n </ThemeProvider>","<div aria-hidden className={root}>\n <ThemeProvider scheme=\"light\" theme={theme} tone=\"default\">\n <MockStudio scheme=\"light\" />\n </ThemeProvider>\n <ThemeProvider scheme=\"dark\" theme={theme} tone=\"default\">\n <MockStudio scheme=\"dark\" />\n </ThemeProvider>\n </div>","assignInlineVars({\n [colors.bg]: color.bg,\n [colors.fg]: color.fg,\n [colors.mutedFg]: color.muted.fg,\n [colors.border]: color.border,\n [colors.accent]: color.button.default.primary.enabled.bg,\n [colors.avatar]: color.avatar.blue.bg,\n [colors.selectedBg]: selected.bg,\n [colors.selectedFg]: selected.fg,\n [colors.inputBg]: inputColor.bg,\n [colors.inputBorder]: inputColor.border,\n })","bg","border","fg","<div className={logo} />","<div className={navbar}>\n <div className={logo} />\n <div className={line.fg} style={{width: '22%'}} />\n <div className={spacer} />\n <div className={avatar} />\n </div>","<div className={list}>\n {LIST_ROWS.map((width, index) => {\n const isSelected = index === 1\n\n return (\n <div className={listRow} data-selected={isSelected} key={width}>\n <div className={isSelected ? line.selected : line.muted} style={{width}} />\n </div>\n )\n })}\n </div>","(width, index) => {\n const isSelected = index === 1\n\n return (\n <div className={listRow} data-selected={isSelected} key={width}>\n <div className={isSelected ? line.selected : line.muted} style={{width}} />\n </div>\n )\n }","<div className={form}>\n <div className={line.fg} style={{width: '45%'}} />\n {FIELDS.map((width) => (\n <div className={field} key={width}>\n <div className={line.muted} style={{width}} />\n <div className={input} />\n </div>\n ))}\n </div>","(width) => (\n <div className={field} key={width}>\n <div className={line.muted} style={{width}} />\n <div className={input} />\n </div>\n )","<div className={body}>\n <div className={list}>\n {LIST_ROWS.map((width, index) => {\n const isSelected = index === 1\n\n return (\n <div className={listRow} data-selected={isSelected} key={width}>\n <div className={isSelected ? line.selected : line.muted} style={{width}} />\n </div>\n )\n })}\n </div>\n\n <div className={pane}>\n <div className={form}>\n <div className={line.fg} style={{width: '45%'}} />\n {FIELDS.map((width) => (\n <div className={field} key={width}>\n <div className={line.muted} style={{width}} />\n <div className={input} />\n </div>\n ))}\n </div>\n <div className={footer}>\n <div className={button} />\n </div>\n </div>\n </div>","<div\n className={studio[scheme]}\n style={assignInlineVars({\n [colors.bg]: color.bg,\n [colors.fg]: color.fg,\n [colors.mutedFg]: color.muted.fg,\n [colors.border]: color.border,\n [colors.accent]: color.button.default.primary.enabled.bg,\n [colors.avatar]: color.avatar.blue.bg,\n [colors.selectedBg]: selected.bg,\n [colors.selectedFg]: selected.fg,\n [colors.inputBg]: inputColor.bg,\n [colors.inputBorder]: inputColor.border,\n })}\n >\n <div className={navbar}>\n <div className={logo} />\n <div className={line.fg} style={{width: '22%'}} />\n <div className={spacer} />\n <div className={avatar} />\n </div>\n\n <div className={body}>\n <div className={list}>\n {LIST_ROWS.map((width, index) => {\n const isSelected = index === 1\n\n return (\n <div className={listRow} data-selected={isSelected} key={width}>\n <div className={isSelected ? line.selected : line.muted} style={{width}} />\n </div>\n )\n })}\n </div>\n\n <div className={pane}>\n <div className={form}>\n <div className={line.fg} style={{width: '45%'}} />\n {FIELDS.map((width) => (\n <div className={field} key={width}>\n <div className={line.muted} style={{width}} />\n <div className={input} />\n </div>\n ))}\n </div>\n <div className={footer}>\n <div className={button} />\n </div>\n </div>\n </div>\n </div>","studio[scheme]","width","<Card overflow=\"hidden\" radius={3} shadow={1}>\n <ThemePreview options={props.options} />\n </Card>","options","removed.map((theme) => (\n <Stack gap={3} key={theme.slug}>\n <Box className={removedThumbnail}>\n <ThemeThumbnail options={theme.options} />\n </Box>\n <Text align=\"center\" size={1} textOverflow=\"ellipsis\">\n {displayTitle(theme.title)}\n </Text>\n <Flex gap={2} justify=\"center\">\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.restore', slug: theme.slug})}\n padding={2}\n text=\"Restore\"\n />\n {theme.source === 'custom' && (\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.delete', slug: theme.slug})}\n padding={2}\n text=\"Delete\"\n tone=\"critical\"\n />\n )}\n </Flex>\n </Stack>\n ))","(theme) => (\n <Stack gap={3} key={theme.slug}>\n <Box className={removedThumbnail}>\n <ThemeThumbnail options={theme.options} />\n </Box>\n <Text align=\"center\" size={1} textOverflow=\"ellipsis\">\n {displayTitle(theme.title)}\n </Text>\n <Flex gap={2} justify=\"center\">\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.restore', slug: theme.slug})}\n padding={2}\n text=\"Restore\"\n />\n {theme.source === 'custom' && (\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.delete', slug: theme.slug})}\n padding={2}\n text=\"Delete\"\n tone=\"critical\"\n />\n )}\n </Flex>\n </Stack>\n )","<ScrollArea padding={3}>\n <Stack gap={5}>\n {removed.map((theme) => (\n <Stack gap={3} key={theme.slug}>\n <Box className={removedThumbnail}>\n <ThemeThumbnail options={theme.options} />\n </Box>\n <Text align=\"center\" size={1} textOverflow=\"ellipsis\">\n {displayTitle(theme.title)}\n </Text>\n <Flex gap={2} justify=\"center\">\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.restore', slug: theme.slug})}\n padding={2}\n text=\"Restore\"\n />\n {theme.source === 'custom' && (\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.delete', slug: theme.slug})}\n padding={2}\n text=\"Delete\"\n tone=\"critical\"\n />\n )}\n </Flex>\n </Stack>\n ))}\n </Stack>\n </ScrollArea>","<Text size={1}>{tooltip}</Text>","<Button\n aria-label={buttonProps.text === undefined ? tooltip : undefined}\n gap={2}\n padding={2}\n {...buttonProps}\n />","buttonProps.text === undefined ? tooltip : undefined","<Tooltip animate content={<Text size={1}>{tooltip}</Text>} placement=\"bottom\" portal>\n <Button\n aria-label={buttonProps.text === undefined ? tooltip : undefined}\n gap={2}\n padding={2}\n {...buttonProps}\n />\n </Tooltip>","() => inputRef.current?.click()","<TooltipButton {...buttonProps} icon={ImageIcon} onClick={() => inputRef.current?.click()} />","<input\n accept=\"image/*\"\n hidden\n onChange={(event) => {\n const file = event.currentTarget.files?.[0]\n\n // Picking the same file again should work, so the input forgets it\n event.currentTarget.value = ''\n\n if (file) onFile(file)\n }}\n ref={inputRef}\n type=\"file\"\n />","<>\n <TooltipButton {...buttonProps} icon={ImageIcon} onClick={() => inputRef.current?.click()} />\n <input\n accept=\"image/*\"\n hidden\n onChange={(event) => {\n const file = event.currentTarget.files?.[0]\n\n // Picking the same file again should work, so the input forgets it\n event.currentTarget.value = ''\n\n if (file) onFile(file)\n }}\n ref={inputRef}\n type=\"file\"\n />\n </>","() => {\n onPaletteRef.current = onPalette\n }","[onPalette]","() => {\n mountedRef.current = true\n\n return () => {\n mountedRef.current = false\n }\n }","[]","(file: File) => {\n const pick = ++latestPickRef.current\n\n setBusy(true)\n\n // Whether this pick still matters: it is the latest one and the\n // component that asked for it is still around\n const isCurrent = () => mountedRef.current && latestPickRef.current === pick\n\n const extract = async () => {\n const palette = extractImagePalette(await readImagePixels(file))\n\n if (!isCurrent()) return\n\n if (Object.values(palette).every((swatch) => swatch === null)) {\n toast.push({status: 'warning', title: 'Found no colors in that image'})\n } else {\n onPaletteRef.current(palette, file)\n }\n }\n\n extract()\n .catch(() => {\n if (isCurrent()) toast.push({status: 'error', title: 'Could not read that image'})\n })\n .finally(() => {\n if (isCurrent()) setBusy(false)\n })\n }","(swatch) => swatch === null","useCallback(\n (file: File) => {\n const pick = ++latestPickRef.current\n\n setBusy(true)\n\n // Whether this pick still matters: it is the latest one and the\n // component that asked for it is still around\n const isCurrent = () => mountedRef.current && latestPickRef.current === pick\n\n const extract = async () => {\n const palette = extractImagePalette(await readImagePixels(file))\n\n if (!isCurrent()) return\n\n if (Object.values(palette).every((swatch) => swatch === null)) {\n toast.push({status: 'warning', title: 'Found no colors in that image'})\n } else {\n onPaletteRef.current(palette, file)\n }\n }\n\n extract()\n .catch(() => {\n if (isCurrent()) toast.push({status: 'error', title: 'Could not read that image'})\n })\n .finally(() => {\n if (isCurrent()) setBusy(false)\n })\n },\n [toast],\n )","{busy, pickImage}","palette ? currentImageVariant(options, palette) : null","() => {\n const row = rowRef.current\n\n if (!row) return undefined\n\n const measure = () => {\n const visible =\n row.clientWidth - ROW_PADDING - (Math.ceil(VISIBLE_VARIANTS) - 1) * VARIANT_GAP\n\n // Each variant is its thumbnail plus the frame on either side\n setThumbnailWidth(Math.max(24, Math.floor(visible / VISIBLE_VARIANTS) - VARIANT_FRAME * 2))\n }\n\n measure()\n\n const observer = new ResizeObserver(measure)\n\n observer.observe(row)\n\n return () => observer.disconnect()\n }","[palette]","useMemo(() => {\n if (!palette) return []\n\n return IMAGE_PALETTE_VARIANTS.flatMap((variant) =>\n palette[variant] === null\n ? []\n : [\n {\n variant,\n title: IMAGE_PALETTE_TITLES[variant],\n options: applyImagePalette(options, palette, variant),\n },\n ],\n )\n }, [options, palette])","[]","IMAGE_PALETTE_VARIANTS.flatMap((variant) =>\n palette[variant] === null\n ? []\n : [\n {\n variant,\n title: IMAGE_PALETTE_TITLES[variant],\n options: applyImagePalette(options, palette, variant),\n },\n ],\n )","(event) => {\n event.preventDefault()\n dragDepth.current++\n setDragging(true)\n }","() => {\n dragDepth.current = Math.max(0, dragDepth.current - 1)\n\n if (dragDepth.current === 0) setDragging(false)\n }","(event) => {\n event.preventDefault()\n dragDepth.current = 0\n setDragging(false)\n\n const file = event.dataTransfer.files[0]\n\n if (file) pickImage(file)\n }","event","<Box flex={1}>\n <Text size={1} weight=\"medium\">\n Image palette\n </Text>\n </Box>","palette && (\n <ImageFileButton\n loading={busy}\n mode=\"bleed\"\n onFile={pickImage}\n padding={2}\n tooltip=\"Pick another image\"\n />\n )","<Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n Image palette\n </Text>\n </Box>\n {palette && (\n <ImageFileButton\n loading={busy}\n mode=\"bleed\"\n onFile={pickImage}\n padding={2}\n tooltip=\"Pick another image\"\n />\n )}\n </Flex>","palette ? (\n <>\n <div className={paletteGrid} data-with-image={imageUrl !== undefined}>\n {imageUrl && <img alt=\"\" className={imageTile} src={imageUrl} />}\n {TILES.map((key) => {\n const hex = palette[key]\n const title = IMAGE_PALETTE_TITLES[key]\n\n return (\n <Tooltip\n animate\n content={\n <Text size={1}>\n {hex ? `${title} · ${hex}` : `No ${title.toLowerCase()} color`}\n </Text>\n }\n key={key}\n placement=\"bottom\"\n portal\n >\n <span\n className={tile}\n data-empty={hex === null}\n style={{background: hex ?? undefined}}\n />\n </Tooltip>\n )\n })}\n </div>\n\n <Stack gap={2}>\n <Text muted size={0}>\n Pick the swatch to build the theme around\n </Text>\n <div\n className={variantRow}\n ref={rowRef}\n style={assignInlineVars({\n [variantWidth]: `${thumbnailWidth}px`,\n [variantScale]: String(thumbnailWidth / VARIANT_WIDTH),\n })}\n >\n {variants.map(({variant, title, options: variantOptions}) => (\n // oxlint-disable-next-line control-has-associated-label -- the title below the thumbnail is the label, deeper than the rule looks\n <button\n aria-pressed={variant === current}\n className={variantButton}\n key={variant}\n onClick={() => onVariant(variant)}\n type=\"button\"\n >\n <span className={variantFrame}>\n <span className={variantScaleBox}>\n <ThemeThumbnail options={variantOptions} />\n </span>\n </span>\n <Box as=\"span\" display=\"block\" paddingTop={1}>\n <Text\n align=\"center\"\n as=\"span\"\n size={0}\n textOverflow=\"ellipsis\"\n weight={variant === current ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>\n </button>\n ))}\n </div>\n </Stack>\n\n <TooltipButton\n icon={SparklesIcon}\n mode=\"ghost\"\n onClick={onLucky}\n text=\"I'm feeling lucky\"\n tooltip=\"Build the theme around a swatch that looks interesting\"\n width=\"fill\"\n />\n </>\n ) : (\n <Stack gap={3}>\n <Text muted size={0}>\n Take the accent, text and background colors from an image — read on device, nothing is\n uploaded.\n </Text>\n <ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n text=\"Pick an image\"\n tooltip=\"Take the colors from an image on your device\"\n width=\"fill\"\n />\n </Stack>\n )","{variant, title, options: variantOptions}","variant","title","<Stack gap={3}>\n <Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n Image palette\n </Text>\n </Box>\n {palette && (\n <ImageFileButton\n loading={busy}\n mode=\"bleed\"\n onFile={pickImage}\n padding={2}\n tooltip=\"Pick another image\"\n />\n )}\n </Flex>\n\n {palette ? (\n <>\n <div className={paletteGrid} data-with-image={imageUrl !== undefined}>\n {imageUrl && <img alt=\"\" className={imageTile} src={imageUrl} />}\n {TILES.map((key) => {\n const hex = palette[key]\n const title = IMAGE_PALETTE_TITLES[key]\n\n return (\n <Tooltip\n animate\n content={\n <Text size={1}>\n {hex ? `${title} · ${hex}` : `No ${title.toLowerCase()} color`}\n </Text>\n }\n key={key}\n placement=\"bottom\"\n portal\n >\n <span\n className={tile}\n data-empty={hex === null}\n style={{background: hex ?? undefined}}\n />\n </Tooltip>\n )\n })}\n </div>\n\n <Stack gap={2}>\n <Text muted size={0}>\n Pick the swatch to build the theme around\n </Text>\n <div\n className={variantRow}\n ref={rowRef}\n style={assignInlineVars({\n [variantWidth]: `${thumbnailWidth}px`,\n [variantScale]: String(thumbnailWidth / VARIANT_WIDTH),\n })}\n >\n {variants.map(({variant, title, options: variantOptions}) => (\n // oxlint-disable-next-line control-has-associated-label -- the title below the thumbnail is the label, deeper than the rule looks\n <button\n aria-pressed={variant === current}\n className={variantButton}\n key={variant}\n onClick={() => onVariant(variant)}\n type=\"button\"\n >\n <span className={variantFrame}>\n <span className={variantScaleBox}>\n <ThemeThumbnail options={variantOptions} />\n </span>\n </span>\n <Box as=\"span\" display=\"block\" paddingTop={1}>\n <Text\n align=\"center\"\n as=\"span\"\n size={0}\n textOverflow=\"ellipsis\"\n weight={variant === current ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>\n </button>\n ))}\n </div>\n </Stack>\n\n <TooltipButton\n icon={SparklesIcon}\n mode=\"ghost\"\n onClick={onLucky}\n text=\"I'm feeling lucky\"\n tooltip=\"Build the theme around a swatch that looks interesting\"\n width=\"fill\"\n />\n </>\n ) : (\n <Stack gap={3}>\n <Text muted size={0}>\n Take the accent, text and background colors from an image — read on device, nothing is\n uploaded.\n </Text>\n <ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n text=\"Pick an image\"\n tooltip=\"Take the colors from an image on your device\"\n width=\"fill\"\n />\n </Stack>\n )}\n </Stack>","<Card\n border\n onDragEnter={(event) => {\n event.preventDefault()\n dragDepth.current++\n setDragging(true)\n }}\n onDragLeave={() => {\n dragDepth.current = Math.max(0, dragDepth.current - 1)\n\n if (dragDepth.current === 0) setDragging(false)\n }}\n onDragOver={(event) => {\n event.preventDefault()\n }}\n onDrop={(event) => {\n event.preventDefault()\n dragDepth.current = 0\n setDragging(false)\n\n const file = event.dataTransfer.files[0]\n\n if (file) pickImage(file)\n }}\n padding={3}\n radius={3}\n tone={dragging ? 'primary' : 'default'}\n >\n <Stack gap={3}>\n <Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n Image palette\n </Text>\n </Box>\n {palette && (\n <ImageFileButton\n loading={busy}\n mode=\"bleed\"\n onFile={pickImage}\n padding={2}\n tooltip=\"Pick another image\"\n />\n )}\n </Flex>\n\n {palette ? (\n <>\n <div className={paletteGrid} data-with-image={imageUrl !== undefined}>\n {imageUrl && <img alt=\"\" className={imageTile} src={imageUrl} />}\n {TILES.map((key) => {\n const hex = palette[key]\n const title = IMAGE_PALETTE_TITLES[key]\n\n return (\n <Tooltip\n animate\n content={\n <Text size={1}>\n {hex ? `${title} · ${hex}` : `No ${title.toLowerCase()} color`}\n </Text>\n }\n key={key}\n placement=\"bottom\"\n portal\n >\n <span\n className={tile}\n data-empty={hex === null}\n style={{background: hex ?? undefined}}\n />\n </Tooltip>\n )\n })}\n </div>\n\n <Stack gap={2}>\n <Text muted size={0}>\n Pick the swatch to build the theme around\n </Text>\n <div\n className={variantRow}\n ref={rowRef}\n style={assignInlineVars({\n [variantWidth]: `${thumbnailWidth}px`,\n [variantScale]: String(thumbnailWidth / VARIANT_WIDTH),\n })}\n >\n {variants.map(({variant, title, options: variantOptions}) => (\n // oxlint-disable-next-line control-has-associated-label -- the title below the thumbnail is the label, deeper than the rule looks\n <button\n aria-pressed={variant === current}\n className={variantButton}\n key={variant}\n onClick={() => onVariant(variant)}\n type=\"button\"\n >\n <span className={variantFrame}>\n <span className={variantScaleBox}>\n <ThemeThumbnail options={variantOptions} />\n </span>\n </span>\n <Box as=\"span\" display=\"block\" paddingTop={1}>\n <Text\n align=\"center\"\n as=\"span\"\n size={0}\n textOverflow=\"ellipsis\"\n weight={variant === current ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>\n </button>\n ))}\n </div>\n </Stack>\n\n <TooltipButton\n icon={SparklesIcon}\n mode=\"ghost\"\n onClick={onLucky}\n text=\"I'm feeling lucky\"\n tooltip=\"Build the theme around a swatch that looks interesting\"\n width=\"fill\"\n />\n </>\n ) : (\n <Stack gap={3}>\n <Text muted size={0}>\n Take the accent, text and background colors from an image — read on device, nothing is\n uploaded.\n </Text>\n <ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n text=\"Pick an image\"\n tooltip=\"Take the colors from an image on your device\"\n width=\"fill\"\n />\n </Stack>\n )}\n </Stack>\n </Card>","dragging ? 'primary' : 'default'","(event) => {\n event.preventDefault()\n }","themes.find((candidate) => candidate.slug === slug && candidate.source === 'custom')","(candidate) => candidate.slug === slug && candidate.source === 'custom'","(variant: ImagePaletteVariant | null) => {\n if (!theme.palette || variant === null) return\n\n send({\n type: 'theme.update',\n slug,\n options: applyImagePalette(theme.options, theme.palette, variant),\n })\n }","options","palette","() => send({type: 'flow.list'})","() => {\n if (!theme.palette) return\n\n applyVariant(\n pickLuckyVariant(theme.palette, {\n exclude: currentImageVariant(theme.options, theme.palette),\n }),\n )\n }","(options) => send({type: 'theme.update', slug, options})","(palette, file) =>\n send({\n type: 'theme.update',\n slug,\n options: applyImagePalette(theme.options, palette),\n palette,\n imageUrl: URL.createObjectURL(file),\n })","() => send({type: 'theme.remove', slug})","(title) => send({type: 'theme.update', slug, title})","<ThemeEditorForm\n focusTitle={focusTitle}\n imageUrl={images[slug]}\n onDone={() => send({type: 'flow.list'})}\n onLucky={() => {\n if (!theme.palette) return\n\n applyVariant(\n pickLuckyVariant(theme.palette, {\n exclude: currentImageVariant(theme.options, theme.palette),\n }),\n )\n }}\n onOptionsChange={(options) => send({type: 'theme.update', slug, options})}\n onPalette={(palette, file) =>\n send({\n type: 'theme.update',\n slug,\n options: applyImagePalette(theme.options, palette),\n palette,\n imageUrl: URL.createObjectURL(file),\n })\n }\n onRemove={() => send({type: 'theme.remove', slug})}\n onTitleChange={(title) => send({type: 'theme.update', slug, title})}\n onVariant={applyVariant}\n options={theme.options}\n palette={theme.palette}\n split={split}\n mobile={mobile}\n title={theme.title}\n />","images[slug]","title","resolveThemeOptions(options)","useMemo(() => resolveThemeOptions(options), [options])","buildPalette(options)","useMemo(() => buildPalette(options), [options])","(scheme: ThemeColorSchemeKey, changes: Partial<SchemeThemeOptions>) => {\n const values: SchemeThemeOptions = {...options[scheme], ...changes}\n\n // Clearing an option means falling back to its default, which is its absence\n for (const key of SCHEME_OPTION_KEYS) {\n if (values[key] === undefined) delete values[key]\n }\n\n const next: BuildThemeOptions = {...options}\n\n if (Object.keys(values).length === 0) {\n delete next[scheme]\n } else {\n next[scheme] = values\n }\n\n onOptionsChange(next)\n }","<ThemeThumbnail options={options} />","<Text size={1} weight=\"medium\">\n Title\n </Text>","(event) => onTitleChange(event.currentTarget.value)","(event) => event.currentTarget.select()","<Stack gap={2}>\n <Text size={1} weight=\"medium\">\n Title\n </Text>\n <TextInput\n autoFocus={focusTitle}\n fontSize={1}\n onChange={(event) => onTitleChange(event.currentTarget.value)}\n onFocus={focusTitle ? (event) => event.currentTarget.select() : undefined}\n padding={2}\n placeholder=\"Untitled theme\"\n value={title}\n />\n </Stack>","focusTitle ? (event) => event.currentTarget.select() : undefined","<ImagePaletteSection\n imageUrl={imageUrl}\n onLucky={onLucky}\n onPalette={onPalette}\n onVariant={onVariant}\n options={options}\n palette={palette}\n />","SCHEMES.map((scheme) => (\n <SchemeCard\n active={split || scheme === studioScheme}\n key={scheme}\n onChange={(changes) => patchScheme(scheme, changes)}\n options={options[scheme] ?? {}}\n palette={palettes[scheme]}\n resolved={resolved[scheme]}\n scheme={scheme}\n split={split}\n splitHint={splitHint(scheme === studioScheme, mobile)}\n />\n ))","scheme","changes","<ScrollAreaBleed>\n {SCHEMES.map((scheme) => (\n <SchemeCard\n active={split || scheme === studioScheme}\n key={scheme}\n onChange={(changes) => patchScheme(scheme, changes)}\n options={options[scheme] ?? {}}\n palette={palettes[scheme]}\n resolved={resolved[scheme]}\n scheme={scheme}\n split={split}\n splitHint={splitHint(scheme === studioScheme, mobile)}\n />\n ))}\n </ScrollAreaBleed>","<ScrollArea padding={3}>\n <Stack gap={4}>\n <ThemeThumbnail options={options} />\n\n <Stack gap={2}>\n <Text size={1} weight=\"medium\">\n Title\n </Text>\n <TextInput\n autoFocus={focusTitle}\n fontSize={1}\n onChange={(event) => onTitleChange(event.currentTarget.value)}\n onFocus={focusTitle ? (event) => event.currentTarget.select() : undefined}\n padding={2}\n placeholder=\"Untitled theme\"\n value={title}\n />\n </Stack>\n\n <ImagePaletteSection\n imageUrl={imageUrl}\n onLucky={onLucky}\n onPalette={onPalette}\n onVariant={onVariant}\n options={options}\n palette={palette}\n />\n\n <ScrollAreaBleed>\n {SCHEMES.map((scheme) => (\n <SchemeCard\n active={split || scheme === studioScheme}\n key={scheme}\n onChange={(changes) => patchScheme(scheme, changes)}\n options={options[scheme] ?? {}}\n palette={palettes[scheme]}\n resolved={resolved[scheme]}\n scheme={scheme}\n split={split}\n splitHint={splitHint(scheme === studioScheme, mobile)}\n />\n ))}\n </ScrollAreaBleed>\n </Stack>\n </ScrollArea>","<TooltipButton\n icon={TrashIcon}\n mode=\"ghost\"\n onClick={onRemove}\n text=\"Remove\"\n tone=\"critical\"\n tooltip=\"Remove the theme — it can be restored until it is deleted\"\n />","<Box flex={1} />","<Button mode=\"ghost\" onClick={onDone} padding={2} text=\"Done\" />","<Card borderTop padding={3}>\n <Flex gap={2}>\n <TooltipButton\n icon={TrashIcon}\n mode=\"ghost\"\n onClick={onRemove}\n text=\"Remove\"\n tone=\"critical\"\n tooltip=\"Remove the theme — it can be restored until it is deleted\"\n />\n <Box flex={1} />\n <Button mode=\"ghost\" onClick={onDone} padding={2} text=\"Done\" />\n </Flex>\n </Card>","<>\n <ScrollArea padding={3}>\n <Stack gap={4}>\n <ThemeThumbnail options={options} />\n\n <Stack gap={2}>\n <Text size={1} weight=\"medium\">\n Title\n </Text>\n <TextInput\n autoFocus={focusTitle}\n fontSize={1}\n onChange={(event) => onTitleChange(event.currentTarget.value)}\n onFocus={focusTitle ? (event) => event.currentTarget.select() : undefined}\n padding={2}\n placeholder=\"Untitled theme\"\n value={title}\n />\n </Stack>\n\n <ImagePaletteSection\n imageUrl={imageUrl}\n onLucky={onLucky}\n onPalette={onPalette}\n onVariant={onVariant}\n options={options}\n palette={palette}\n />\n\n <ScrollAreaBleed>\n {SCHEMES.map((scheme) => (\n <SchemeCard\n active={split || scheme === studioScheme}\n key={scheme}\n onChange={(changes) => patchScheme(scheme, changes)}\n options={options[scheme] ?? {}}\n palette={palettes[scheme]}\n resolved={resolved[scheme]}\n scheme={scheme}\n split={split}\n splitHint={splitHint(scheme === studioScheme, mobile)}\n />\n ))}\n </ScrollAreaBleed>\n </Stack>\n </ScrollArea>\n\n <Card borderTop padding={3}>\n <Flex gap={2}>\n <TooltipButton\n icon={TrashIcon}\n mode=\"ghost\"\n onClick={onRemove}\n text=\"Remove\"\n tone=\"critical\"\n tooltip=\"Remove the theme — it can be restored until it is deleted\"\n />\n <Box flex={1} />\n <Button mode=\"ghost\" onClick={onDone} padding={2} text=\"Done\" />\n </Flex>\n </Card>\n </>","event","<Box flex={1}>\n <Text size={1} weight=\"medium\">\n {name}\n </Text>\n </Box>","active && (\n <Badge fontSize={0} tone=\"primary\">\n Active\n </Badge>\n )","<Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n {name}\n </Text>\n </Box>\n {active && (\n <Badge fontSize={0} tone=\"primary\">\n Active\n </Badge>\n )}\n </Flex>","<Text muted size={0}>\n {split\n ? splitHint\n : active\n ? 'Shown in the Studio right now'\n : `Shown once the Studio appearance is ${scheme}`}\n </Text>","split\n ? splitHint\n : active\n ? 'Shown in the Studio right now'\n : `Shown once the Studio appearance is ${scheme}`","<Stack gap={2}>\n <Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n {name}\n </Text>\n </Box>\n {active && (\n <Badge fontSize={0} tone=\"primary\">\n Active\n </Badge>\n )}\n </Flex>\n <Text muted size={0}>\n {split\n ? splitHint\n : active\n ? 'Shown in the Studio right now'\n : `Shown once the Studio appearance is ${scheme}`}\n </Text>\n </Stack>","(accent) => onChange({accent})","() => onChange({accent: undefined})","<ColorRow\n auto={options.accent === undefined}\n autoLabel=\"default\"\n label={`${name} accent`}\n onChange={(accent) => onChange({accent})}\n onClear={() => onChange({accent: undefined})}\n title=\"Accent\"\n value={resolved.accent}\n />","accent","options.accent === undefined","`${name} accent`","(text) => onChange({text})","() => onChange({text: undefined})","<ColorRow\n auto={options.text === undefined}\n label={`${name} text`}\n onChange={(text) => onChange({text})}\n onClear={() => onChange({text: undefined})}\n title=\"Text\"\n value={resolved.text}\n />","text","options.text === undefined","`${name} text`","(background) => onChange({background})","() => onChange({background: undefined})","<ColorRow\n adjusted={scheme === 'dark' ? palette.black : palette.white}\n auto={options.background === undefined}\n label={`${name} background`}\n onChange={(background) => onChange({background})}\n onClear={() => onChange({background: undefined})}\n title=\"Background\"\n value={resolved.background}\n />","background","scheme === 'dark' ? palette.black : palette.white","options.background === undefined","`${name} background`","<Text size={1}>Contrast</Text>","<Stack flex={1} gap={2}>\n <Text size={1}>Contrast</Text>\n <Text muted size={0}>\n {resolved.contrast}\n {options.contrast === undefined ? ' · auto' : ''}\n </Text>\n </Stack>","contrast","options.contrast === undefined ? ' · auto' : ''","options.contrast !== undefined && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={() => onChange({contrast: undefined})}\n padding={2}\n tooltip=\"Reset to auto\"\n />\n )","<Flex align=\"center\" gap={2}>\n <Stack flex={1} gap={2}>\n <Text size={1}>Contrast</Text>\n <Text muted size={0}>\n {resolved.contrast}\n {options.contrast === undefined ? ' · auto' : ''}\n </Text>\n </Stack>\n {options.contrast !== undefined && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={() => onChange({contrast: undefined})}\n padding={2}\n tooltip=\"Reset to auto\"\n />\n )}\n </Flex>","(event) => onChange({contrast: Number(event.currentTarget.value)})","<input\n aria-label={`${name} contrast`}\n className={range}\n max={100}\n min={15}\n onChange={(event) => onChange({contrast: Number(event.currentTarget.value)})}\n step={1}\n type=\"range\"\n value={resolved.contrast}\n />","`${name} contrast`","<Text muted size={0}>\n 100 keeps text and borders neutral — lower values blend in the accent\n </Text>","<Stack gap={3}>\n <Flex align=\"center\" gap={2}>\n <Stack flex={1} gap={2}>\n <Text size={1}>Contrast</Text>\n <Text muted size={0}>\n {resolved.contrast}\n {options.contrast === undefined ? ' · auto' : ''}\n </Text>\n </Stack>\n {options.contrast !== undefined && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={() => onChange({contrast: undefined})}\n padding={2}\n tooltip=\"Reset to auto\"\n />\n )}\n </Flex>\n <input\n aria-label={`${name} contrast`}\n className={range}\n max={100}\n min={15}\n onChange={(event) => onChange({contrast: Number(event.currentTarget.value)})}\n step={1}\n type=\"range\"\n value={resolved.contrast}\n />\n <Text muted size={0}>\n 100 keeps text and borders neutral — lower values blend in the accent\n </Text>\n </Stack>","<Card borderTop padding={3}>\n <Stack gap={4}>\n <Stack gap={2}>\n <Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n {name}\n </Text>\n </Box>\n {active && (\n <Badge fontSize={0} tone=\"primary\">\n Active\n </Badge>\n )}\n </Flex>\n <Text muted size={0}>\n {split\n ? splitHint\n : active\n ? 'Shown in the Studio right now'\n : `Shown once the Studio appearance is ${scheme}`}\n </Text>\n </Stack>\n\n <ColorRow\n auto={options.accent === undefined}\n autoLabel=\"default\"\n label={`${name} accent`}\n onChange={(accent) => onChange({accent})}\n onClear={() => onChange({accent: undefined})}\n title=\"Accent\"\n value={resolved.accent}\n />\n <ColorRow\n auto={options.text === undefined}\n label={`${name} text`}\n onChange={(text) => onChange({text})}\n onClear={() => onChange({text: undefined})}\n title=\"Text\"\n value={resolved.text}\n />\n <ColorRow\n adjusted={scheme === 'dark' ? palette.black : palette.white}\n auto={options.background === undefined}\n label={`${name} background`}\n onChange={(background) => onChange({background})}\n onClear={() => onChange({background: undefined})}\n title=\"Background\"\n value={resolved.background}\n />\n\n <Stack gap={3}>\n <Flex align=\"center\" gap={2}>\n <Stack flex={1} gap={2}>\n <Text size={1}>Contrast</Text>\n <Text muted size={0}>\n {resolved.contrast}\n {options.contrast === undefined ? ' · auto' : ''}\n </Text>\n </Stack>\n {options.contrast !== undefined && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={() => onChange({contrast: undefined})}\n padding={2}\n tooltip=\"Reset to auto\"\n />\n )}\n </Flex>\n <input\n aria-label={`${name} contrast`}\n className={range}\n max={100}\n min={15}\n onChange={(event) => onChange({contrast: Number(event.currentTarget.value)})}\n step={1}\n type=\"range\"\n value={resolved.contrast}\n />\n <Text muted size={0}>\n 100 keeps text and borders neutral — lower values blend in the accent\n </Text>\n </Stack>\n </Stack>\n </Card>","autoLabel = 'auto'","(event) => onChange(event.currentTarget.value)","<input\n aria-label={label}\n className={swatch}\n onChange={(event) => onChange(event.currentTarget.value)}\n type=\"color\"\n value={value}\n />","<Text size={1}>{title}</Text>","<Text muted size={0} textOverflow=\"ellipsis\">\n {value}\n {adjusted !== undefined && adjusted !== value ? ` → ${adjusted}` : ''}\n {auto ? ` · ${autoLabel}` : ''}\n </Text>","adjusted !== undefined && adjusted !== value ? ` → ${adjusted}` : ''","auto ? ` · ${autoLabel}` : ''","<Stack className={colorRowText} flex={1} gap={2}>\n <Text size={1}>{title}</Text>\n <Text muted size={0} textOverflow=\"ellipsis\">\n {value}\n {adjusted !== undefined && adjusted !== value ? ` → ${adjusted}` : ''}\n {auto ? ` · ${autoLabel}` : ''}\n </Text>\n </Stack>","onClear && !auto && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={onClear}\n padding={2}\n tooltip={`Reset to ${autoLabel}`}\n />\n )","<Flex align=\"center\" gap={2}>\n <input\n aria-label={label}\n className={swatch}\n onChange={(event) => onChange(event.currentTarget.value)}\n type=\"color\"\n value={value}\n />\n <Stack className={colorRowText} flex={1} gap={2}>\n <Text size={1}>{title}</Text>\n <Text muted size={0} textOverflow=\"ellipsis\">\n {value}\n {adjusted !== undefined && adjusted !== value ? ` → ${adjusted}` : ''}\n {auto ? ` · ${autoLabel}` : ''}\n </Text>\n </Stack>\n {onClear && !auto && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={onClear}\n padding={2}\n tooltip={`Reset to ${autoLabel}`}\n />\n )}\n </Flex>","async (theme: SharedTheme) => {\n const title = displayTitle(theme.title)\n\n try {\n await navigator.clipboard.writeText(encodeTheme(theme))\n toast.push({\n status: 'success',\n title: `Copied ${title} as a code`,\n description: 'Paste it in the themer of another Studio to add the theme there',\n })\n } catch {\n toast.push({status: 'error', title: `Could not copy ${title}`})\n }\n }","(text: string) => {\n const theme = decodeTheme(text)\n\n if (!theme) return false\n\n send({type: 'theme.import', title: theme.title, options: theme.options})\n toast.push({status: 'success', title: `Added ${theme.title}`})\n\n return true\n }","theme","async () => {\n let text: string\n\n try {\n text = await navigator.clipboard.readText()\n } catch {\n return 'no-clipboard'\n }\n\n return addThemeFromText(text) ? 'added' : 'no-theme'\n }","text","{copyTheme, addThemeFromText, addThemeFromClipboard}","() => {\n if (!open) return undefined\n\n const handlePaste = (event: ClipboardEvent) => {\n if (isEditable(event.target)) return\n\n const theme = decodeTheme(event.clipboardData?.getData('text/plain') ?? '')\n\n if (!theme) return\n\n event.preventDefault()\n send({type: 'theme.import', title: theme.title, options: theme.options})\n toast.push({status: 'success', title: `Added ${theme.title}`})\n }\n\n document.addEventListener('paste', handlePaste)\n\n return () => document.removeEventListener('paste', handlePaste)\n }","[open, send, toast]","() => {\n if (addThemeFromText(text)) {\n onClose()\n } else {\n setInvalid(true)\n }\n }","<Button mode=\"ghost\" onClick={onClose} padding={2} text=\"Cancel\" />","text.trim()","<Button\n disabled={text.trim() === ''}\n onClick={add}\n padding={2}\n text=\"Add theme\"\n tone=\"primary\"\n />","text.trim() === ''","<Flex gap={2} justify=\"flex-end\" padding={3}>\n <Button mode=\"ghost\" onClick={onClose} padding={2} text=\"Cancel\" />\n <Button\n disabled={text.trim() === ''}\n onClick={add}\n padding={2}\n text=\"Add theme\"\n tone=\"primary\"\n />\n </Flex>","(event) => {\n event.preventDefault()\n add()\n }","<Text muted size={1}>\n Paste the code someone copied from their themer — the theme is added to your list and\n applied.\n </Text>","(event) => {\n setText(event.currentTarget.value)\n setInvalid(false)\n }","event","<TextInput\n aria-describedby={invalid ? 'themer-paste-error' : undefined}\n aria-invalid={invalid}\n aria-label=\"Theme code\"\n autoFocus\n customValidity={invalid ? 'That is not a theme code' : undefined}\n fontSize={1}\n onChange={(event) => {\n setText(event.currentTarget.value)\n setInvalid(false)\n }}\n placeholder=\"AQ…\"\n value={text}\n />","invalid ? 'themer-paste-error' : undefined","invalid ? 'That is not a theme code' : undefined","invalid && (\n <Card id=\"themer-paste-error\" padding={3} radius={2} role=\"alert\" tone=\"critical\">\n <Text size={1}>\n That is not a theme code — codes are one word, copied from a theme's menu.\n </Text>\n </Card>\n )","<Stack gap={3} padding={4}>\n <Text muted size={1}>\n Paste the code someone copied from their themer — the theme is added to your list and\n applied.\n </Text>\n <TextInput\n aria-describedby={invalid ? 'themer-paste-error' : undefined}\n aria-invalid={invalid}\n aria-label=\"Theme code\"\n autoFocus\n customValidity={invalid ? 'That is not a theme code' : undefined}\n fontSize={1}\n onChange={(event) => {\n setText(event.currentTarget.value)\n setInvalid(false)\n }}\n placeholder=\"AQ…\"\n value={text}\n />\n {invalid && (\n <Card id=\"themer-paste-error\" padding={3} radius={2} role=\"alert\" tone=\"critical\">\n <Text size={1}>\n That is not a theme code — codes are one word, copied from a theme's menu.\n </Text>\n </Card>\n )}\n </Stack>","<form\n onSubmit={(event) => {\n event.preventDefault()\n add()\n }}\n >\n <Stack gap={3} padding={4}>\n <Text muted size={1}>\n Paste the code someone copied from their themer — the theme is added to your list and\n applied.\n </Text>\n <TextInput\n aria-describedby={invalid ? 'themer-paste-error' : undefined}\n aria-invalid={invalid}\n aria-label=\"Theme code\"\n autoFocus\n customValidity={invalid ? 'That is not a theme code' : undefined}\n fontSize={1}\n onChange={(event) => {\n setText(event.currentTarget.value)\n setInvalid(false)\n }}\n placeholder=\"AQ…\"\n value={text}\n />\n {invalid && (\n <Card id=\"themer-paste-error\" padding={3} radius={2} role=\"alert\" tone=\"critical\">\n <Text size={1}>\n That is not a theme code — codes are one word, copied from a theme's menu.\n </Text>\n </Card>\n )}\n </Stack>\n </form>","<Dialog\n animate\n footer={\n <Flex gap={2} justify=\"flex-end\" padding={3}>\n <Button mode=\"ghost\" onClick={onClose} padding={2} text=\"Cancel\" />\n <Button\n disabled={text.trim() === ''}\n onClick={add}\n padding={2}\n text=\"Add theme\"\n tone=\"primary\"\n />\n </Flex>\n }\n header=\"Add a shared theme\"\n id=\"themer-paste\"\n onClickOutside={onClose}\n onClose={onClose}\n width={0}\n >\n <form\n onSubmit={(event) => {\n event.preventDefault()\n add()\n }}\n >\n <Stack gap={3} padding={4}>\n <Text muted size={1}>\n Paste the code someone copied from their themer — the theme is added to your list and\n applied.\n </Text>\n <TextInput\n aria-describedby={invalid ? 'themer-paste-error' : undefined}\n aria-invalid={invalid}\n aria-label=\"Theme code\"\n autoFocus\n customValidity={invalid ? 'That is not a theme code' : undefined}\n fontSize={1}\n onChange={(event) => {\n setText(event.currentTarget.value)\n setInvalid(false)\n }}\n placeholder=\"AQ…\"\n value={text}\n />\n {invalid && (\n <Card id=\"themer-paste-error\" padding={3} radius={2} role=\"alert\" tone=\"critical\">\n <Text size={1}>\n That is not a theme code — codes are one word, copied from a theme's menu.\n </Text>\n </Card>\n )}\n </Stack>\n </form>\n </Dialog>","displayTitle(theme.title)","title","(candidate) => candidate.slug === theme.slug","slug","(to: number) => {\n const order = themes.map((candidate) => candidate.slug)\n\n order.splice(to, 0, ...order.splice(index, 1))\n send({type: 'theme.reorder', order})\n }","(candidate) => candidate.slug","() => {\n // The click lands before the next frame\n requestAnimationFrame(() => {\n dragged.current = false\n })\n }","() => {\n dragged.current = true\n }","{scale: 1.04}","() => {\n if (!dragged.current) send({type: 'theme.pick', slug: theme.slug})\n }","(event) => {\n // A mouse or pen drags the card off the button; a finger scrolls the\n // list instead, and moves themes from the menu\n if (event.pointerType !== 'touch') dragControls.start(event)\n }","<span className={frame}>\n <ThemeThumbnail options={theme.options} />\n </span>","options","<Box as=\"span\" display=\"block\" paddingTop={2} paddingX={2}>\n <Text\n align=\"center\"\n as=\"span\"\n size={1}\n textOverflow=\"ellipsis\"\n weight={active ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>","active ? 'medium' : 'regular'","<button\n aria-pressed={active}\n className={pickButton}\n onClick={() => {\n if (!dragged.current) send({type: 'theme.pick', slug: theme.slug})\n }}\n onPointerDown={(event) => {\n // A mouse or pen drags the card off the button; a finger scrolls the\n // list instead, and moves themes from the menu\n if (event.pointerType !== 'touch') dragControls.start(event)\n }}\n type=\"button\"\n >\n <span className={frame}>\n <ThemeThumbnail options={theme.options} />\n </span>\n <Box as=\"span\" display=\"block\" paddingTop={2} paddingX={2}>\n <Text\n align=\"center\"\n as=\"span\"\n size={1}\n textOverflow=\"ellipsis\"\n weight={active ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>\n </button>","<Text size={1}>Show more</Text>","<Button\n aria-label={`Show more for ${title}`}\n fontSize={1}\n icon={EllipsisHorizontalIcon}\n mode=\"ghost\"\n padding={1}\n radius={2}\n />","`Show more for ${title}`","() => moveTo(index - 1)","<MenuItem\n disabled={index <= 0}\n icon={ArrowUpIcon}\n onClick={() => moveTo(index - 1)}\n text=\"Move up\"\n />","index <= 0","() => moveTo(index + 1)","<MenuItem\n disabled={index >= themes.length - 1}\n icon={ArrowDownIcon}\n onClick={() => moveTo(index + 1)}\n text=\"Move down\"\n />","index >= themes.length - 1","<MenuDivider />","theme.source === 'custom' && (\n <MenuItem\n icon={EditIcon}\n onClick={() => send({type: 'theme.edit', slug: theme.slug})}\n text=\"Edit\"\n />\n )","source","() => send({type: 'theme.duplicate', slug: theme.slug})","<MenuItem\n icon={CopyIcon}\n onClick={() => send({type: 'theme.duplicate', slug: theme.slug})}\n text={theme.source === 'custom' ? 'Duplicate' : 'Duplicate to edit'}\n />","theme.source === 'custom' ? 'Duplicate' : 'Duplicate to edit'","<MenuItem\n icon={ShareIcon}\n onClick={() => void copyTheme(theme)}\n text=\"Copy to share\"\n />","theme.source !== 'config' && (\n <>\n <MenuDivider />\n <MenuItem\n icon={TrashIcon}\n onClick={() => send({type: 'theme.remove', slug: theme.slug})}\n text=\"Remove\"\n tone=\"critical\"\n />\n </>\n )","<Menu>\n <MenuItem\n disabled={index <= 0}\n icon={ArrowUpIcon}\n onClick={() => moveTo(index - 1)}\n text=\"Move up\"\n />\n <MenuItem\n disabled={index >= themes.length - 1}\n icon={ArrowDownIcon}\n onClick={() => moveTo(index + 1)}\n text=\"Move down\"\n />\n <MenuDivider />\n {theme.source === 'custom' && (\n <MenuItem\n icon={EditIcon}\n onClick={() => send({type: 'theme.edit', slug: theme.slug})}\n text=\"Edit\"\n />\n )}\n <MenuItem\n icon={CopyIcon}\n onClick={() => send({type: 'theme.duplicate', slug: theme.slug})}\n text={theme.source === 'custom' ? 'Duplicate' : 'Duplicate to edit'}\n />\n <MenuItem\n icon={ShareIcon}\n onClick={() => void copyTheme(theme)}\n text=\"Copy to share\"\n />\n {theme.source !== 'config' && (\n <>\n <MenuDivider />\n <MenuItem\n icon={TrashIcon}\n onClick={() => send({type: 'theme.remove', slug: theme.slug})}\n text=\"Remove\"\n tone=\"critical\"\n />\n </>\n )}\n </Menu>","() => setMenuOpen(false)","() => setMenuOpen(true)","{animate: true, placement: 'bottom-end', portal: true, scheme}","<Tooltip animate content={<Text size={1}>Show more</Text>} placement=\"bottom\" portal>\n <Card className={menuCard} radius={2} scheme=\"dark\">\n <MenuButton\n button={\n <Button\n aria-label={`Show more for ${title}`}\n fontSize={1}\n icon={EllipsisHorizontalIcon}\n mode=\"ghost\"\n padding={1}\n radius={2}\n />\n }\n id={`themer-theme-${theme.slug}`}\n menu={\n <Menu>\n <MenuItem\n disabled={index <= 0}\n icon={ArrowUpIcon}\n onClick={() => moveTo(index - 1)}\n text=\"Move up\"\n />\n <MenuItem\n disabled={index >= themes.length - 1}\n icon={ArrowDownIcon}\n onClick={() => moveTo(index + 1)}\n text=\"Move down\"\n />\n <MenuDivider />\n {theme.source === 'custom' && (\n <MenuItem\n icon={EditIcon}\n onClick={() => send({type: 'theme.edit', slug: theme.slug})}\n text=\"Edit\"\n />\n )}\n <MenuItem\n icon={CopyIcon}\n onClick={() => send({type: 'theme.duplicate', slug: theme.slug})}\n text={theme.source === 'custom' ? 'Duplicate' : 'Duplicate to edit'}\n />\n <MenuItem\n icon={ShareIcon}\n onClick={() => void copyTheme(theme)}\n text=\"Copy to share\"\n />\n {theme.source !== 'config' && (\n <>\n <MenuDivider />\n <MenuItem\n icon={TrashIcon}\n onClick={() => send({type: 'theme.remove', slug: theme.slug})}\n text=\"Remove\"\n tone=\"critical\"\n />\n </>\n )}\n </Menu>\n }\n onClose={() => setMenuOpen(false)}\n onOpen={() => setMenuOpen(true)}\n popover={{animate: true, placement: 'bottom-end', portal: true, scheme}}\n />\n </Card>\n </Tooltip>","`themer-theme-${theme.slug}`","<div className={menuSlot} data-visible={menuOpen}>\n <Tooltip animate content={<Text size={1}>Show more</Text>} placement=\"bottom\" portal>\n <Card className={menuCard} radius={2} scheme=\"dark\">\n <MenuButton\n button={\n <Button\n aria-label={`Show more for ${title}`}\n fontSize={1}\n icon={EllipsisHorizontalIcon}\n mode=\"ghost\"\n padding={1}\n radius={2}\n />\n }\n id={`themer-theme-${theme.slug}`}\n menu={\n <Menu>\n <MenuItem\n disabled={index <= 0}\n icon={ArrowUpIcon}\n onClick={() => moveTo(index - 1)}\n text=\"Move up\"\n />\n <MenuItem\n disabled={index >= themes.length - 1}\n icon={ArrowDownIcon}\n onClick={() => moveTo(index + 1)}\n text=\"Move down\"\n />\n <MenuDivider />\n {theme.source === 'custom' && (\n <MenuItem\n icon={EditIcon}\n onClick={() => send({type: 'theme.edit', slug: theme.slug})}\n text=\"Edit\"\n />\n )}\n <MenuItem\n icon={CopyIcon}\n onClick={() => send({type: 'theme.duplicate', slug: theme.slug})}\n text={theme.source === 'custom' ? 'Duplicate' : 'Duplicate to edit'}\n />\n <MenuItem\n icon={ShareIcon}\n onClick={() => void copyTheme(theme)}\n text=\"Copy to share\"\n />\n {theme.source !== 'config' && (\n <>\n <MenuDivider />\n <MenuItem\n icon={TrashIcon}\n onClick={() => send({type: 'theme.remove', slug: theme.slug})}\n text=\"Remove\"\n tone=\"critical\"\n />\n </>\n )}\n </Menu>\n }\n onClose={() => setMenuOpen(false)}\n onOpen={() => setMenuOpen(true)}\n popover={{animate: true, placement: 'bottom-end', portal: true, scheme}}\n />\n </Card>\n </Tooltip>\n </div>","<Reorder.Item\n as=\"div\"\n className={root}\n dragControls={dragControls}\n dragListener={false}\n onDragEnd={() => {\n // The click lands before the next frame\n requestAnimationFrame(() => {\n dragged.current = false\n })\n }}\n onDragStart={() => {\n dragged.current = true\n }}\n ref={active ? scrollRefIntoViewIfNeeded : undefined}\n value={theme.slug}\n whileDrag={{scale: 1.04}}\n >\n <button\n aria-pressed={active}\n className={pickButton}\n onClick={() => {\n if (!dragged.current) send({type: 'theme.pick', slug: theme.slug})\n }}\n onPointerDown={(event) => {\n // A mouse or pen drags the card off the button; a finger scrolls the\n // list instead, and moves themes from the menu\n if (event.pointerType !== 'touch') dragControls.start(event)\n }}\n type=\"button\"\n >\n <span className={frame}>\n <ThemeThumbnail options={theme.options} />\n </span>\n <Box as=\"span\" display=\"block\" paddingTop={2} paddingX={2}>\n <Text\n align=\"center\"\n as=\"span\"\n size={1}\n textOverflow=\"ellipsis\"\n weight={active ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>\n </button>\n\n {/* The tooltip wraps a card rather than the button: the menu button\n clones its button to wire it up, which a tooltip in between would\n swallow. The card keeps the button dark, as it sits on the dark\n half of the thumbnail, while the menu opens in the sidebar's scheme */}\n <div className={menuSlot} data-visible={menuOpen}>\n <Tooltip animate content={<Text size={1}>Show more</Text>} placement=\"bottom\" portal>\n <Card className={menuCard} radius={2} scheme=\"dark\">\n <MenuButton\n button={\n <Button\n aria-label={`Show more for ${title}`}\n fontSize={1}\n icon={EllipsisHorizontalIcon}\n mode=\"ghost\"\n padding={1}\n radius={2}\n />\n }\n id={`themer-theme-${theme.slug}`}\n menu={\n <Menu>\n <MenuItem\n disabled={index <= 0}\n icon={ArrowUpIcon}\n onClick={() => moveTo(index - 1)}\n text=\"Move up\"\n />\n <MenuItem\n disabled={index >= themes.length - 1}\n icon={ArrowDownIcon}\n onClick={() => moveTo(index + 1)}\n text=\"Move down\"\n />\n <MenuDivider />\n {theme.source === 'custom' && (\n <MenuItem\n icon={EditIcon}\n onClick={() => send({type: 'theme.edit', slug: theme.slug})}\n text=\"Edit\"\n />\n )}\n <MenuItem\n icon={CopyIcon}\n onClick={() => send({type: 'theme.duplicate', slug: theme.slug})}\n text={theme.source === 'custom' ? 'Duplicate' : 'Duplicate to edit'}\n />\n <MenuItem\n icon={ShareIcon}\n onClick={() => void copyTheme(theme)}\n text=\"Copy to share\"\n />\n {theme.source !== 'config' && (\n <>\n <MenuDivider />\n <MenuItem\n icon={TrashIcon}\n onClick={() => send({type: 'theme.remove', slug: theme.slug})}\n text=\"Remove\"\n tone=\"critical\"\n />\n </>\n )}\n </Menu>\n }\n onClose={() => setMenuOpen(false)}\n onOpen={() => setMenuOpen(true)}\n popover={{animate: true, placement: 'bottom-end', portal: true, scheme}}\n />\n </Card>\n </Tooltip>\n </div>\n </Reorder.Item>","active ? scrollRefIntoViewIfNeeded : undefined","Reorder.Item","candidate","(palette, file) =>\n send({\n type: 'theme.add',\n title: titleFromFileName(file.name),\n options: optionsFromImagePalette(palette),\n palette,\n imageUrl: URL.createObjectURL(file),\n })","(order: string[]) => send({type: 'theme.reorder', order})","themes.map((theme) => theme.slug)","(theme) => theme.slug","themes.map((theme) => (\n <ThemeCard active={theme.slug === active.slug} key={theme.slug} theme={theme} />\n ))","active.slug","(theme) => (\n <ThemeCard active={theme.slug === active.slug} key={theme.slug} theme={theme} />\n )","theme","<MotionConfig reducedMotion=\"user\">\n <Reorder.Group\n as=\"div\"\n className={cardGrid}\n onReorder={(order: string[]) => send({type: 'theme.reorder', order})}\n values={themes.map((theme) => theme.slug)}\n >\n {themes.map((theme) => (\n <ThemeCard active={theme.slug === active.slug} key={theme.slug} theme={theme} />\n ))}\n </Reorder.Group>\n </MotionConfig>","Reorder.Group","removed.length > 0 && (\n <Button\n gap={2}\n icon={RestoreIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.removed'})}\n padding={2}\n text={`Show removed (${removed.length})`}\n width=\"fill\"\n />\n )","length","<ScrollArea padding={3}>\n <Stack gap={4}>\n {/* The group tells a column from a grid by measuring the cards, and\n lets go of the layout animations for users who prefer reduced motion */}\n <MotionConfig reducedMotion=\"user\">\n <Reorder.Group\n as=\"div\"\n className={cardGrid}\n onReorder={(order: string[]) => send({type: 'theme.reorder', order})}\n values={themes.map((theme) => theme.slug)}\n >\n {themes.map((theme) => (\n <ThemeCard active={theme.slug === active.slug} key={theme.slug} theme={theme} />\n ))}\n </Reorder.Group>\n </MotionConfig>\n {/* Trails the list rather than sitting in the footer, so the footer\n does not shift when the first theme gets removed */}\n {removed.length > 0 && (\n <Button\n gap={2}\n icon={RestoreIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.removed'})}\n padding={2}\n text={`Show removed (${removed.length})`}\n width=\"fill\"\n />\n )}\n </Stack>\n </ScrollArea>","<Box flex={1}>\n <TooltipButton\n icon={AddIcon}\n mode=\"ghost\"\n onClick={() => send({type: 'theme.add'})}\n text=\"Add theme\"\n tooltip=\"Add a theme based on the applied one\"\n width=\"fill\"\n />\n </Box>","<ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n tooltip=\"Add a theme from the colors of an image\"\n />","<TooltipButton\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={async () => {\n // Straight from the clipboard where the browser allows; by hand otherwise\n if ((await addThemeFromClipboard()) !== 'added') setPasting(true)\n }}\n tooltip=\"Add a theme from a code someone shared\"\n />","<Card borderTop padding={3}>\n <Flex gap={2}>\n <Box flex={1}>\n <TooltipButton\n icon={AddIcon}\n mode=\"ghost\"\n onClick={() => send({type: 'theme.add'})}\n text=\"Add theme\"\n tooltip=\"Add a theme based on the applied one\"\n width=\"fill\"\n />\n </Box>\n <ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n tooltip=\"Add a theme from the colors of an image\"\n />\n <TooltipButton\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={async () => {\n // Straight from the clipboard where the browser allows; by hand otherwise\n if ((await addThemeFromClipboard()) !== 'added') setPasting(true)\n }}\n tooltip=\"Add a theme from a code someone shared\"\n />\n </Flex>\n </Card>","pasting && <PasteThemeDialog onClose={() => setPasting(false)} />","<>\n <ScrollArea padding={3}>\n <Stack gap={4}>\n {/* The group tells a column from a grid by measuring the cards, and\n lets go of the layout animations for users who prefer reduced motion */}\n <MotionConfig reducedMotion=\"user\">\n <Reorder.Group\n as=\"div\"\n className={cardGrid}\n onReorder={(order: string[]) => send({type: 'theme.reorder', order})}\n values={themes.map((theme) => theme.slug)}\n >\n {themes.map((theme) => (\n <ThemeCard active={theme.slug === active.slug} key={theme.slug} theme={theme} />\n ))}\n </Reorder.Group>\n </MotionConfig>\n {/* Trails the list rather than sitting in the footer, so the footer\n does not shift when the first theme gets removed */}\n {removed.length > 0 && (\n <Button\n gap={2}\n icon={RestoreIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.removed'})}\n padding={2}\n text={`Show removed (${removed.length})`}\n width=\"fill\"\n />\n )}\n </Stack>\n </ScrollArea>\n\n <Card borderTop padding={3}>\n <Flex gap={2}>\n <Box flex={1}>\n <TooltipButton\n icon={AddIcon}\n mode=\"ghost\"\n onClick={() => send({type: 'theme.add'})}\n text=\"Add theme\"\n tooltip=\"Add a theme based on the applied one\"\n width=\"fill\"\n />\n </Box>\n <ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n tooltip=\"Add a theme from the colors of an image\"\n />\n <TooltipButton\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={async () => {\n // Straight from the clipboard where the browser allows; by hand otherwise\n if ((await addThemeFromClipboard()) !== 'added') setPasting(true)\n }}\n tooltip=\"Add a theme from a code someone shared\"\n />\n </Flex>\n </Card>\n\n {pasting && <PasteThemeDialog onClose={() => setPasting(false)} />}\n </>","createThemeSnippet(theme.options)","options","async () => {\n try {\n await navigator.clipboard.writeText(snippet)\n toast.push({status: 'success', title: 'Copied the theme to the clipboard'})\n } catch {\n toast.push({status: 'error', title: 'Could not copy the theme'})\n }\n }","<Flex gap={2} justify=\"flex-end\" padding={3}>\n <Button\n gap={2}\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={() => void handleCopy()}\n padding={2}\n text=\"Copy\"\n />\n </Flex>","<code>sanity.config.ts</code>","<code>theme</code>","<code>defineConfig</code>","displayTitle(theme.title)","title","<Text muted size={1}>\n Export this from <code>sanity.config.ts</code> and pass it as the <code>theme</code> of{' '}\n <code>defineConfig</code> to make “{displayTitle(theme.title)}” the Studio theme without\n this tool.\n </Text>","<Card border overflow=\"auto\" padding={3} radius={2} tone=\"transparent\">\n <Code language=\"ts\" size={1}>\n {snippet}\n </Code>\n </Card>","<Box padding={4}>\n <Stack gap={4}>\n <Text muted size={1}>\n Export this from <code>sanity.config.ts</code> and pass it as the <code>theme</code> of{' '}\n <code>defineConfig</code> to make “{displayTitle(theme.title)}” the Studio theme without\n this tool.\n </Text>\n <Card border overflow=\"auto\" padding={3} radius={2} tone=\"transparent\">\n <Code language=\"ts\" size={1}>\n {snippet}\n </Code>\n </Card>\n </Stack>\n </Box>","<Dialog\n animate\n footer={\n <Flex gap={2} justify=\"flex-end\" padding={3}>\n <Button\n gap={2}\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={() => void handleCopy()}\n padding={2}\n text=\"Copy\"\n />\n </Flex>\n }\n header=\"Add to your config\"\n id=\"themer-snippet\"\n onClickOutside={onClose}\n onClose={onClose}\n width={1}\n >\n <Box padding={4}>\n <Stack gap={4}>\n <Text muted size={1}>\n Export this from <code>sanity.config.ts</code> and pass it as the <code>theme</code> of{' '}\n <code>defineConfig</code> to make “{displayTitle(theme.title)}” the Studio theme without\n this tool.\n </Text>\n <Card border overflow=\"auto\" padding={3} radius={2} tone=\"transparent\">\n <Code language=\"ts\" size={1}>\n {snippet}\n </Code>\n </Card>\n </Stack>\n </Box>\n </Dialog>","navbarHeight === null ? undefined : {height: navbarHeight}","!inList && (\n <TooltipButton\n icon={ArrowLeftIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.list'})}\n padding={2}\n tooltip=\"Back to the themes\"\n />\n )","<Box className={title} flex={1}>\n <Text size={1} textOverflow=\"ellipsis\" weight=\"semibold\">\n {VIEW_TITLES[view.name]}\n </Text>\n </Box>","VIEW_TITLES[view.name]","view.name !== 'removed' && (\n <>\n <TooltipButton\n aria-pressed={split}\n icon={SplitVerticalIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'preview.toggle'})}\n padding={2}\n selected={split}\n tooltip=\"Show light and dark side by side\"\n />\n <TooltipButton\n icon={CodeBlockIcon}\n mode=\"bleed\"\n onClick={() => setSnippetOpen(true)}\n padding={2}\n tooltip=\"Show the code for the applied theme\"\n />\n </>\n )","name","<TooltipButton\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.close'})}\n padding={2}\n tooltip=\"Close themer\"\n />","<Flex>\n {view.name !== 'removed' && (\n <>\n <TooltipButton\n aria-pressed={split}\n icon={SplitVerticalIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'preview.toggle'})}\n padding={2}\n selected={split}\n tooltip=\"Show light and dark side by side\"\n />\n <TooltipButton\n icon={CodeBlockIcon}\n mode=\"bleed\"\n onClick={() => setSnippetOpen(true)}\n padding={2}\n tooltip=\"Show the code for the applied theme\"\n />\n </>\n )}\n <TooltipButton\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.close'})}\n padding={2}\n tooltip=\"Close themer\"\n />\n </Flex>","<Flex align=\"center\" gap={1} height=\"fill\">\n {!inList && (\n <TooltipButton\n icon={ArrowLeftIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.list'})}\n padding={2}\n tooltip=\"Back to the themes\"\n />\n )}\n <Box className={title} flex={1}>\n <Text size={1} textOverflow=\"ellipsis\" weight=\"semibold\">\n {VIEW_TITLES[view.name]}\n </Text>\n </Box>\n {/* Packed without gaps, so the title still fits next to them at the\n narrowest sidebar width */}\n <Flex>\n {view.name !== 'removed' && (\n <>\n <TooltipButton\n aria-pressed={split}\n icon={SplitVerticalIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'preview.toggle'})}\n padding={2}\n selected={split}\n tooltip=\"Show light and dark side by side\"\n />\n <TooltipButton\n icon={CodeBlockIcon}\n mode=\"bleed\"\n onClick={() => setSnippetOpen(true)}\n padding={2}\n tooltip=\"Show the code for the applied theme\"\n />\n </>\n )}\n <TooltipButton\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.close'})}\n padding={2}\n tooltip=\"Close themer\"\n />\n </Flex>\n </Flex>","<Card\n borderBottom\n padding={3}\n sizing=\"border\"\n style={navbarHeight === null ? undefined : {height: navbarHeight}}\n >\n <Flex align=\"center\" gap={1} height=\"fill\">\n {!inList && (\n <TooltipButton\n icon={ArrowLeftIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.list'})}\n padding={2}\n tooltip=\"Back to the themes\"\n />\n )}\n <Box className={title} flex={1}>\n <Text size={1} textOverflow=\"ellipsis\" weight=\"semibold\">\n {VIEW_TITLES[view.name]}\n </Text>\n </Box>\n {/* Packed without gaps, so the title still fits next to them at the\n narrowest sidebar width */}\n <Flex>\n {view.name !== 'removed' && (\n <>\n <TooltipButton\n aria-pressed={split}\n icon={SplitVerticalIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'preview.toggle'})}\n padding={2}\n selected={split}\n tooltip=\"Show light and dark side by side\"\n />\n <TooltipButton\n icon={CodeBlockIcon}\n mode=\"bleed\"\n onClick={() => setSnippetOpen(true)}\n padding={2}\n tooltip=\"Show the code for the applied theme\"\n />\n </>\n )}\n <TooltipButton\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.close'})}\n padding={2}\n tooltip=\"Close themer\"\n />\n </Flex>\n </Flex>\n </Card>","view.name === 'list' && <ThemeList />","view.name === 'edit' && <ThemeEditor focusTitle={view.focusTitle} slug={view.slug} />","focusTitle","slug","view.name === 'removed' && <RemovedThemes />","<Flex direction=\"column\" height=\"fill\">\n {/* The header sits next to the Studio navbar and takes its height, so\n the two bottom borders line up — whatever height the Studio, its\n breakpoint or a custom navbar gives it */}\n <Card\n borderBottom\n padding={3}\n sizing=\"border\"\n style={navbarHeight === null ? undefined : {height: navbarHeight}}\n >\n <Flex align=\"center\" gap={1} height=\"fill\">\n {!inList && (\n <TooltipButton\n icon={ArrowLeftIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.list'})}\n padding={2}\n tooltip=\"Back to the themes\"\n />\n )}\n <Box className={title} flex={1}>\n <Text size={1} textOverflow=\"ellipsis\" weight=\"semibold\">\n {VIEW_TITLES[view.name]}\n </Text>\n </Box>\n {/* Packed without gaps, so the title still fits next to them at the\n narrowest sidebar width */}\n <Flex>\n {view.name !== 'removed' && (\n <>\n <TooltipButton\n aria-pressed={split}\n icon={SplitVerticalIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'preview.toggle'})}\n padding={2}\n selected={split}\n tooltip=\"Show light and dark side by side\"\n />\n <TooltipButton\n icon={CodeBlockIcon}\n mode=\"bleed\"\n onClick={() => setSnippetOpen(true)}\n padding={2}\n tooltip=\"Show the code for the applied theme\"\n />\n </>\n )}\n <TooltipButton\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.close'})}\n padding={2}\n tooltip=\"Close themer\"\n />\n </Flex>\n </Flex>\n </Card>\n\n {view.name === 'list' && <ThemeList />}\n {view.name === 'edit' && <ThemeEditor focusTitle={view.focusTitle} slug={view.slug} />}\n {view.name === 'removed' && <RemovedThemes />}\n </Flex>","snippetOpen && <ThemeSnippetDialog onClose={() => setSnippetOpen(false)} theme={active} />","<Card height=\"fill\">\n <Flex direction=\"column\" height=\"fill\">\n {/* The header sits next to the Studio navbar and takes its height, so\n the two bottom borders line up — whatever height the Studio, its\n breakpoint or a custom navbar gives it */}\n <Card\n borderBottom\n padding={3}\n sizing=\"border\"\n style={navbarHeight === null ? undefined : {height: navbarHeight}}\n >\n <Flex align=\"center\" gap={1} height=\"fill\">\n {!inList && (\n <TooltipButton\n icon={ArrowLeftIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.list'})}\n padding={2}\n tooltip=\"Back to the themes\"\n />\n )}\n <Box className={title} flex={1}>\n <Text size={1} textOverflow=\"ellipsis\" weight=\"semibold\">\n {VIEW_TITLES[view.name]}\n </Text>\n </Box>\n {/* Packed without gaps, so the title still fits next to them at the\n narrowest sidebar width */}\n <Flex>\n {view.name !== 'removed' && (\n <>\n <TooltipButton\n aria-pressed={split}\n icon={SplitVerticalIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'preview.toggle'})}\n padding={2}\n selected={split}\n tooltip=\"Show light and dark side by side\"\n />\n <TooltipButton\n icon={CodeBlockIcon}\n mode=\"bleed\"\n onClick={() => setSnippetOpen(true)}\n padding={2}\n tooltip=\"Show the code for the applied theme\"\n />\n </>\n )}\n <TooltipButton\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.close'})}\n padding={2}\n tooltip=\"Close themer\"\n />\n </Flex>\n </Flex>\n </Card>\n\n {view.name === 'list' && <ThemeList />}\n {view.name === 'edit' && <ThemeEditor focusTitle={view.focusTitle} slug={view.slug} />}\n {view.name === 'removed' && <RemovedThemes />}\n </Flex>\n\n {snippetOpen && <ThemeSnippetDialog onClose={() => setSnippetOpen(false)} theme={active} />}\n </Card>","() => writeStoredWidth(width)","[width]","(event: React.PointerEvent<HTMLDivElement>) => {\n if (event.button !== 0) return\n\n event.preventDefault()\n event.currentTarget.setPointerCapture(event.pointerId)\n drag.current = {pointerId: event.pointerId, startX: event.clientX, startWidth: width}\n setDragging(true)\n }","useCallback(\n (event: React.PointerEvent<HTMLDivElement>) => {\n if (event.button !== 0) return\n\n event.preventDefault()\n event.currentTarget.setPointerCapture(event.pointerId)\n drag.current = {pointerId: event.pointerId, startX: event.clientX, startWidth: width}\n setDragging(true)\n },\n [width],\n )","(event: React.PointerEvent<HTMLDivElement>) => {\n if (!drag.current || drag.current.pointerId !== event.pointerId) return\n\n // The sidebar sits on the right, so dragging left makes it wider\n setWidth(clampWidth(drag.current.startWidth - (event.clientX - drag.current.startX)))\n }","event","useCallback((event: React.PointerEvent<HTMLDivElement>) => {\n if (!drag.current || drag.current.pointerId !== event.pointerId) return\n\n // The sidebar sits on the right, so dragging left makes it wider\n setWidth(clampWidth(drag.current.startWidth - (event.clientX - drag.current.startX)))\n }, [])","(event: React.PointerEvent<HTMLDivElement>) => {\n if (!drag.current || drag.current.pointerId !== event.pointerId) return\n\n drag.current = null\n setDragging(false)\n }","useCallback((event: React.PointerEvent<HTMLDivElement>) => {\n if (!drag.current || drag.current.pointerId !== event.pointerId) return\n\n drag.current = null\n setDragging(false)\n }, [])","(event: React.KeyboardEvent<HTMLDivElement>) => {\n const steps: Record<string, number> = {\n ArrowLeft: KEYBOARD_STEP,\n ArrowRight: -KEYBOARD_STEP,\n Home: MAXIMUM_WIDTH,\n End: -MAXIMUM_WIDTH,\n }\n const step = steps[event.key]\n\n if (step === undefined) return\n\n event.preventDefault()\n setWidth((current) => clampWidth(current + step))\n }","useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {\n const steps: Record<string, number> = {\n ArrowLeft: KEYBOARD_STEP,\n ArrowRight: -KEYBOARD_STEP,\n Home: MAXIMUM_WIDTH,\n End: -MAXIMUM_WIDTH,\n }\n const step = steps[event.key]\n\n if (step === undefined) return\n\n event.preventDefault()\n setWidth((current) => clampWidth(current + step))\n }, [])","overlay ? undefined : {width}","!overlay && (\n <div\n aria-label=\"Resize the themer\"\n className={resizeHandle}\n aria-orientation=\"vertical\"\n aria-valuemax={MAXIMUM_WIDTH}\n aria-valuemin={MINIMUM_WIDTH}\n aria-valuenow={width}\n data-dragging={dragging}\n onDoubleClick={() => setWidth(MINIMUM_WIDTH)}\n onKeyDown={handleKeyDown}\n onPointerCancel={handlePointerUp}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n // oxlint-disable-next-line prefer-tag-over-role -- a window splitter is a focusable, draggable separator, which an hr is not\n role=\"separator\"\n tabIndex={0}\n />\n )","<Box className={content} height=\"fill\" overflow=\"hidden\">\n <ThemerSidebar />\n </Box>","<Card borderLeft={!overlay} className={frame} height=\"fill\">\n {!overlay && (\n <div\n aria-label=\"Resize the themer\"\n className={resizeHandle}\n aria-orientation=\"vertical\"\n aria-valuemax={MAXIMUM_WIDTH}\n aria-valuemin={MINIMUM_WIDTH}\n aria-valuenow={width}\n data-dragging={dragging}\n onDoubleClick={() => setWidth(MINIMUM_WIDTH)}\n onKeyDown={handleKeyDown}\n onPointerCancel={handlePointerUp}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n // oxlint-disable-next-line prefer-tag-over-role -- a window splitter is a focusable, draggable separator, which an hr is not\n role=\"separator\"\n tabIndex={0}\n />\n )}\n <Box className={content} height=\"fill\" overflow=\"hidden\">\n <ThemerSidebar />\n </Box>\n </Card>","!overlay","<Layer\n className={sidebar}\n data-overlay={overlay}\n style={overlay ? undefined : {width}}\n zOffset={100}\n >\n <Card borderLeft={!overlay} className={frame} height=\"fill\">\n {!overlay && (\n <div\n aria-label=\"Resize the themer\"\n className={resizeHandle}\n aria-orientation=\"vertical\"\n aria-valuemax={MAXIMUM_WIDTH}\n aria-valuemin={MINIMUM_WIDTH}\n aria-valuenow={width}\n data-dragging={dragging}\n onDoubleClick={() => setWidth(MINIMUM_WIDTH)}\n onKeyDown={handleKeyDown}\n onPointerCancel={handlePointerUp}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n // oxlint-disable-next-line prefer-tag-over-role -- a window splitter is a focusable, draggable separator, which an hr is not\n role=\"separator\"\n tabIndex={0}\n />\n )}\n <Box className={content} height=\"fill\" overflow=\"hidden\">\n <ThemerSidebar />\n </Box>\n </Card>\n </Layer>","() => {\n const root = ref.current\n\n if (!root) return undefined\n\n let navbar: Element | null = null\n const resizeObserver = new ResizeObserver(() => {\n if (navbar) setHeight(navbar.getBoundingClientRect().height)\n })\n\n // The navbar can render after the layout (the Studio suspends while it\n // loads) and remount (switching workspaces), so it is looked up again\n // whenever the Studio's DOM changes — cheap, as it sits near the top\n const observeNavbar = () => {\n const next = root.querySelector(NAVBAR_SELECTOR)\n\n if (next === navbar) return\n\n if (navbar) resizeObserver.unobserve(navbar)\n\n navbar = next\n\n if (navbar) {\n resizeObserver.observe(navbar)\n setHeight(navbar.getBoundingClientRect().height)\n } else {\n setHeight(null)\n }\n }\n\n const mutationObserver = new MutationObserver(observeNavbar)\n\n mutationObserver.observe(root, {childList: true, subtree: true})\n observeNavbar()\n\n return () => {\n mutationObserver.disconnect()\n resizeObserver.disconnect()\n }\n }","[ref]","ref.current","() => ({baseOptions, stored: readStoredState()})","{input}","snapshot.hasTag('panel')","snapshot.hasTag('split')","snapshot.hasTag('moving')","snapshot.hasTag('switching')","resolveThemes(stored, baseOptions)","useMemo(\n () => resolveThemes(stored, baseOptions),\n [stored, baseOptions],\n )","activeOptions === null ? null : buildTheme(activeOptions)","useMemo(\n () => (activeOptions === null ? null : buildTheme(activeOptions)),\n [activeOptions],\n )","() => {\n const background = shownTheme?.v2?.color[scheme].default.bg\n\n if (background === undefined) return undefined\n\n const {style} = document.body\n const previous = style.backgroundColor\n\n style.backgroundColor = background\n\n return () => {\n style.backgroundColor = previous\n }\n }","color","[scheme, shownTheme]","() => () => {\n for (const url of Object.values(actorRef.getSnapshot().context.images)) {\n URL.revokeObjectURL(url)\n }\n }","[actorRef]","{\n baseOptions,\n themes,\n removed,\n active,\n images,\n view,\n open,\n split,\n mobile,\n navbarHeight,\n send,\n }","useMemo<ThemerContextValue>(\n () => ({\n baseOptions,\n themes,\n removed,\n active,\n images,\n view,\n open,\n split,\n mobile,\n navbarHeight,\n send,\n }),\n [active, baseOptions, images, mobile, navbarHeight, open, removed, send, split, themes, view],\n )","() => send({type: 'layout.transitioned'})","reduceMotion\n ? {\n copyEnter: 'none',\n copyExit: 'none',\n panelEnter: 'none',\n panelExit: 'none',\n update: 'none',\n studioUpdate: 'none',\n }\n : {\n copyEnter: mobile ? splitTransitionClasses.dropIn : splitTransitionClasses.slideIn,\n copyExit: mobile ? splitTransitionClasses.dropOut : splitTransitionClasses.slideOut,\n panelEnter: panelTransitionClasses.slideIn,\n panelExit: panelTransitionClasses.slideOut,\n // The copy and the panel cross-fade to another theme, in place\n update: switching ? splitTransitionClasses.crossfade : 'none',\n // The Studio too — unless it is giving way or taking room, which cross-fades as it goes\n studioUpdate: moving\n ? splitTransitionClasses.resize\n : switching\n ? splitTransitionClasses.crossfade\n : 'none',\n }","shownOpen && (\n <Activity mode={shownSplit ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"opposite\"\n enter={classes.copyEnter}\n exit={classes.copyExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <StudioPreview\n borderBottom={mobile}\n borderRight={!mobile}\n scheme={oppositeScheme}\n theme={shownTheme}\n >\n {studio}\n </StudioPreview>\n </ViewTransition>\n </Activity>\n )","copyEnter","copyExit","update","<StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>","<ViewTransition key=\"primary\" onUpdate={transitioned} update={classes.studioUpdate}>\n <StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>\n </ViewTransition>","studioUpdate","<ResizableSidebar overlay={mobile} />","<ThemeProvider theme={shownTheme ?? undefined}>\n <ResizableSidebar overlay={mobile} />\n </ThemeProvider>","shownTheme ?? undefined","<ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n <ResizableSidebar overlay={mobile} />\n </ThemeProvider>\n </ViewTransition>","panelEnter","panelExit","<Activity mode={shownOpen ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n <ResizableSidebar overlay={mobile} />\n </ThemeProvider>\n </ViewTransition>\n </Activity>","shownOpen ? 'visible' : 'hidden'","<Flex className={layout} direction={mobile ? 'column' : 'row'} height=\"fill\" sizing=\"border\">\n {/* The opposite scheme comes first — on the far side of the sidebar,\n or on top on small screens — so the Studio the user was looking at\n stays where it is, mounted, in its own scheme. A whole second\n Studio is too costly to keep around (its styled-components alone\n insert CSS as they render), so it only exists while the sidebar is\n open — hidden until the split shows, warmed up for the transition */}\n {shownOpen && (\n <Activity mode={shownSplit ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"opposite\"\n enter={classes.copyEnter}\n exit={classes.copyExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <StudioPreview\n borderBottom={mobile}\n borderRight={!mobile}\n scheme={oppositeScheme}\n theme={shownTheme}\n >\n {studio}\n </StudioPreview>\n </ViewTransition>\n </Activity>\n )}\n {/* The Studio gives way and takes room while the machine says the\n layout is moving, and cross-fades while it says a theme is being\n switched to — the Studio updates in transitions of its own all the\n time, and none of those may animate it. Once a transition is under\n way the machine hears of it, and the Studio is its own again before\n the next commit */}\n <ViewTransition key=\"primary\" onUpdate={transitioned} update={classes.studioUpdate}>\n <StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>\n </ViewTransition>\n\n {/* The sidebar is small and cheap: it stays mounted, hidden while\n closed, keeping its state and ready to show */}\n <Activity mode={shownOpen ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n <ResizableSidebar overlay={mobile} />\n </ThemeProvider>\n </ViewTransition>\n </Activity>\n </Flex>","mobile ? 'column' : 'row'","<ThemerContext.Provider value={context}>\n <Flex className={layout} direction={mobile ? 'column' : 'row'} height=\"fill\" sizing=\"border\">\n {/* The opposite scheme comes first — on the far side of the sidebar,\n or on top on small screens — so the Studio the user was looking at\n stays where it is, mounted, in its own scheme. A whole second\n Studio is too costly to keep around (its styled-components alone\n insert CSS as they render), so it only exists while the sidebar is\n open — hidden until the split shows, warmed up for the transition */}\n {shownOpen && (\n <Activity mode={shownSplit ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"opposite\"\n enter={classes.copyEnter}\n exit={classes.copyExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <StudioPreview\n borderBottom={mobile}\n borderRight={!mobile}\n scheme={oppositeScheme}\n theme={shownTheme}\n >\n {studio}\n </StudioPreview>\n </ViewTransition>\n </Activity>\n )}\n {/* The Studio gives way and takes room while the machine says the\n layout is moving, and cross-fades while it says a theme is being\n switched to — the Studio updates in transitions of its own all the\n time, and none of those may animate it. Once a transition is under\n way the machine hears of it, and the Studio is its own again before\n the next commit */}\n <ViewTransition key=\"primary\" onUpdate={transitioned} update={classes.studioUpdate}>\n <StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>\n </ViewTransition>\n\n {/* The sidebar is small and cheap: it stays mounted, hidden while\n closed, keeping its state and ready to show */}\n <Activity mode={shownOpen ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n <ResizableSidebar overlay={mobile} />\n </ThemeProvider>\n </ViewTransition>\n </Activity>\n </Flex>\n </ThemerContext.Provider>","ThemerContext.Provider","<Card\n borderBottom={borderBottom}\n borderRight={borderRight}\n className={scheme ? studioScheme[scheme] : undefined}\n flex={1}\n height=\"fill\"\n overflow=\"hidden\"\n ref={ref}\n >\n {children}\n </Card>","scheme ? studioScheme[scheme] : undefined","<ThemeProvider scheme={scheme} theme={theme ?? undefined}>\n <Card\n borderBottom={borderBottom}\n borderRight={borderRight}\n className={scheme ? studioScheme[scheme] : undefined}\n flex={1}\n height=\"fill\"\n overflow=\"hidden\"\n ref={ref}\n >\n {children}\n </Card>\n </ThemeProvider>","theme ?? undefined","<Text size={1}>Themer</Text>","() => send({type: 'sidebar.toggle'})","<Tooltip animate content={<Text size={1}>Themer</Text>} portal>\n <Button\n aria-label=\"Themer\"\n icon={ColorWheelIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.toggle'})}\n // The Studio's own navbar buttons go through a wrapper that pins them\n // to this padding, where `@sanity/ui` defaults to a roomier 3\n padding={2}\n selected={open}\n />\n </Tooltip>","props.__internal_actions ?? []","__internal_actions","props.renderDefault({\n ...props,\n __internal_actions: [\n ...(props.__internal_actions ?? []),\n {\n location: 'topbar',\n name: 'themer-topbar',\n render: () => <ThemerNavbarButton />,\n },\n {\n icon: ColorWheelIcon,\n location: 'sidebar',\n name: 'themer-sidebar',\n onAction: () => send({type: 'sidebar.toggle'}),\n selected: open,\n title: 'Themer',\n },\n ],\n })","() => <ThemerNavbarButton />","<ThemerLayout {...props} baseOptions={baseOptions} />"],"sources":["../src/tool/context.ts","../src/tool/imagePalette.ts","../src/tool/options.ts","../src/tool/themes.ts","../src/tool/storage.ts","../src/tool/machine.ts","../src/tool/ScrollArea.css.ts","../src/tool/ScrollArea.tsx","../src/tool/ThemePreview.css.ts","../src/tool/ThemePreview.tsx","../src/tool/ThemeThumbnail.tsx","../src/tool/RemovedThemes.css.ts","../src/tool/RemovedThemes.tsx","../src/tool/TooltipButton.tsx","../src/tool/ImageFileButton.tsx","../src/tool/imageFile.ts","../src/tool/useImagePalette.ts","../src/tool/ImagePaletteSection.css.ts","../src/tool/ImagePaletteSection.tsx","../src/tool/ThemeEditor.css.ts","../src/tool/ThemeEditor.tsx","../src/tool/share.ts","../src/tool/useThemeCodes.ts","../src/tool/PasteThemeDialog.tsx","../src/tool/ThemeCard.css.ts","../src/tool/ThemeCard.tsx","../src/tool/ThemeList.css.ts","../src/tool/ThemeList.tsx","../src/tool/snippet.ts","../src/tool/ThemeSnippetDialog.tsx","../src/tool/ThemerSidebar.css.ts","../src/tool/ThemerSidebar.tsx","../src/tool/ResizableSidebar.css.ts","../src/tool/ResizableSidebar.tsx","../src/tool/usePrefersReducedMotion.ts","../src/tool/useStudioNavbarHeight.ts","../src/tool/ThemerLayout.css.ts","../src/tool/ThemerLayout.tsx","../src/tool/ThemerNavbar.tsx","../src/tool/plugin.tsx"],"sourcesContent":["import {createContext, useContext} from 'react'\n\nimport {BuildThemeOptions} from '../theme/options'\nimport {ThemerEvent} from './machine'\nimport {ThemerTheme} from './themes'\n\n/**\n * The flow the sidebar is in: picking a theme from the list, editing one of\n * the user's own themes, or restoring removed ones.\n *\n * @internal\n */\nexport type ThemerView =\n | {name: 'list'}\n | {\n name: 'edit'\n slug: string\n /** Whether the title input should take focus, for themes that were just created */\n focusTitle: boolean\n }\n | {name: 'removed'}\n\n/** @internal */\nexport interface ThemerContextValue {\n /** The theme options the Studio's configured theme was generated from */\n baseOptions: BuildThemeOptions\n /** The themes to pick from, in list order */\n themes: ThemerTheme[]\n /** The removed themes, which can be restored */\n removed: ThemerTheme[]\n /** The applied theme */\n active: ThemerTheme\n /** Object URLs of the images themes took their palette from this session, by slug */\n images: Record<string, string>\n view: ThemerView\n /** Whether the themer sidebar is open */\n open: boolean\n /** Whether the Studio shows twice side by side, in light and dark */\n split: boolean\n /** Whether the Studio is on a small screen, where the sidebar covers it and the split preview stacks */\n mobile: boolean\n /** The height of the Studio navbar, which the sidebar's header matches — `null` until it has rendered */\n navbarHeight: number | null\n /** Sends an event to the themer machine */\n send: (event: ThemerEvent) => void\n}\n\n/** @internal */\nexport const ThemerContext = createContext<ThemerContextValue | null>(null)\n\n/** @internal */\nexport function useThemer(): ThemerContextValue {\n const context = useContext(ThemerContext)\n\n if (!context) {\n throw new Error('useThemer must be used within the `themerTool` plugin')\n }\n\n return context\n}\n","import {type ThemeColorSchemeKey} from '@sanity/ui/theme'\n\nimport {hexToHsl, hslToHex, relativeLuminance} from '../theme/hsl'\nimport {BuildThemeOptions, DEFAULT_BACKGROUND, SCHEMES, SchemeThemeOptions} from '../theme/options'\n\n/**\n * The swatches of an image palette — the same set the Sanity image pipeline\n * derives for uploaded assets (`metadata.palette`), extracted on device here.\n *\n * @internal\n */\nexport type ImagePaletteKey =\n | 'dominant'\n | 'vibrant'\n | 'lightVibrant'\n | 'darkVibrant'\n | 'muted'\n | 'lightMuted'\n | 'darkMuted'\n\n/**\n * An image palette: a hex color per swatch, or `null` when the image has no\n * color that qualifies for it.\n *\n * @internal\n */\nexport type ImagePalette = Record<ImagePaletteKey, string | null>\n\n/** @internal */\nexport const IMAGE_PALETTE_KEYS: ImagePaletteKey[] = [\n 'vibrant',\n 'lightVibrant',\n 'darkVibrant',\n 'muted',\n 'lightMuted',\n 'darkMuted',\n 'dominant',\n]\n\n/**\n * The swatches a theme can be built around — every swatch but the dominant\n * one, which only repeats one of them.\n *\n * @internal\n */\nexport type ImagePaletteVariant = Exclude<ImagePaletteKey, 'dominant'>\n\n/** The variants in the order the tool offers them @internal */\nexport const IMAGE_PALETTE_VARIANTS: ImagePaletteVariant[] = [\n 'muted',\n 'vibrant',\n 'lightMuted',\n 'lightVibrant',\n 'darkMuted',\n 'darkVibrant',\n]\n\n/** @internal */\nexport const IMAGE_PALETTE_TITLES: Record<ImagePaletteKey, string> = {\n dominant: 'Dominant',\n vibrant: 'Vibrant',\n lightVibrant: 'Light vibrant',\n darkVibrant: 'Dark vibrant',\n muted: 'Muted',\n lightMuted: 'Light muted',\n darkMuted: 'Dark muted',\n}\n\n/** Pixels the way a canvas hands them out: rows of RGBA bytes @internal */\nexport interface PixelData {\n data: ArrayLike<number>\n width: number\n height: number\n}\n\ninterface Bucket {\n count: number\n r: number\n g: number\n b: number\n}\n\ninterface Candidate {\n hex: string\n population: number\n saturation: number\n lightness: number\n}\n\n/** What a swatch looks for: a lightness and saturation to aim at, within bounds */\ninterface Target {\n lightness: [target: number, min: number, max: number]\n saturation: [target: number, min: number, max: number]\n}\n\n/**\n * The swatch targets of the Vibrant algorithm (the one behind Sanity's image\n * palettes), in the order they get to claim colors.\n */\nconst TARGETS: Array<[key: Exclude<ImagePaletteKey, 'dominant'>, target: Target]> = [\n ['vibrant', {lightness: [0.5, 0.3, 0.7], saturation: [1, 0.35, 1]}],\n ['lightVibrant', {lightness: [0.74, 0.55, 1], saturation: [1, 0.35, 1]}],\n ['darkVibrant', {lightness: [0.26, 0, 0.45], saturation: [1, 0.35, 1]}],\n ['muted', {lightness: [0.5, 0.3, 0.7], saturation: [0.3, 0, 0.4]}],\n ['lightMuted', {lightness: [0.74, 0.55, 1], saturation: [0.3, 0, 0.4]}],\n ['darkMuted', {lightness: [0.26, 0, 0.45], saturation: [0.3, 0, 0.4]}],\n]\n\nconst SATURATION_WEIGHT = 3\nconst LIGHTNESS_WEIGHT = 6.5\nconst POPULATION_WEIGHT = 0.5\n\n/** Pixels this transparent are not part of the picture */\nconst MINIMUM_ALPHA = 125\n\n/** Near-black and near-white pixels are backdrop rather than color */\nconst MINIMUM_LIGHTNESS = 0.05\nconst MAXIMUM_LIGHTNESS = 0.95\n\n/** How many of the 8 bits per channel survive the quantization */\nconst QUANTIZE_BITS = 5\n\n/**\n * Extracts the color palette of an image from its pixels, the way the Vibrant\n * algorithm does: the colors are quantized into a coarse histogram, and each\n * swatch claims the color that comes closest to its target lightness and\n * saturation — weighted by how much of the image it covers — that no other\n * swatch has claimed yet. The dominant swatch is simply the most common\n * color. Runs anywhere pixels come from, so it can run on device.\n *\n * @internal\n */\nexport function extractImagePalette(pixels: PixelData): ImagePalette {\n const candidates = quantize(pixels)\n const palette: ImagePalette = {\n dominant: null,\n vibrant: null,\n lightVibrant: null,\n darkVibrant: null,\n muted: null,\n lightMuted: null,\n darkMuted: null,\n }\n\n if (candidates.length === 0) return palette\n\n const maximumPopulation = Math.max(...candidates.map((candidate) => candidate.population))\n const claimed = new Set<string>()\n\n for (const [key, target] of TARGETS) {\n let best: Candidate | null = null\n let bestScore = -Infinity\n\n for (const candidate of candidates) {\n if (claimed.has(candidate.hex) || !matchesTarget(candidate, target)) continue\n\n const score = scoreCandidate(candidate, target, maximumPopulation)\n\n if (score > bestScore) {\n best = candidate\n bestScore = score\n }\n }\n\n if (best) {\n palette[key] = best.hex\n claimed.add(best.hex)\n }\n }\n\n palette.dominant = candidates.reduce((most, candidate) =>\n candidate.population > most.population ? candidate : most,\n ).hex\n\n return palette\n}\n\n/**\n * Turns the pixels into a histogram of quantized colors, each averaged over\n * the pixels it stands for, leaving out transparent pixels and the near-black\n * and near-white ones that are backdrop rather than color.\n */\nfunction quantize(pixels: PixelData): Candidate[] {\n const {data} = pixels\n const buckets = new Map<number, Bucket>()\n const shift = 8 - QUANTIZE_BITS\n const length = Math.min(data.length, pixels.width * pixels.height * 4)\n\n for (let offset = 0; offset + 3 < length; offset += 4) {\n const r = data[offset]\n const g = data[offset + 1]\n const b = data[offset + 2]\n const a = data[offset + 3]\n\n if (a < MINIMUM_ALPHA) continue\n\n const lightness = (Math.max(r, g, b) + Math.min(r, g, b)) / 510\n\n if (lightness < MINIMUM_LIGHTNESS || lightness > MAXIMUM_LIGHTNESS) continue\n\n const key =\n ((r >> shift) << (QUANTIZE_BITS * 2)) | ((g >> shift) << QUANTIZE_BITS) | (b >> shift)\n const bucket = buckets.get(key)\n\n if (bucket) {\n bucket.count++\n bucket.r += r\n bucket.g += g\n bucket.b += b\n } else {\n buckets.set(key, {count: 1, r, g, b})\n }\n }\n\n const candidates: Candidate[] = []\n\n for (const bucket of buckets.values()) {\n const r = Math.round(bucket.r / bucket.count)\n const g = Math.round(bucket.g / bucket.count)\n const b = Math.round(bucket.b / bucket.count)\n const {saturation, lightness} = saturationAndLightness(r, g, b)\n\n candidates.push({hex: rgbToHex(r, g, b), population: bucket.count, saturation, lightness})\n }\n\n return candidates\n}\n\nfunction matchesTarget(candidate: Candidate, target: Target): boolean {\n return (\n candidate.lightness >= target.lightness[1] &&\n candidate.lightness <= target.lightness[2] &&\n candidate.saturation >= target.saturation[1] &&\n candidate.saturation <= target.saturation[2]\n )\n}\n\nfunction scoreCandidate(candidate: Candidate, target: Target, maximumPopulation: number): number {\n return (\n SATURATION_WEIGHT * (1 - Math.abs(candidate.saturation - target.saturation[0])) +\n LIGHTNESS_WEIGHT * (1 - Math.abs(candidate.lightness - target.lightness[0])) +\n POPULATION_WEIGHT * (candidate.population / maximumPopulation)\n )\n}\n\nfunction saturationAndLightness(r: number, g: number, b: number) {\n const max = Math.max(r, g, b) / 255\n const min = Math.min(r, g, b) / 255\n const lightness = (max + min) / 2\n const delta = max - min\n const saturation =\n delta === 0 ? 0 : lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min)\n\n return {saturation, lightness}\n}\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n return `#${[r, g, b].map((channel) => channel.toString(16).padStart(2, '0')).join('')}`\n}\n\n/** Reads `#rgb` and `#rrggbb` alike — stored palettes may hold either */\nfunction hexToRgb(hex: string): [number, number, number] {\n const expanded =\n hex.length === 4 ? `${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex.slice(1)\n\n return [\n parseInt(expanded.slice(0, 2), 16),\n parseInt(expanded.slice(2, 4), 16),\n parseInt(expanded.slice(4, 6), 16),\n ]\n}\n\n/** Mixes `amount` of `color` into `base` */\nfunction tint(base: string, color: string, amount: number): string {\n const [r1, g1, b1] = hexToRgb(base)\n const [r2, g2, b2] = hexToRgb(color)\n\n return rgbToHex(\n Math.round(r1 + (r2 - r1) * amount),\n Math.round(g1 + (g2 - g1) * amount),\n Math.round(b1 + (b2 - b1) * amount),\n )\n}\n\n/** How much of the palette tints each scheme's stock background */\nconst BACKGROUND_TINT = {light: 0.08, dark: 0.3}\n\n/**\n * The WCAG AA contrast for normal text — what a button label needs against\n * the button. Sanity UI labels solid buttons in white on the accent's 500\n * tint in the light scheme and in black on its 400 tint in the dark scheme\n * (lighter than the accent, so an accent that passes against black passes\n * there too).\n */\nconst MINIMUM_ACCENT_CONTRAST = 4.5\nconst BUTTON_LABEL: Record<ThemeColorSchemeKey, string> = {light: '#ffffff', dark: '#000000'}\nconst ACCENT_LIGHTNESS_STEP = 0.01\n\nfunction contrastRatio(a: string, b: string): number {\n const [lighter, darker] = [relativeLuminance(a), relativeLuminance(b)].sort((x, y) => y - x)\n\n return (lighter + 0.05) / (darker + 0.05)\n}\n\n/**\n * Makes an accent readable as a button in the given scheme: a light scheme\n * accent is darkened until white text on it reaches AA contrast, a dark\n * scheme accent lightened until black text does — as far as the accent's\n * hue and saturation allow. Accents that already pass come back untouched.\n *\n * @internal\n */\nexport function readableAccent(hex: string, scheme: ThemeColorSchemeKey): string {\n const label = BUTTON_LABEL[scheme]\n const {h, s} = hexToHsl(hex)\n let {l} = hexToHsl(hex)\n let current = hex\n\n while (contrastRatio(current, label) < MINIMUM_ACCENT_CONTRAST) {\n const next = scheme === 'light' ? l - ACCENT_LIGHTNESS_STEP : l + ACCENT_LIGHTNESS_STEP\n\n if (next < 0 || next > 1) break\n\n l = next\n current = hslToHex({h, s, l})\n }\n\n return current\n}\n\n/** The accent a variant gives a scheme, made readable on a button */\nfunction variantAccent(\n palette: ImagePalette,\n variant: ImagePaletteVariant | null,\n scheme: ThemeColorSchemeKey,\n): string | null {\n const swatch =\n (variant ? palette[variant] : null) ??\n palette.vibrant ??\n (scheme === 'light' ? palette.darkVibrant : palette.lightVibrant) ??\n palette.dominant\n\n return swatch === null ? null : readableAccent(swatch, scheme)\n}\n\n/**\n * Derives theme options from an image palette: the accent of both schemes is\n * the swatch of the chosen variant — or, without one, the vibrant swatch,\n * each scheme falling back to the vibrant swatch of its own end of the scale —\n * adjusted until it is readable as a button. The muted swatch becomes the text color, and the light and dark muted\n * swatches tint the stock backgrounds of their scheme — enough to carry the\n * image's tone without giving up a usable canvas. Whatever the palette lacks\n * stays at the theme defaults.\n *\n * @internal\n */\nexport function optionsFromImagePalette(\n palette: ImagePalette,\n variant?: ImagePaletteVariant,\n): BuildThemeOptions {\n const options: BuildThemeOptions = {}\n\n const light = schemeFromPalette(\n variantAccent(palette, variant ?? null, 'light'),\n palette.muted ?? palette.darkMuted,\n palette.lightMuted ?? palette.muted ?? palette.lightVibrant,\n 'light',\n )\n const dark = schemeFromPalette(\n variantAccent(palette, variant ?? null, 'dark'),\n palette.muted ?? palette.lightMuted,\n palette.darkMuted ?? palette.muted ?? palette.darkVibrant,\n 'dark',\n )\n\n if (light) options.light = light\n if (dark) options.dark = dark\n\n return options\n}\n\nfunction schemeFromPalette(\n accent: string | null,\n text: string | null,\n backgroundTint: string | null,\n scheme: 'light' | 'dark',\n): SchemeThemeOptions | null {\n const options: SchemeThemeOptions = {}\n\n if (accent) options.accent = accent\n if (text) options.text = text\n if (backgroundTint) {\n options.background = tint(DEFAULT_BACKGROUND[scheme], backgroundTint, BACKGROUND_TINT[scheme])\n }\n\n return Object.keys(options).length > 0 ? options : null\n}\n\n/**\n * Applies an image palette to existing theme options: the colors the palette\n * provides replace the current ones scheme by scheme, and whatever it does\n * not touch — like the contrast — stays as it is.\n *\n * @internal\n */\nexport function applyImagePalette(\n options: BuildThemeOptions,\n palette: ImagePalette,\n variant?: ImagePaletteVariant,\n): BuildThemeOptions {\n const derived = optionsFromImagePalette(palette, variant)\n const next: BuildThemeOptions = {...options}\n\n for (const scheme of SCHEMES) {\n if (derived[scheme]) next[scheme] = {...options[scheme], ...derived[scheme]}\n }\n\n return next\n}\n\n/**\n * The variant a theme is built around: the one whose swatch is the theme's\n * accent, or `null` when the accent comes from somewhere else.\n *\n * @internal\n */\nexport function currentImageVariant(\n options: BuildThemeOptions,\n palette: ImagePalette,\n): ImagePaletteVariant | null {\n const accent = options.light?.accent\n\n if (!accent) return null\n\n return (\n IMAGE_PALETTE_VARIANTS.find(\n (variant) => palette[variant] !== null && variantAccent(palette, variant, 'light') === accent,\n ) ?? null\n )\n}\n\n/**\n * How much a swatch has going for it as an accent: saturation first, then\n * closeness to mid lightness — the range where an accent reads best.\n */\nfunction interest(hex: string): number {\n const {s, l} = hexToHsl(hex)\n\n return 0.1 + 0.55 * s + 0.35 * (1 - Math.abs(l - 0.5) * 2)\n}\n\n/**\n * Picks a variant for the \"I'm feeling lucky\" button: a random one, weighted\n * by how interesting its swatch is as an accent, and never the one the theme\n * already uses while there are others to pick from.\n *\n * @internal\n */\nexport function pickLuckyVariant(\n palette: ImagePalette,\n options: {exclude?: ImagePaletteVariant | null; random?: () => number} = {},\n): ImagePaletteVariant | null {\n const {exclude = null, random = Math.random} = options\n let candidates = IMAGE_PALETTE_VARIANTS.filter((variant) => palette[variant] !== null)\n\n if (candidates.length > 1) {\n candidates = candidates.filter((variant) => variant !== exclude)\n }\n\n if (candidates.length === 0) return null\n\n const weights = candidates.map((variant) => interest(palette[variant]!))\n const total = weights.reduce((sum, weight) => sum + weight, 0)\n let pick = random() * total\n\n for (const [index, variant] of candidates.entries()) {\n pick -= weights[index]\n\n if (pick < 0) return variant\n }\n\n return candidates.at(-1) ?? null\n}\n\n/** A theme title for the image a palette came from, e.g. `sunset-beach.jpg` → `sunset beach` @internal */\nexport function titleFromFileName(name: string): string {\n return (\n name\n .replace(/\\.[^.]+$/, '')\n .replace(/[-_]+/g, ' ')\n .trim() || 'Image theme'\n )\n}\n","import {\n BuildThemeOptions,\n DEFAULT_ACCENT,\n DEFAULT_BACKGROUND,\n DEFAULT_CONTRAST,\n deriveTextColor,\n ResolvedSchemeOptions,\n resolveThemeOptions,\n SCHEMES,\n SchemeThemeOptions,\n} from '../theme/options'\n\n/**\n * Reduces theme options to the minimal object that recreates them through\n * `buildTheme`: in each scheme the accent is dropped when it is the stock\n * one, the text color when it matches the one derived from the accent, and\n * the background and contrast when they match the defaults — and a scheme\n * with nothing left is dropped altogether. Options that boil down to the\n * stock Studio theme reduce to `null` — they need nothing from this package.\n *\n * @internal\n */\nexport function minimizeOptions(options: BuildThemeOptions): BuildThemeOptions | null {\n const resolved = resolveThemeOptions(options)\n const minimized: BuildThemeOptions = {}\n\n for (const scheme of SCHEMES) {\n const values = resolved[scheme]\n const kept: SchemeThemeOptions = {}\n\n if (values.accent !== DEFAULT_ACCENT) kept.accent = values.accent\n if (values.text !== deriveTextColor(values.accent)) kept.text = values.text\n if (values.background !== DEFAULT_BACKGROUND[scheme]) kept.background = values.background\n if (values.contrast !== DEFAULT_CONTRAST) kept.contrast = values.contrast\n\n if (Object.keys(kept).length > 0) minimized[scheme] = kept\n }\n\n return Object.keys(minimized).length === 0 ? null : minimized\n}\n\n/**\n * Whether two sets of theme options generate the same theme, comparing their\n * resolved forms so that derived and explicit values (and hex casing) don't\n * matter.\n *\n * @internal\n */\nexport function sameOptions(a: BuildThemeOptions, b: BuildThemeOptions): boolean {\n const left = resolveThemeOptions(a)\n const right = resolveThemeOptions(b)\n\n return SCHEMES.every((scheme) => sameScheme(left[scheme], right[scheme]))\n}\n\nfunction sameScheme(a: ResolvedSchemeOptions, b: ResolvedSchemeOptions): boolean {\n return (\n a.accent === b.accent &&\n a.text === b.text &&\n a.background === b.background &&\n a.contrast === b.contrast\n )\n}\n","import {BuildThemeOptions} from '../theme/options'\nimport {presets} from '../theme/presets'\nimport {ImagePalette} from './imagePalette'\nimport {sameOptions} from './options'\n\n/**\n * Where a theme in the themer list comes from: the theme the Studio config\n * was generated from, one of the presets defined in code, or one the user\n * added in the tool. Only custom themes are editable — the others are\n * duplicated into a custom theme first.\n */\ntype ThemerThemeSource = 'config' | 'preset' | 'custom'\n\n/** A theme the themer tool lists @internal */\nexport interface ThemerTheme {\n slug: string\n title: string\n options: BuildThemeOptions\n source: ThemerThemeSource\n /** The palette of the image the theme's colors were taken from, for custom themes */\n palette?: ImagePalette\n}\n\n/** A theme the user added in the tool, as persisted @internal */\nexport interface CustomTheme {\n slug: string\n title: string\n options: BuildThemeOptions\n /** The palette of the image the theme's colors were taken from */\n palette?: ImagePalette\n}\n\n/** The persisted themer state @internal */\nexport interface ThemerState {\n /**\n * The slug of the applied theme — `null` applies nothing on top of the\n * Studio's configured theme\n */\n active: string | null\n /** The themes the user added */\n custom: CustomTheme[]\n /** The slugs of the themes the user removed from the list */\n removed: string[]\n /**\n * The slugs of the themes in the order the user arranged them — themes not\n * in it (new presets, themes added since) follow in their default order\n */\n order: string[]\n}\n\n/** The slug of the theme the Studio config was generated from @internal */\nexport const CONFIG_SLUG = 'config'\n\n/** The title of the theme the Studio config was generated from */\nconst CONFIG_TITLE = 'Studio config'\n\n/** The title new themes start out with @internal */\nexport const UNTITLED_THEME = 'Untitled theme'\n\n/** @internal */\nexport const initialThemerState: ThemerState = {active: null, custom: [], removed: [], order: []}\n\n/** The themes the tool works with, derived from the persisted state @internal */\nexport interface ResolvedThemes {\n /** The themes to pick from, in list order */\n themes: ThemerTheme[]\n /** The removed themes, which can be restored */\n removed: ThemerTheme[]\n /** The applied theme */\n active: ThemerTheme\n}\n\n/**\n * Resolves the list of themes: the configured theme first, then the presets\n * (minus the ones that would only repeat the configured theme), then the\n * user's own themes — rearranged into the order the user dragged them into,\n * with themes that order does not know about kept in that default order after\n * the ones it does. Removed themes are set aside so they can be restored, and\n * the active theme falls back to the configured one when its slug no longer\n * resolves.\n *\n * @internal\n */\nexport function resolveThemes(state: ThemerState, baseOptions: BuildThemeOptions): ResolvedThemes {\n const config: ThemerTheme = {\n slug: CONFIG_SLUG,\n title: CONFIG_TITLE,\n options: baseOptions,\n source: 'config',\n }\n\n const all: ThemerTheme[] = [config]\n\n for (const preset of presets) {\n if (!sameOptions(preset.options, baseOptions)) {\n all.push({slug: preset.slug, title: preset.title, options: preset.options, source: 'preset'})\n }\n }\n\n for (const theme of state.custom) {\n all.push({\n slug: theme.slug,\n title: theme.title,\n options: theme.options,\n source: 'custom',\n ...(theme.palette ? {palette: theme.palette} : {}),\n })\n }\n\n const position = new Map(state.order.map((slug, index) => [slug, index]))\n const rank = (theme: ThemerTheme) => position.get(theme.slug) ?? state.order.length\n\n // A stable sort: the themes the order does not know about keep their default order\n all.sort((a, b) => rank(a) - rank(b))\n\n const removedSlugs = new Set(state.removed)\n const themes = all.filter((theme) => !removedSlugs.has(theme.slug))\n const removed = all.filter((theme) => removedSlugs.has(theme.slug))\n const active = themes.find((theme) => theme.slug === state.active) ?? config\n\n return {themes, removed, active}\n}\n\n/**\n * Creates a custom theme with a slug that cannot collide with the presets or\n * the configured theme.\n *\n * @internal\n */\nexport function createCustomTheme(\n title: string,\n options: BuildThemeOptions,\n palette?: ImagePalette,\n): CustomTheme {\n return {slug: `custom-${randomId()}`, title, options, ...(palette ? {palette} : {})}\n}\n\n/** The title of a duplicated theme @internal */\nexport function duplicateTitle(title: string): string {\n return `${displayTitle(title)} copy`\n}\n\n/** The title to show for a theme, falling back when the user cleared it @internal */\nexport function displayTitle(title: string): string {\n return title.trim() || UNTITLED_THEME\n}\n\nfunction randomId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n\n return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`\n}\n","import {isColor} from '../lib/mix'\nimport {\n BuildThemeOptions,\n MAXIMUM_CONTRAST,\n MINIMUM_CONTRAST,\n SCHEMES,\n SchemeThemeOptions,\n} from '../theme/options'\nimport {presets} from '../theme/presets'\nimport {IMAGE_PALETTE_KEYS, ImagePalette} from './imagePalette'\nimport {\n CONFIG_SLUG,\n createCustomTheme,\n CustomTheme,\n displayTitle,\n initialThemerState,\n ThemerState,\n} from './themes'\n\nconst STORAGE_KEY = 'sanityStudio:themer:state'\n\n/**\n * Where earlier versions of the tool kept their single draft theme — it is\n * migrated into a custom theme, so a draft survives the upgrade.\n */\nconst LEGACY_STORAGE_KEY = 'sanityStudio:themer:options'\n\n/** The title a draft from before themes had titles is imported under */\nconst LEGACY_DRAFT_TITLE = 'Draft theme'\n\nfunction sanitizeColor(value: unknown): string | null {\n return typeof value === 'string' && isColor(value) ? value.toLowerCase() : null\n}\n\nfunction sanitizeContrast(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value)\n ? Math.min(MAXIMUM_CONTRAST, Math.max(MINIMUM_CONTRAST, value))\n : null\n}\n\nfunction sanitizeSchemeOptions(value: unknown): SchemeThemeOptions {\n const options: SchemeThemeOptions = {}\n\n if (!value || typeof value !== 'object') return options\n\n const accent = sanitizeColor(Reflect.get(value, 'accent'))\n const text = sanitizeColor(Reflect.get(value, 'text'))\n const background = sanitizeColor(Reflect.get(value, 'background'))\n const contrast = sanitizeContrast(Reflect.get(value, 'contrast'))\n\n if (accent) options.accent = accent\n if (text) options.text = text\n if (background) options.background = background\n if (contrast !== null) options.contrast = contrast\n\n return options\n}\n\n/**\n * Options from before they were grouped by scheme: one accent, text color and\n * contrast for both schemes, and a background per scheme. Both schemes get\n * the shared colors, so the theme keeps looking the same.\n */\nfunction sanitizeFlatOptions(value: object): BuildThemeOptions | null {\n const accent = sanitizeColor(Reflect.get(value, 'accent'))\n\n if (!accent) return null\n\n const shared: SchemeThemeOptions = {accent}\n const text = sanitizeColor(Reflect.get(value, 'text'))\n const contrast = sanitizeContrast(Reflect.get(value, 'contrast'))\n\n if (text) shared.text = text\n if (contrast !== null) shared.contrast = contrast\n\n const backgrounds: unknown = Reflect.get(value, 'background')\n const options: BuildThemeOptions = {}\n\n for (const scheme of SCHEMES) {\n const background =\n backgrounds && typeof backgrounds === 'object'\n ? sanitizeColor(Reflect.get(backgrounds, scheme))\n : null\n\n options[scheme] = background ? {...shared, background} : {...shared}\n }\n\n return options\n}\n\nfunction sanitizeOptions(value: unknown): BuildThemeOptions | null {\n if (!value || typeof value !== 'object') return null\n\n // The flat shape always had an accent, the scheme shape never has one at the top\n if ('accent' in value) {\n return sanitizeFlatOptions(value)\n }\n\n const options: BuildThemeOptions = {}\n\n for (const scheme of SCHEMES) {\n const schemeOptions = sanitizeSchemeOptions(Reflect.get(value, scheme))\n\n if (Object.keys(schemeOptions).length > 0) options[scheme] = schemeOptions\n }\n\n return options\n}\n\nfunction isReservedSlug(slug: string): boolean {\n return slug === CONFIG_SLUG || presets.some((preset) => preset.slug === slug)\n}\n\nfunction sanitizeCustomTheme(value: unknown): CustomTheme | null {\n if (!value || typeof value !== 'object') return null\n\n const slug: unknown = Reflect.get(value, 'slug')\n\n if (typeof slug !== 'string' || !slug || isReservedSlug(slug)) return null\n\n const options = sanitizeOptions(Reflect.get(value, 'options'))\n\n if (!options) return null\n\n const title: unknown = Reflect.get(value, 'title')\n const palette = sanitizePalette(Reflect.get(value, 'palette'))\n\n return {\n slug,\n title: displayTitle(typeof title === 'string' ? title : ''),\n options,\n ...(palette ? {palette} : {}),\n }\n}\n\nfunction sanitizePalette(value: unknown): ImagePalette | null {\n if (!value || typeof value !== 'object') return null\n\n const palette: Partial<ImagePalette> = {}\n let swatches = 0\n\n for (const key of IMAGE_PALETTE_KEYS) {\n const swatch = sanitizeColor(Reflect.get(value, key))\n\n palette[key] = swatch\n if (swatch) swatches++\n }\n\n // oxlint-disable-next-line no-unsafe-type-assertion -- the loop assigns every key\n return swatches > 0 ? (palette as ImagePalette) : null\n}\n\nfunction sanitizeState(value: unknown): ThemerState {\n if (!value || typeof value !== 'object') return initialThemerState\n\n const custom: CustomTheme[] = []\n const rawCustom: unknown = Reflect.get(value, 'custom')\n\n if (Array.isArray(rawCustom)) {\n for (const item of rawCustom) {\n const theme = sanitizeCustomTheme(item)\n\n if (theme && !custom.some((existing) => existing.slug === theme.slug)) {\n custom.push(theme)\n }\n }\n }\n\n const removed: string[] = []\n const rawRemoved: unknown = Reflect.get(value, 'removed')\n\n if (Array.isArray(rawRemoved)) {\n for (const slug of rawRemoved) {\n const known =\n typeof slug === 'string' &&\n slug !== CONFIG_SLUG &&\n (presets.some((preset) => preset.slug === slug) ||\n custom.some((theme) => theme.slug === slug))\n\n if (known && !removed.includes(slug)) removed.push(slug)\n }\n }\n\n const order: string[] = []\n const rawOrder: unknown = Reflect.get(value, 'order')\n\n if (Array.isArray(rawOrder)) {\n for (const slug of rawOrder) {\n const known =\n typeof slug === 'string' &&\n (slug === CONFIG_SLUG ||\n presets.some((preset) => preset.slug === slug) ||\n custom.some((theme) => theme.slug === slug))\n\n if (known && !order.includes(slug)) order.push(slug)\n }\n }\n\n const rawActive: unknown = Reflect.get(value, 'active')\n\n return {active: typeof rawActive === 'string' ? rawActive : null, custom, removed, order}\n}\n\nfunction readLegacyState(): ThemerState | null {\n const raw = localStorage.getItem(LEGACY_STORAGE_KEY)\n\n if (!raw) return null\n\n const options = sanitizeOptions(JSON.parse(raw))\n\n if (!options) return null\n\n const theme = createCustomTheme(LEGACY_DRAFT_TITLE, options)\n\n return {active: theme.slug, custom: [theme], removed: [], order: []}\n}\n\n/**\n * Restores the themer state from localStorage, so the user's themes survive\n * studio reloads. Falls back to the initial state when nothing usable is\n * stored.\n *\n * @internal\n */\nexport function readStoredState(): ThemerState {\n try {\n if (typeof localStorage === 'undefined') return initialThemerState\n\n const raw = localStorage.getItem(STORAGE_KEY)\n\n if (raw) return sanitizeState(JSON.parse(raw))\n\n return readLegacyState() ?? initialThemerState\n } catch {\n return initialThemerState\n }\n}\n\n/** @internal */\nexport function writeStoredState(state: ThemerState): void {\n try {\n if (typeof localStorage === 'undefined') return\n\n localStorage.setItem(STORAGE_KEY, JSON.stringify(state))\n localStorage.removeItem(LEGACY_STORAGE_KEY)\n } catch {\n // Storage can be unavailable (e.g. private browsing) — themes just won't persist\n }\n}\n","import {assign, not, or, setup, SnapshotFrom, stateIn} from 'xstate'\n\nimport {BuildThemeOptions} from '../theme/options'\nimport {ImagePalette} from './imagePalette'\nimport {writeStoredState} from './storage'\nimport {\n CONFIG_SLUG,\n createCustomTheme,\n duplicateTitle,\n resolveThemes,\n ThemerState,\n UNTITLED_THEME,\n} from './themes'\n\n/**\n * How long the layout's motions — the panel's and the split preview's, and\n * the cross-fade to another theme — are taken to last when the layout never\n * says: it reports every view transition it starts, so this only ends a\n * motion that has none (reduced motion, a browser without view transitions),\n * where the `moving` and `switching` tags have nothing to affect. Generous,\n * so that it never runs out before a slow deferred commit starts the real one.\n *\n * @internal\n */\nexport const MOTION_DURATION = 2000\n\n/** What the themer machine starts from @internal */\nexport interface ThemerInput {\n /** The theme options the Studio's configured theme was generated from */\n baseOptions: BuildThemeOptions\n /** The persisted state of an earlier session */\n stored: ThemerState\n}\n\n/** @internal */\nexport interface ThemerMachineContext extends ThemerState {\n baseOptions: BuildThemeOptions\n /** The theme open in the editor, while the `flow` is `edit` */\n editing: {\n slug: string\n /** Whether the title input should take focus, for themes that were just created */\n focusTitle: boolean\n } | null\n /**\n * Object URLs of the images that themes took their palette from, by theme\n * slug — kept for the session only, so the image can be shown right after\n * it was picked\n */\n images: Record<string, string>\n}\n\n/** @internal */\nexport type ThemerEvent =\n | {type: 'sidebar.toggle'}\n | {type: 'sidebar.close'}\n /**\n * A view transition of the layout is under way — the panel or the split copy\n * arriving or leaving, the Studio giving way, taking room or cross-fading to\n * another theme — and the layout is no longer `moving` or `switching` for\n * the machine\n */\n | {type: 'layout.transitioned'}\n /**\n * Switches between showing the Studio once, in its own appearance, and\n * twice side by side, in light and dark\n */\n | {type: 'preview.toggle'}\n /** Applies a theme to the whole Studio */\n | {type: 'theme.pick'; slug: string}\n /**\n * Adds a theme and opens it in the editor — based on the applied theme, or\n * on the given title, options and image palette (e.g. from an image)\n */\n | {\n type: 'theme.add'\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n }\n /** Adds a copy of a theme and opens it in the editor */\n | {type: 'theme.duplicate'; slug: string}\n /** Adds a theme someone shared as a code, and applies it — staying in the list */\n | {type: 'theme.import'; title: string; options: BuildThemeOptions}\n /** Opens one of the user's own themes in the editor */\n | {type: 'theme.edit'; slug: string}\n | {\n type: 'theme.update'\n slug: string\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n }\n /** Takes a theme out of the list — it can be restored until it is deleted */\n | {type: 'theme.remove'; slug: string}\n /** Puts a removed theme back in the list */\n | {type: 'theme.restore'; slug: string}\n /** Deletes one of the user's own themes for good */\n | {type: 'theme.delete'; slug: string}\n /** Rearranges the list: the slugs of the listed themes, in their new order */\n | {type: 'theme.reorder'; order: string[]}\n /** Back to picking a theme */\n | {type: 'flow.list'}\n /** On to restoring removed themes */\n | {type: 'flow.removed'}\n\nfunction themesOf(context: ThemerMachineContext) {\n return resolveThemes(context, context.baseOptions)\n}\n\nfunction revokeObjectUrl(url: string) {\n if (typeof URL !== 'undefined' && typeof URL.revokeObjectURL === 'function') {\n URL.revokeObjectURL(url)\n }\n}\n\n/**\n * The state of the themer tool: three parallel regions, whether the\n * `sidebar` is open, which `flow` it is in — picking a theme from the `list`,\n * `edit`ing one of the user's own themes, or restoring `removed` ones — and\n * whether the `preview` shows the Studio once (`single`) or in light and dark\n * side by side (`split`). The context carries the persisted state (the\n * applied theme, the user's themes and what was removed) alongside what the\n * flows need.\n *\n * The sidebar and the preview pass through `opening`/`closing` and\n * `splitting`/`unsplitting` on their way, states tagged for the UI: `panel`\n * and `split` say what shows (the panel from `opening` on, the split copy from\n * `splitting` on), `moving` that the Studio is about to give way or take room\n * — which is when, and only when, the layout lets a view transition animate\n * it. A fourth region, `theme`, is `switching` (its tag) right after an event\n * that applies another theme — picking one, adding, duplicating, importing or\n * editing one, removing or deleting the applied one — when the layout\n * cross-fades to it; edits to the applied theme's colors are not switches,\n * they follow the pointer. These states last until the layout reports its\n * transition under way — every boundary reports, the panel's and the copy's\n * entering and leaving as much as the Studio's morphing — or for\n * `MOTION_DURATION` when nothing animates.\n *\n * Theme operations are handled in every flow, and the flows leave on their\n * own when they lose their subject: the editor when its theme is removed or\n * deleted, and the removed view when its last theme is restored or deleted.\n *\n * @internal\n */\nexport const themerMachine = setup({\n // XState reads `types` for their types only — the values are never used\n types: {\n // oxlint-disable-next-line no-unsafe-type-assertion -- type-level placeholder\n context: {} as ThemerMachineContext,\n // oxlint-disable-next-line no-unsafe-type-assertion -- type-level placeholder\n events: {} as ThemerEvent,\n // oxlint-disable-next-line no-unsafe-type-assertion -- type-level placeholder\n input: {} as ThemerInput,\n },\n guards: {\n isCustomTheme: ({context}, params: {slug: string}) =>\n themesOf(context).themes.some(\n (theme) => theme.slug === params.slug && theme.source === 'custom',\n ),\n isEditingListedTheme: ({context}) =>\n context.editing !== null &&\n themesOf(context).themes.some(\n (theme) => theme.slug === context.editing?.slug && theme.source === 'custom',\n ),\n hasRemovedThemes: ({context}) => themesOf(context).removed.length > 0,\n sidebarShown: or([stateIn({sidebar: 'opening'}), stateIn({sidebar: 'open'})]),\n // Only another theme than the applied one changes anything to cross-fade to\n isAnotherTheme: ({context}, params: {slug: string}) =>\n params.slug !== (context.active ?? CONFIG_SLUG),\n // Removing or deleting the applied theme is what changes the applied theme\n isAppliedTheme: ({context}, params: {slug: string}) => params.slug === context.active,\n // A theme added from given options (an image's) looks different from the applied one\n hasOptions: (_, params: {options: BuildThemeOptions | undefined}) =>\n params.options !== undefined,\n },\n actions: {\n // The state that survives sessions is written as the machine starts — that\n // completes the migration of what an earlier version stored — and on\n // every change to it\n persist: ({context}) => {\n const {active, custom, removed, order} = context\n\n writeStoredState({active, custom, removed, order})\n },\n // The image of a replaced palette or a deleted theme is released from memory\n revokeImage: (_, params: {url: string | undefined}) => {\n if (params.url) revokeObjectUrl(params.url)\n },\n pick: assign((_, params: {slug: string}) => ({\n active: params.slug === CONFIG_SLUG ? null : params.slug,\n })),\n add: assign(\n (\n {context},\n params: {\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n },\n ) => {\n const theme = createCustomTheme(\n params.title ?? UNTITLED_THEME,\n params.options ?? themesOf(context).active.options,\n params.palette,\n )\n\n return {\n active: theme.slug,\n custom: [...context.custom, theme],\n // A theme named after its image needs no renaming right away\n editing: {slug: theme.slug, focusTitle: params.title === undefined},\n images: params.imageUrl\n ? {...context.images, [theme.slug]: params.imageUrl}\n : context.images,\n }\n },\n ),\n import: assign(({context}, params: {title: string; options: BuildThemeOptions}) => {\n const theme = createCustomTheme(params.title, params.options)\n\n return {active: theme.slug, custom: [...context.custom, theme]}\n }),\n duplicate: assign(({context}, params: {slug: string}) => {\n const {themes, removed} = themesOf(context)\n const source = [...themes, ...removed].find((theme) => theme.slug === params.slug)\n\n if (!source) return {}\n\n const theme = createCustomTheme(duplicateTitle(source.title), source.options, source.palette)\n\n return {\n active: theme.slug,\n custom: [...context.custom, theme],\n editing: {slug: theme.slug, focusTitle: true},\n }\n }),\n startEditing: assign((_, params: {slug: string}) => ({\n active: params.slug,\n editing: {slug: params.slug, focusTitle: false},\n })),\n stopEditing: assign({editing: null}),\n update: assign(\n (\n {context},\n params: {\n slug: string\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n },\n ) => ({\n custom: context.custom.map((theme) => {\n if (theme.slug !== params.slug) return theme\n\n // Keep the options identity when only the title changes, so the\n // applied theme is not rebuilt on every keystroke\n return {\n ...theme,\n title: params.title ?? theme.title,\n options: params.options ?? theme.options,\n ...(params.palette ? {palette: params.palette} : {}),\n }\n }),\n images:\n params.imageUrl && context.custom.some((theme) => theme.slug === params.slug)\n ? {...context.images, [params.slug]: params.imageUrl}\n : context.images,\n }),\n ),\n remove: assign(({context}, params: {slug: string}) => {\n if (params.slug === CONFIG_SLUG || context.removed.includes(params.slug)) return {}\n\n return {\n active: context.active === params.slug ? null : context.active,\n removed: [...context.removed, params.slug],\n }\n }),\n restore: assign(({context}, params: {slug: string}) => ({\n removed: context.removed.filter((slug) => slug !== params.slug),\n })),\n reorder: assign(({context}, params: {order: string[]}) => {\n const listed = new Set(themesOf(context).themes.map((theme) => theme.slug))\n const order = params.order.filter((slug) => listed.has(slug))\n\n // The removed themes keep their place in line for when they are restored\n return {\n order: [...order, ...context.order.filter((slug) => !order.includes(slug))],\n }\n }),\n delete: assign(({context}, params: {slug: string}) => {\n if (!context.custom.some((theme) => theme.slug === params.slug)) return {}\n\n const {[params.slug]: _deleted, ...images} = context.images\n\n return {\n active: context.active === params.slug ? null : context.active,\n custom: context.custom.filter((theme) => theme.slug !== params.slug),\n removed: context.removed.filter((slug) => slug !== params.slug),\n images,\n }\n }),\n },\n}).createMachine({\n id: 'themer',\n context: ({input}) => ({\n ...input.stored,\n baseOptions: input.baseOptions,\n editing: null,\n images: {},\n }),\n entry: 'persist',\n type: 'parallel',\n states: {\n sidebar: {\n initial: 'closed',\n states: {\n closed: {\n on: {'sidebar.toggle': 'opening'},\n },\n opening: {\n tags: ['panel', 'moving'],\n after: {[MOTION_DURATION]: 'open'},\n on: {\n 'layout.transitioned': 'open',\n 'sidebar.toggle': 'closing',\n 'sidebar.close': 'closing',\n },\n },\n open: {\n tags: ['panel'],\n on: {'sidebar.toggle': 'closing', 'sidebar.close': 'closing'},\n },\n closing: {\n tags: ['moving'],\n after: {[MOTION_DURATION]: 'closed'},\n on: {'layout.transitioned': 'closed', 'sidebar.toggle': 'opening'},\n },\n },\n },\n // The split preview belongs to a sidebar session: closing the sidebar —\n // from its header or from the navbar — ends the split with it\n preview: {\n initial: 'single',\n states: {\n single: {\n on: {'preview.toggle': 'splitting'},\n },\n splitting: {\n tags: ['split', 'moving'],\n after: {[MOTION_DURATION]: 'split'},\n on: {\n 'layout.transitioned': 'split',\n 'preview.toggle': 'unsplitting',\n 'sidebar.close': 'unsplitting',\n 'sidebar.toggle': {guard: 'sidebarShown', target: 'unsplitting'},\n },\n },\n split: {\n tags: ['split'],\n on: {\n 'preview.toggle': 'unsplitting',\n 'sidebar.close': 'unsplitting',\n 'sidebar.toggle': {guard: 'sidebarShown', target: 'unsplitting'},\n },\n },\n unsplitting: {\n tags: ['moving'],\n after: {[MOTION_DURATION]: 'single'},\n on: {'layout.transitioned': 'single', 'preview.toggle': 'splitting'},\n },\n },\n },\n theme: {\n initial: 'applied',\n states: {\n // Only events that change what is applied count: adding a copy of the\n // applied theme, editing or duplicating it, picking it again, or\n // removing another theme leaves the Studio looking the same — with\n // nothing to cross-fade, nothing would report the switch over, and the\n // first edit that followed would cross-fade instead of following the\n // pointer\n applied: {\n on: {\n 'theme.pick': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.edit': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.duplicate': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.remove': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.delete': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.add': {\n guard: {type: 'hasOptions', params: ({event}) => ({options: event.options})},\n target: 'switching',\n },\n 'theme.import': 'switching',\n },\n },\n switching: {\n tags: ['switching'],\n after: {[MOTION_DURATION]: 'applied'},\n on: {\n 'layout.transitioned': 'applied',\n // Another switch starts the clock over\n 'theme.pick': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.edit': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.duplicate': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.remove': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.delete': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.add': {\n guard: {type: 'hasOptions', params: ({event}) => ({options: event.options})},\n target: 'switching',\n reenter: true,\n },\n 'theme.import': {target: 'switching', reenter: true},\n },\n },\n },\n },\n flow: {\n initial: 'list',\n on: {\n 'theme.pick': {\n actions: [{type: 'pick', params: ({event}) => ({slug: event.slug})}, 'persist'],\n },\n 'theme.add': {\n target: '.edit',\n actions: [\n {\n type: 'add',\n params: ({event}) => ({\n title: event.title,\n options: event.options,\n palette: event.palette,\n imageUrl: event.imageUrl,\n }),\n },\n 'persist',\n ],\n },\n 'theme.duplicate': {\n target: '.edit',\n actions: [{type: 'duplicate', params: ({event}) => ({slug: event.slug})}, 'persist'],\n },\n 'theme.edit': {\n target: '.edit',\n guard: {type: 'isCustomTheme', params: ({event}) => ({slug: event.slug})},\n actions: [{type: 'startEditing', params: ({event}) => ({slug: event.slug})}, 'persist'],\n },\n 'theme.update': {\n actions: [\n // A new image replaces the one the theme had\n {\n type: 'revokeImage',\n params: ({context, event}) => ({\n url: event.imageUrl ? context.images[event.slug] : undefined,\n }),\n },\n {\n type: 'update',\n params: ({event}) => ({\n slug: event.slug,\n title: event.title,\n options: event.options,\n palette: event.palette,\n imageUrl: event.imageUrl,\n }),\n },\n 'persist',\n ],\n },\n 'theme.remove': {\n actions: [{type: 'remove', params: ({event}) => ({slug: event.slug})}, 'persist'],\n },\n 'theme.restore': {\n actions: [{type: 'restore', params: ({event}) => ({slug: event.slug})}, 'persist'],\n },\n 'theme.delete': {\n actions: [\n {\n type: 'revokeImage',\n params: ({context, event}) => ({url: context.images[event.slug]}),\n },\n {type: 'delete', params: ({event}) => ({slug: event.slug})},\n 'persist',\n ],\n },\n 'theme.reorder': {\n actions: [{type: 'reorder', params: ({event}) => ({order: event.order})}, 'persist'],\n },\n 'theme.import': {\n actions: [\n {\n type: 'import',\n params: ({event}) => ({title: event.title, options: event.options}),\n },\n 'persist',\n ],\n },\n 'flow.list': '.list',\n 'flow.removed': {\n target: '.removed',\n guard: 'hasRemovedThemes',\n },\n },\n states: {\n list: {},\n edit: {\n exit: 'stopEditing',\n // The theme being edited can go away — removed from the editor, or\n // duplicated from a theme that does not exist — and then there is\n // nothing left to edit\n always: {guard: not('isEditingListedTheme'), target: 'list'},\n },\n removed: {\n always: {guard: not('hasRemovedThemes'), target: 'list'},\n },\n },\n },\n },\n})\n\n/** @internal */\nexport type ThemerSnapshot = SnapshotFrom<typeof themerMachine>\n\n/** The part of the machine context that is persisted between sessions @internal */\nexport function selectStoredState(snapshot: ThemerSnapshot): ThemerState {\n const {active, custom, removed, order} = snapshot.context\n\n return {active, custom, removed, order}\n}\n","import {style} from '@vanilla-extract/css'\n\n/** Always reserves the scrollbar's space, so the content never shifts */\nexport const root = style({\n scrollbarGutter: 'stable',\n})\n","import {Box, useTheme_v2} from '@sanity/ui'\nimport {createContext, useContext, useEffect, useMemo, useRef, useState} from 'react'\n\nimport {root} from './ScrollArea.css'\n\n/** How far the content of a scroll area sits from its edges, in pixels */\ninterface ScrollAreaInset {\n left: number\n right: number\n bottom: number\n}\n\nconst ScrollAreaInsetContext = createContext<ScrollAreaInset>({left: 0, right: 0, bottom: 0})\n\n/**\n * The gutter the last scroll area measured. It is the same for every scroll\n * area (the reserved gutter does not depend on the content), so the next one\n * starts out with the right padding instead of shifting after its first\n * measurement — which would also throw off anything scrolling into view as\n * the content mounts.\n */\nlet lastGutter = 0\n\n/**\n * The scrolling body of a sidebar flow. It always reserves the scrollbar's\n * space, so the content does not shift when the list grows or shrinks past\n * the point where it needs to scroll — and it takes that space out of its\n * right padding, so the content stays centered rather than hugging the left.\n * With overlay scrollbars there is no gutter and the padding stays even.\n *\n * @internal\n */\nexport function ScrollArea(props: {children: React.ReactNode; padding: number}) {\n const {children, padding} = props\n const {space} = useTheme_v2()\n const ref = useRef<HTMLDivElement | null>(null)\n const [gutter, setGutter] = useState(() => lastGutter)\n\n useEffect(() => {\n const element = ref.current\n\n if (!element) return undefined\n\n // The reserved gutter is the part of the box the content cannot use\n const measure = () => {\n lastGutter = element.offsetWidth - element.clientWidth\n setGutter(lastGutter)\n }\n\n measure()\n\n const observer = new ResizeObserver(measure)\n\n observer.observe(element)\n\n return () => observer.disconnect()\n }, [])\n\n const inset = useMemo<ScrollAreaInset>(\n () => ({\n left: space[padding],\n right: Math.max(0, space[padding] - gutter),\n bottom: space[padding],\n }),\n [gutter, padding, space],\n )\n\n return (\n <Box\n className={root}\n flex={1}\n overflow=\"auto\"\n padding={padding}\n ref={ref}\n style={{paddingRight: inset.right}}\n >\n <ScrollAreaInsetContext.Provider value={inset}>{children}</ScrollAreaInsetContext.Provider>\n </Box>\n )\n}\n\n/**\n * Content that runs edge to edge in a scroll area — up to the scrollbar —\n * instead of sitting inside its padding. It goes last: it runs down to the\n * bottom edge too.\n *\n * @internal\n */\nexport function ScrollAreaBleed(props: {children: React.ReactNode}) {\n const {left, right, bottom} = useContext(ScrollAreaInsetContext)\n\n return (\n <div style={{marginLeft: -left, marginRight: -right, marginBottom: -bottom}}>\n {props.children}\n </div>\n )\n}\n","import {createVar, style, styleVariants} from '@vanilla-extract/css'\n\n/**\n * The dark scheme covers the right side of the preview behind a diagonal cut,\n * like the \"Auto\" appearance preview in macOS System Settings shows both\n * schemes at once.\n */\nconst DARK_CLIP_PATH = 'polygon(58% 0, 100% 0, 100% 100%, 42% 100%)'\n\n/** The thickness of a line of text — bold enough to survive a half-size preview */\nconst LINE = 4\n\n/**\n * The colors a mockup paints with — only the ones the theme options change —\n * assigned per color scheme from the theme it previews\n */\nexport const colors = {\n bg: createVar(),\n fg: createVar(),\n mutedFg: createVar(),\n border: createVar(),\n accent: createVar(),\n avatar: createVar(),\n selectedBg: createVar(),\n selectedFg: createVar(),\n inputBg: createVar(),\n inputBorder: createVar(),\n}\n\nexport const root = style({\n position: 'relative',\n aspectRatio: '16 / 9',\n overflow: 'hidden',\n})\n\nconst studioBase = style({\n position: 'absolute',\n inset: 0,\n display: 'flex',\n flexDirection: 'column',\n background: colors.bg,\n})\n\nexport const studio = styleVariants({\n light: [studioBase],\n dark: [studioBase, {clipPath: DARK_CLIP_PATH}],\n})\n\nexport const navbar = style({\n flex: 'none',\n display: 'flex',\n alignItems: 'center',\n gap: 5,\n height: '18%',\n padding: '0 5%',\n borderBottom: `1px solid ${colors.border}`,\n})\n\nexport const logo = style({\n flex: 'none',\n width: 8,\n height: 8,\n borderRadius: 2,\n background: colors.accent,\n})\n\nexport const spacer = style({\n flex: 1,\n})\n\nexport const avatar = style({\n flex: 'none',\n width: 8,\n height: 8,\n borderRadius: '50%',\n background: colors.avatar,\n})\n\nexport const body = style({\n flex: 1,\n display: 'flex',\n minHeight: 0,\n})\n\nexport const list = style({\n flex: 'none',\n display: 'flex',\n flexDirection: 'column',\n width: '36%',\n borderRight: `1px solid ${colors.border}`,\n})\n\nexport const listRow = style({\n flex: 'none',\n display: 'flex',\n alignItems: 'center',\n height: '18%',\n padding: '0 9%',\n selectors: {\n \"&[data-selected='true']\": {\n background: colors.selectedBg,\n },\n },\n})\n\nexport const pane = style({\n flex: 1,\n display: 'flex',\n flexDirection: 'column',\n minWidth: 0,\n})\n\nexport const form = style({\n flex: 1,\n display: 'flex',\n flexDirection: 'column',\n gap: 7,\n padding: '9% 8% 0',\n minHeight: 0,\n overflow: 'hidden',\n})\n\nexport const field = style({\n flex: 'none',\n display: 'flex',\n flexDirection: 'column',\n gap: 3,\n})\n\nexport const input = style({\n height: 12,\n borderRadius: 3,\n background: colors.inputBg,\n boxShadow: `inset 0 0 0 1px ${colors.inputBorder}`,\n})\n\nexport const footer = style({\n flex: 'none',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'flex-end',\n height: '20%',\n padding: '0 8%',\n borderTop: `1px solid ${colors.border}`,\n})\n\nexport const button = style({\n width: '32%',\n height: '50%',\n borderRadius: 3,\n background: colors.accent,\n})\n\nconst lineBase = style({\n flex: 'none',\n height: LINE,\n borderRadius: LINE / 2,\n})\n\n/** A line of text, in one of the text colors — its width is set inline */\nexport const line = styleVariants({\n fg: [lineBase, {background: colors.fg}],\n muted: [lineBase, {background: colors.mutedFg}],\n selected: [lineBase, {background: colors.selectedFg}],\n})\n","import {ThemeProvider, useTheme_v2} from '@sanity/ui'\nimport {type ThemeColorSchemeKey} from '@sanity/ui/theme'\nimport {assignInlineVars} from '@vanilla-extract/dynamic'\nimport {useMemo} from 'react'\n\nimport {buildTheme} from '../theme/buildTheme'\nimport {BuildThemeOptions} from '../theme/options'\n\nimport {\n avatar,\n body,\n button,\n colors,\n field,\n footer,\n form,\n input,\n line,\n list,\n listRow,\n logo,\n navbar,\n pane,\n root,\n spacer,\n studio,\n} from './ThemePreview.css'\n\n/** The document rows in the list pane — the widths of their title lines */\nconst LIST_ROWS = ['62%', '46%', '70%', '52%']\n\n/** The fields in the document form — the widths of their labels */\nconst FIELDS = ['32%', '24%']\n\n/**\n * A tiny, lo-fi mockup of a Studio in the given theme — the navbar over the\n * structure tool with a list of documents on the left and a document form on\n * the right — in both color schemes at once: the light scheme on the left and\n * the dark scheme on the right, split diagonally. It only paints colors the\n * theme options change (backgrounds, text, borders, the accent) so that a\n * glance shows what the theme does to the Studio — with few and bold enough\n * elements to still read when scaled down to a thumbnail.\n *\n * @internal\n */\nexport function ThemePreview(props: {options: BuildThemeOptions}) {\n const {options} = props\n const theme = useMemo(() => buildTheme(options), [options])\n\n return (\n <div aria-hidden className={root}>\n <ThemeProvider scheme=\"light\" theme={theme} tone=\"default\">\n <MockStudio scheme=\"light\" />\n </ThemeProvider>\n <ThemeProvider scheme=\"dark\" theme={theme} tone=\"default\">\n <MockStudio scheme=\"dark\" />\n </ThemeProvider>\n </div>\n )\n}\n\nfunction MockStudio(props: {scheme: ThemeColorSchemeKey}) {\n const {scheme} = props\n const {color} = useTheme_v2()\n const selected = color.selectable.default.selected\n const inputColor = color.input.default.enabled\n\n return (\n <div\n className={studio[scheme]}\n style={assignInlineVars({\n [colors.bg]: color.bg,\n [colors.fg]: color.fg,\n [colors.mutedFg]: color.muted.fg,\n [colors.border]: color.border,\n [colors.accent]: color.button.default.primary.enabled.bg,\n [colors.avatar]: color.avatar.blue.bg,\n [colors.selectedBg]: selected.bg,\n [colors.selectedFg]: selected.fg,\n [colors.inputBg]: inputColor.bg,\n [colors.inputBorder]: inputColor.border,\n })}\n >\n <div className={navbar}>\n <div className={logo} />\n <div className={line.fg} style={{width: '22%'}} />\n <div className={spacer} />\n <div className={avatar} />\n </div>\n\n <div className={body}>\n <div className={list}>\n {LIST_ROWS.map((width, index) => {\n const isSelected = index === 1\n\n return (\n <div className={listRow} data-selected={isSelected} key={width}>\n <div className={isSelected ? line.selected : line.muted} style={{width}} />\n </div>\n )\n })}\n </div>\n\n <div className={pane}>\n <div className={form}>\n <div className={line.fg} style={{width: '45%'}} />\n {FIELDS.map((width) => (\n <div className={field} key={width}>\n <div className={line.muted} style={{width}} />\n <div className={input} />\n </div>\n ))}\n </div>\n <div className={footer}>\n <div className={button} />\n </div>\n </div>\n </div>\n </div>\n )\n}\n","import {Card} from '@sanity/ui'\n\nimport {BuildThemeOptions} from '../theme/options'\nimport {ThemePreview} from './ThemePreview'\n\n/**\n * The floating preview card of a theme: the lo-fi studio mockup on a raised,\n * rounded card, like the appearance previews in macOS System Settings.\n *\n * @internal\n */\nexport function ThemeThumbnail(props: {options: BuildThemeOptions}) {\n return (\n <Card overflow=\"hidden\" radius={3} shadow={1}>\n <ThemePreview options={props.options} />\n </Card>\n )\n}\n","import {style} from '@vanilla-extract/css'\n\n/** A removed theme's thumbnail, dimmed to read as removed */\nexport const removedThumbnail = style({\n opacity: 0.6,\n})\n","import {Box, Button, Flex, Stack, Text} from '@sanity/ui'\n\nimport {useThemer} from './context'\nimport {ScrollArea} from './ScrollArea'\nimport {displayTitle} from './themes'\nimport {ThemeThumbnail} from './ThemeThumbnail'\n\nimport {removedThumbnail} from './RemovedThemes.css'\n\n/**\n * The flow for restoring removed themes: the presets and custom themes the\n * user took out of the list, each with a button to put it back — and, for\n * custom themes, one to delete it for good. The machine leaves the flow once\n * the last one is gone.\n *\n * @internal\n */\nexport function RemovedThemes() {\n const {removed, send} = useThemer()\n\n return (\n <ScrollArea padding={3}>\n <Stack gap={5}>\n {removed.map((theme) => (\n <Stack gap={3} key={theme.slug}>\n <Box className={removedThumbnail}>\n <ThemeThumbnail options={theme.options} />\n </Box>\n <Text align=\"center\" size={1} textOverflow=\"ellipsis\">\n {displayTitle(theme.title)}\n </Text>\n <Flex gap={2} justify=\"center\">\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.restore', slug: theme.slug})}\n padding={2}\n text=\"Restore\"\n />\n {theme.source === 'custom' && (\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.delete', slug: theme.slug})}\n padding={2}\n text=\"Delete\"\n tone=\"critical\"\n />\n )}\n </Flex>\n </Stack>\n ))}\n </Stack>\n </ScrollArea>\n )\n}\n","import {Button, ButtonProps, Text} from '@sanity/ui'\nimport {Tooltip} from '@sanity/ui/tooltip'\n\n/**\n * A button with an animated tooltip instead of a `title` attribute, sized like\n * the Studio's own buttons (`padding` and `gap` of 2). Icon-only buttons take\n * the tooltip as their accessible name.\n *\n * @internal\n */\nexport function TooltipButton(props: ButtonProps & {tooltip: string}) {\n const {tooltip, ...buttonProps} = props\n\n return (\n <Tooltip animate content={<Text size={1}>{tooltip}</Text>} placement=\"bottom\" portal>\n <Button\n aria-label={buttonProps.text === undefined ? tooltip : undefined}\n gap={2}\n padding={2}\n {...buttonProps}\n />\n </Tooltip>\n )\n}\n","import {ImageIcon} from '@sanity/icons/Image'\nimport {ButtonProps} from '@sanity/ui'\nimport {useRef} from 'react'\n\nimport {TooltipButton} from './TooltipButton'\n\n/**\n * A button that opens the file picker for an image, and hands the chosen file\n * over — the picker is a hidden file input, so nothing is uploaded anywhere.\n *\n * @internal\n */\nexport function ImageFileButton(\n props: {onFile: (file: File) => void; tooltip: string} & Pick<\n ButtonProps,\n 'disabled' | 'loading' | 'mode' | 'padding' | 'text' | 'width'\n >,\n) {\n const {onFile, ...buttonProps} = props\n const inputRef = useRef<HTMLInputElement | null>(null)\n\n return (\n <>\n <TooltipButton {...buttonProps} icon={ImageIcon} onClick={() => inputRef.current?.click()} />\n <input\n accept=\"image/*\"\n hidden\n onChange={(event) => {\n const file = event.currentTarget.files?.[0]\n\n // Picking the same file again should work, so the input forgets it\n event.currentTarget.value = ''\n\n if (file) onFile(file)\n }}\n ref={inputRef}\n type=\"file\"\n />\n </>\n )\n}\n","import {PixelData} from './imagePalette'\n\n/**\n * The longest side the image is scaled down to before its colors are read —\n * a few thousand pixels are plenty for a palette, and keep it instant\n */\nconst SAMPLE_SIZE = 96\n\n/**\n * Reads the pixels of an image file on device: the image is decoded, drawn\n * onto a small canvas and read back, so nothing leaves the browser.\n *\n * @internal\n */\nexport async function readImagePixels(file: Blob): Promise<PixelData> {\n const image = await decodeImage(file)\n\n try {\n const scale = Math.min(1, SAMPLE_SIZE / Math.max(image.width, image.height))\n const width = Math.max(1, Math.round(image.width * scale))\n const height = Math.max(1, Math.round(image.height * scale))\n const canvas = document.createElement('canvas')\n\n canvas.width = width\n canvas.height = height\n\n const context = canvas.getContext('2d', {willReadFrequently: true})\n\n if (!context) throw new Error('Could not draw the image')\n\n context.drawImage(image, 0, 0, width, height)\n\n return {data: context.getImageData(0, 0, width, height).data, width, height}\n } finally {\n // Where `createImageBitmap` is missing, `ImageBitmap` may be too\n if (typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap) image.close()\n }\n}\n\nasync function decodeImage(file: Blob): Promise<ImageBitmap | HTMLImageElement> {\n if (typeof createImageBitmap === 'function') {\n try {\n return await createImageBitmap(file)\n } catch {\n // Some browsers cannot decode every format this way (e.g. SVG) — an\n // image element handles whatever the browser can display\n }\n }\n\n const url = URL.createObjectURL(file)\n\n try {\n return await new Promise<HTMLImageElement>((resolve, reject) => {\n const image = new Image()\n\n image.addEventListener('load', () => resolve(image), {once: true})\n image.addEventListener('error', () => reject(new Error('Could not decode the image')), {\n once: true,\n })\n image.src = url\n })\n } finally {\n URL.revokeObjectURL(url)\n }\n}\n","import {useToast} from '@sanity/ui/toast'\nimport {useCallback, useEffect, useRef, useState} from 'react'\n\nimport {readImagePixels} from './imageFile'\nimport {extractImagePalette, ImagePalette} from './imagePalette'\n\n/**\n * Extracts the palette of image files on device and hands it to `onPalette`,\n * along with the file it came from. Failures end up as a toast.\n *\n * @internal\n */\nexport function useImagePalette(onPalette: (palette: ImagePalette, file: File) => void): {\n busy: boolean\n pickImage: (file: File) => void\n} {\n const toast = useToast()\n const [busy, setBusy] = useState(false)\n const onPaletteRef = useRef(onPalette)\n const mountedRef = useRef(true)\n // Picks are numbered so that a slow earlier image cannot overwrite a later\n // pick once its extraction finishes\n const latestPickRef = useRef(0)\n\n useEffect(() => {\n onPaletteRef.current = onPalette\n }, [onPalette])\n\n useEffect(() => {\n mountedRef.current = true\n\n return () => {\n mountedRef.current = false\n }\n }, [])\n\n const pickImage = useCallback(\n (file: File) => {\n const pick = ++latestPickRef.current\n\n setBusy(true)\n\n // Whether this pick still matters: it is the latest one and the\n // component that asked for it is still around\n const isCurrent = () => mountedRef.current && latestPickRef.current === pick\n\n const extract = async () => {\n const palette = extractImagePalette(await readImagePixels(file))\n\n if (!isCurrent()) return\n\n if (Object.values(palette).every((swatch) => swatch === null)) {\n toast.push({status: 'warning', title: 'Found no colors in that image'})\n } else {\n onPaletteRef.current(palette, file)\n }\n }\n\n extract()\n .catch(() => {\n if (isCurrent()) toast.push({status: 'error', title: 'Could not read that image'})\n })\n .finally(() => {\n if (isCurrent()) setBusy(false)\n })\n },\n [toast],\n )\n\n return {busy, pickImage}\n}\n","import {createVar, style} from '@vanilla-extract/css'\n\nconst TILE_SIZE = 24\nconst TILE_GAP = 4\n\n/** The image spans both rows of tiles */\nconst IMAGE_SIZE = TILE_SIZE * 2 + TILE_GAP\n\n/** The variant previews are the theme thumbnails, laid out at this width and scaled down */\nexport const VARIANT_WIDTH = 144\n\nexport const VARIANT_GAP = 8\nexport const ROW_PADDING = 12\n\n/** The frame around a variant's thumbnail, on either side */\nexport const VARIANT_FRAME = 4\n\n/** The width of one variant's thumbnail, set on the row once it is measured */\nexport const variantWidth = createVar()\n\n/** How much a variant's thumbnail is scaled down from `VARIANT_WIDTH` */\nexport const variantScale = createVar()\n\nexport const paletteGrid = style({\n display: 'grid',\n gridTemplateColumns: `repeat(3, ${TILE_SIZE}px)`,\n gridAutoRows: TILE_SIZE,\n gap: TILE_GAP,\n selectors: {\n \"&[data-with-image='true']\": {\n gridTemplateColumns: `${IMAGE_SIZE}px repeat(3, ${TILE_SIZE}px)`,\n },\n },\n})\n\nexport const imageTile = style({\n display: 'block',\n gridRow: 'span 2',\n width: IMAGE_SIZE,\n height: IMAGE_SIZE,\n borderRadius: 3,\n objectFit: 'cover',\n boxShadow: 'inset 0 0 0 1px var(--card-border-color)',\n})\n\nexport const tile = style({\n display: 'block',\n borderRadius: 3,\n boxShadow: 'inset 0 0 0 1px var(--card-border-color)',\n selectors: {\n \"&[data-empty='true']\": {\n background:\n 'repeating-linear-gradient(-45deg, transparent 0 3px, var(--card-border-color) 3px 4px)',\n },\n },\n})\n\n/**\n * The variants scroll sideways and snap into place, bleeding into the card's\n * padding so the row runs from edge to edge\n */\nexport const variantRow = style({\n display: 'flex',\n gap: VARIANT_GAP,\n margin: `0 -${ROW_PADDING}px`,\n padding: `0 ${ROW_PADDING}px`,\n overflowX: 'auto',\n scrollSnapType: 'x mandatory',\n scrollPaddingLeft: ROW_PADDING,\n scrollbarWidth: 'thin',\n})\n\nexport const variantButton = style({\n 'appearance': 'none',\n 'flex': 'none',\n 'display': 'block',\n 'boxSizing': 'border-box',\n 'width': `calc(${variantWidth} + ${VARIANT_FRAME * 2}px)`,\n 'margin': 0,\n 'padding': 0,\n 'border': 0,\n 'background': 'none',\n 'color': 'inherit',\n 'font': 'inherit',\n 'textAlign': 'center',\n 'cursor': 'pointer',\n 'scrollSnapAlign': 'start',\n ':focus': {\n outline: 'none',\n },\n})\n\n/**\n * Clips the scaled-down thumbnail to its visual size, with room for the ring\n * — drawn inside the frame, like the theme cards', so the scrolling row\n * never clips it\n */\nexport const variantFrame = style({\n display: 'block',\n boxSizing: 'border-box',\n width: `calc(${variantWidth} + ${VARIANT_FRAME * 2}px)`,\n height: `calc(${variantWidth} * 9 / 16 + ${VARIANT_FRAME * 2}px)`,\n padding: VARIANT_FRAME,\n borderRadius: 7,\n overflow: 'hidden',\n transition: 'box-shadow 100ms',\n selectors: {\n [`${variantButton}:hover &`]: {\n boxShadow: 'inset 0 0 0 2px var(--card-border-color)',\n },\n [`${variantButton}[aria-pressed='true'] &`]: {\n boxShadow: 'inset 0 0 0 2px var(--card-focus-ring-color)',\n },\n [`${variantButton}:focus-visible &`]: {\n outline: '2px solid var(--card-focus-ring-color)',\n outlineOffset: -2,\n },\n },\n})\n\nexport const variantScaleBox = style({\n display: 'block',\n width: VARIANT_WIDTH,\n transform: `scale(${variantScale})`,\n transformOrigin: 'top left',\n})\n","import {SparklesIcon} from '@sanity/icons/Sparkles'\nimport {Box, Card, Flex, Stack, Text} from '@sanity/ui'\nimport {Tooltip} from '@sanity/ui/tooltip'\nimport {assignInlineVars} from '@vanilla-extract/dynamic'\nimport {useEffect, useMemo, useRef, useState} from 'react'\n\nimport {BuildThemeOptions} from '../theme/options'\nimport {ImageFileButton} from './ImageFileButton'\nimport {\n applyImagePalette,\n currentImageVariant,\n IMAGE_PALETTE_TITLES,\n IMAGE_PALETTE_VARIANTS,\n ImagePalette,\n ImagePaletteKey,\n ImagePaletteVariant,\n} from './imagePalette'\nimport {ThemeThumbnail} from './ThemeThumbnail'\nimport {TooltipButton} from './TooltipButton'\nimport {useImagePalette} from './useImagePalette'\n\nimport {\n imageTile,\n paletteGrid,\n ROW_PADDING,\n tile,\n VARIANT_FRAME,\n VARIANT_GAP,\n VARIANT_WIDTH,\n variantButton,\n variantFrame,\n variantRow,\n variantScale,\n variantScaleBox,\n variantWidth,\n} from './ImagePaletteSection.css'\n\n/** The swatches on display: the vibrant family over the muted one */\nconst TILES: ImagePaletteKey[] = [\n 'vibrant',\n 'lightVibrant',\n 'darkVibrant',\n 'muted',\n 'lightMuted',\n 'darkMuted',\n]\n\n/**\n * How many variants show at once: two and a half, so that the cut-off third\n * one gives away that the row scrolls\n */\nconst VISIBLE_VARIANTS = 2.5\n\n/** The width of one variant, before the row has been measured */\nconst FALLBACK_VARIANT_WIDTH = 58\n\n/**\n * Takes the colors of a theme from an image, on device: the image is read\n * into a canvas and its palette extracted right there — nothing is uploaded.\n * Shows the image (while the session lasts) next to its palette, offers a\n * theme variant built around each swatch as a small preview, and a lucky\n * button that picks an interesting one.\n *\n * @internal\n */\nexport function ImagePaletteSection(props: {\n imageUrl?: string\n onPalette: (palette: ImagePalette, file: File) => void\n onVariant: (variant: ImagePaletteVariant) => void\n onLucky: () => void\n options: BuildThemeOptions\n palette?: ImagePalette\n}) {\n const {imageUrl, onPalette, onVariant, onLucky, options, palette} = props\n const {busy, pickImage} = useImagePalette(onPalette)\n const [dragging, setDragging] = useState(false)\n // `dragleave` also fires when the pointer crosses into a child, so the\n // highlight tracks how deep the drag is rather than the last event\n const dragDepth = useRef(0)\n const current = palette ? currentImageVariant(options, palette) : null\n const rowRef = useRef<HTMLDivElement | null>(null)\n const [thumbnailWidth, setThumbnailWidth] = useState(FALLBACK_VARIANT_WIDTH)\n\n // Size the variants to the row, so two and a half of them are in view\n useEffect(() => {\n const row = rowRef.current\n\n if (!row) return undefined\n\n const measure = () => {\n const visible =\n row.clientWidth - ROW_PADDING - (Math.ceil(VISIBLE_VARIANTS) - 1) * VARIANT_GAP\n\n // Each variant is its thumbnail plus the frame on either side\n setThumbnailWidth(Math.max(24, Math.floor(visible / VISIBLE_VARIANTS) - VARIANT_FRAME * 2))\n }\n\n measure()\n\n const observer = new ResizeObserver(measure)\n\n observer.observe(row)\n\n return () => observer.disconnect()\n }, [palette])\n\n const variants = useMemo(() => {\n if (!palette) return []\n\n return IMAGE_PALETTE_VARIANTS.flatMap((variant) =>\n palette[variant] === null\n ? []\n : [\n {\n variant,\n title: IMAGE_PALETTE_TITLES[variant],\n options: applyImagePalette(options, palette, variant),\n },\n ],\n )\n }, [options, palette])\n\n return (\n <Card\n border\n onDragEnter={(event) => {\n event.preventDefault()\n dragDepth.current++\n setDragging(true)\n }}\n onDragLeave={() => {\n dragDepth.current = Math.max(0, dragDepth.current - 1)\n\n if (dragDepth.current === 0) setDragging(false)\n }}\n onDragOver={(event) => {\n event.preventDefault()\n }}\n onDrop={(event) => {\n event.preventDefault()\n dragDepth.current = 0\n setDragging(false)\n\n const file = event.dataTransfer.files[0]\n\n if (file) pickImage(file)\n }}\n padding={3}\n radius={3}\n tone={dragging ? 'primary' : 'default'}\n >\n <Stack gap={3}>\n <Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n Image palette\n </Text>\n </Box>\n {palette && (\n <ImageFileButton\n loading={busy}\n mode=\"bleed\"\n onFile={pickImage}\n padding={2}\n tooltip=\"Pick another image\"\n />\n )}\n </Flex>\n\n {palette ? (\n <>\n <div className={paletteGrid} data-with-image={imageUrl !== undefined}>\n {imageUrl && <img alt=\"\" className={imageTile} src={imageUrl} />}\n {TILES.map((key) => {\n const hex = palette[key]\n const title = IMAGE_PALETTE_TITLES[key]\n\n return (\n <Tooltip\n animate\n content={\n <Text size={1}>\n {hex ? `${title} · ${hex}` : `No ${title.toLowerCase()} color`}\n </Text>\n }\n key={key}\n placement=\"bottom\"\n portal\n >\n <span\n className={tile}\n data-empty={hex === null}\n style={{background: hex ?? undefined}}\n />\n </Tooltip>\n )\n })}\n </div>\n\n <Stack gap={2}>\n <Text muted size={0}>\n Pick the swatch to build the theme around\n </Text>\n <div\n className={variantRow}\n ref={rowRef}\n style={assignInlineVars({\n [variantWidth]: `${thumbnailWidth}px`,\n [variantScale]: String(thumbnailWidth / VARIANT_WIDTH),\n })}\n >\n {variants.map(({variant, title, options: variantOptions}) => (\n // oxlint-disable-next-line control-has-associated-label -- the title below the thumbnail is the label, deeper than the rule looks\n <button\n aria-pressed={variant === current}\n className={variantButton}\n key={variant}\n onClick={() => onVariant(variant)}\n type=\"button\"\n >\n <span className={variantFrame}>\n <span className={variantScaleBox}>\n <ThemeThumbnail options={variantOptions} />\n </span>\n </span>\n <Box as=\"span\" display=\"block\" paddingTop={1}>\n <Text\n align=\"center\"\n as=\"span\"\n size={0}\n textOverflow=\"ellipsis\"\n weight={variant === current ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>\n </button>\n ))}\n </div>\n </Stack>\n\n <TooltipButton\n icon={SparklesIcon}\n mode=\"ghost\"\n onClick={onLucky}\n text=\"I'm feeling lucky\"\n tooltip=\"Build the theme around a swatch that looks interesting\"\n width=\"fill\"\n />\n </>\n ) : (\n <Stack gap={3}>\n <Text muted size={0}>\n Take the accent, text and background colors from an image — read on device, nothing is\n uploaded.\n </Text>\n <ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n text=\"Pick an image\"\n tooltip=\"Take the colors from an image on your device\"\n width=\"fill\"\n />\n </Stack>\n )}\n </Stack>\n </Card>\n )\n}\n","import {style} from '@vanilla-extract/css'\n\n/** A native color input, trimmed down to its swatch */\nexport const swatch = style({\n boxSizing: 'border-box',\n flex: 'none',\n width: 33,\n height: 33,\n padding: 0,\n border: '1px solid var(--card-border-color)',\n borderRadius: 4,\n background: 'none',\n cursor: 'pointer',\n selectors: {\n '&::-webkit-color-swatch-wrapper': {\n padding: 0,\n },\n '&::-webkit-color-swatch': {\n border: 'none',\n borderRadius: 3,\n },\n '&::-moz-color-swatch': {\n border: 'none',\n borderRadius: 3,\n },\n },\n})\n\n/** A native range input, themed through `accent-color` */\nexport const range = style({\n display: 'block',\n width: '100%',\n margin: 0,\n accentColor: 'var(--card-focus-ring-color)',\n})\n\n/** Lets the color row's text shrink and truncate instead of widening the row */\nexport const colorRowText = style({\n minWidth: 0,\n})\n","import {ResetIcon} from '@sanity/icons/Reset'\nimport {TrashIcon} from '@sanity/icons/Trash'\nimport {Badge, Box, Button, Card, Flex, Stack, Text, TextInput} from '@sanity/ui'\nimport {type ThemeColorSchemeKey} from '@sanity/ui/theme'\nimport {useMemo} from 'react'\nimport {useColorSchemeValue} from 'sanity'\n\nimport {buildPalette, GeneratedColorPalette} from '../theme/buildPalette'\nimport {\n BuildThemeOptions,\n ResolvedSchemeOptions,\n resolveThemeOptions,\n SCHEMES,\n SchemeThemeOptions,\n} from '../theme/options'\nimport {useThemer} from './context'\nimport {\n applyImagePalette,\n currentImageVariant,\n ImagePalette,\n ImagePaletteVariant,\n pickLuckyVariant,\n} from './imagePalette'\nimport {ImagePaletteSection} from './ImagePaletteSection'\nimport {ScrollArea, ScrollAreaBleed} from './ScrollArea'\nimport {ThemeThumbnail} from './ThemeThumbnail'\nimport {TooltipButton} from './TooltipButton'\n\nimport {colorRowText, range, swatch} from './ThemeEditor.css'\n\nconst SCHEME_TITLES: Record<ThemeColorSchemeKey, string> = {\n dark: 'Dark mode',\n light: 'Light mode',\n}\n\n/**\n * Where the split preview shows a scheme: the Studio's own scheme stays next\n * to the sidebar (or at the bottom, stacked on small screens), the other one\n * takes the far side\n */\nfunction splitHint(own: boolean, mobile: boolean): string {\n if (mobile) return own ? 'Shown at the bottom' : 'Shown at the top'\n\n return own ? 'Shown on the right, next to the themer' : 'Shown on the left'\n}\n\nconst SCHEME_OPTION_KEYS: Array<keyof SchemeThemeOptions> = [\n 'accent',\n 'text',\n 'background',\n 'contrast',\n]\n\n/**\n * `<input type=\"color\">` paints the color into a shadow-DOM swatch that brings\n * its own border and padding, which then sits inside ours as a second border.\n * Stripping that chrome leaves the themed border as the only one.\n */\n/**\n * The flow for editing one of the user's own themes: its title, a live\n * preview, and a card per color scheme with the accent/text/background\n * pickers and the contrast slider of that scheme. Every change applies to the\n * whole Studio right away — in the scheme the Studio is showing, or in both\n * side by side in the split preview.\n *\n * @internal\n */\nexport function ThemeEditor(props: {focusTitle: boolean; slug: string}) {\n const {focusTitle, slug} = props\n const {themes, images, split, mobile, send} = useThemer()\n const theme = themes.find((candidate) => candidate.slug === slug && candidate.source === 'custom')\n\n // The machine only enters the edit flow for a listed custom theme, and\n // leaves it as soon as the theme is removed or deleted\n if (!theme) return null\n\n const applyVariant = (variant: ImagePaletteVariant | null) => {\n if (!theme.palette || variant === null) return\n\n send({\n type: 'theme.update',\n slug,\n options: applyImagePalette(theme.options, theme.palette, variant),\n })\n }\n\n return (\n <ThemeEditorForm\n focusTitle={focusTitle}\n imageUrl={images[slug]}\n onDone={() => send({type: 'flow.list'})}\n onLucky={() => {\n if (!theme.palette) return\n\n applyVariant(\n pickLuckyVariant(theme.palette, {\n exclude: currentImageVariant(theme.options, theme.palette),\n }),\n )\n }}\n onOptionsChange={(options) => send({type: 'theme.update', slug, options})}\n onPalette={(palette, file) =>\n send({\n type: 'theme.update',\n slug,\n options: applyImagePalette(theme.options, palette),\n palette,\n imageUrl: URL.createObjectURL(file),\n })\n }\n onRemove={() => send({type: 'theme.remove', slug})}\n onTitleChange={(title) => send({type: 'theme.update', slug, title})}\n onVariant={applyVariant}\n options={theme.options}\n palette={theme.palette}\n split={split}\n mobile={mobile}\n title={theme.title}\n />\n )\n}\n\n// Split from `ThemeEditor` so that the derived values below can be hooks\n// while the editor bails out early when the theme is gone\nfunction ThemeEditorForm(props: {\n focusTitle: boolean\n imageUrl?: string\n onDone: () => void\n onLucky: () => void\n onOptionsChange: (options: BuildThemeOptions) => void\n onPalette: (palette: ImagePalette, file: File) => void\n onRemove: () => void\n onTitleChange: (title: string) => void\n onVariant: (variant: ImagePaletteVariant) => void\n options: BuildThemeOptions\n palette?: ImagePalette\n /** Whether the Studio shows in light and dark side by side */\n split: boolean\n /** Whether the split preview stacks, on a small screen */\n mobile: boolean\n title: string\n}) {\n const {\n focusTitle,\n imageUrl,\n onDone,\n onLucky,\n onOptionsChange,\n onPalette,\n onRemove,\n onTitleChange,\n onVariant,\n options,\n palette,\n split,\n mobile,\n title,\n } = props\n // The scheme the Studio is showing, with the appearance setting resolved\n const studioScheme = useColorSchemeValue()\n const resolved = useMemo(() => resolveThemeOptions(options), [options])\n const palettes = useMemo(() => buildPalette(options), [options])\n\n const patchScheme = (scheme: ThemeColorSchemeKey, changes: Partial<SchemeThemeOptions>) => {\n const values: SchemeThemeOptions = {...options[scheme], ...changes}\n\n // Clearing an option means falling back to its default, which is its absence\n for (const key of SCHEME_OPTION_KEYS) {\n if (values[key] === undefined) delete values[key]\n }\n\n const next: BuildThemeOptions = {...options}\n\n if (Object.keys(values).length === 0) {\n delete next[scheme]\n } else {\n next[scheme] = values\n }\n\n onOptionsChange(next)\n }\n\n return (\n <>\n <ScrollArea padding={3}>\n <Stack gap={4}>\n <ThemeThumbnail options={options} />\n\n <Stack gap={2}>\n <Text size={1} weight=\"medium\">\n Title\n </Text>\n <TextInput\n autoFocus={focusTitle}\n fontSize={1}\n onChange={(event) => onTitleChange(event.currentTarget.value)}\n onFocus={focusTitle ? (event) => event.currentTarget.select() : undefined}\n padding={2}\n placeholder=\"Untitled theme\"\n value={title}\n />\n </Stack>\n\n <ImagePaletteSection\n imageUrl={imageUrl}\n onLucky={onLucky}\n onPalette={onPalette}\n onVariant={onVariant}\n options={options}\n palette={palette}\n />\n\n <ScrollAreaBleed>\n {SCHEMES.map((scheme) => (\n <SchemeCard\n active={split || scheme === studioScheme}\n key={scheme}\n onChange={(changes) => patchScheme(scheme, changes)}\n options={options[scheme] ?? {}}\n palette={palettes[scheme]}\n resolved={resolved[scheme]}\n scheme={scheme}\n split={split}\n splitHint={splitHint(scheme === studioScheme, mobile)}\n />\n ))}\n </ScrollAreaBleed>\n </Stack>\n </ScrollArea>\n\n <Card borderTop padding={3}>\n <Flex gap={2}>\n <TooltipButton\n icon={TrashIcon}\n mode=\"ghost\"\n onClick={onRemove}\n text=\"Remove\"\n tone=\"critical\"\n tooltip=\"Remove the theme — it can be restored until it is deleted\"\n />\n <Box flex={1} />\n <Button mode=\"ghost\" onClick={onDone} padding={2} text=\"Done\" />\n </Flex>\n </Card>\n </>\n )\n}\n\n/**\n * The colors of one scheme. The cards span the sidebar edge to edge, one\n * after the other with a border between them, in the sidebar's own scheme —\n * the swatches show the colors they edit. The scheme the Studio is showing is\n * marked as active: its changes show up in the Studio right away, the other\n * scheme's only once the appearance setting switches to it — or both are\n * active, in the split preview.\n */\nfunction SchemeCard(props: {\n active: boolean\n onChange: (changes: Partial<SchemeThemeOptions>) => void\n options: SchemeThemeOptions\n palette: GeneratedColorPalette\n resolved: ResolvedSchemeOptions\n scheme: ThemeColorSchemeKey\n split: boolean\n /** Where the split preview shows this scheme */\n splitHint: string\n}) {\n const {active, onChange, options, palette, resolved, scheme, split, splitHint} = props\n const name = SCHEME_TITLES[scheme]\n\n return (\n <Card borderTop padding={3}>\n <Stack gap={4}>\n <Stack gap={2}>\n <Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n {name}\n </Text>\n </Box>\n {active && (\n <Badge fontSize={0} tone=\"primary\">\n Active\n </Badge>\n )}\n </Flex>\n <Text muted size={0}>\n {split\n ? splitHint\n : active\n ? 'Shown in the Studio right now'\n : `Shown once the Studio appearance is ${scheme}`}\n </Text>\n </Stack>\n\n <ColorRow\n auto={options.accent === undefined}\n autoLabel=\"default\"\n label={`${name} accent`}\n onChange={(accent) => onChange({accent})}\n onClear={() => onChange({accent: undefined})}\n title=\"Accent\"\n value={resolved.accent}\n />\n <ColorRow\n auto={options.text === undefined}\n label={`${name} text`}\n onChange={(text) => onChange({text})}\n onClear={() => onChange({text: undefined})}\n title=\"Text\"\n value={resolved.text}\n />\n <ColorRow\n adjusted={scheme === 'dark' ? palette.black : palette.white}\n auto={options.background === undefined}\n label={`${name} background`}\n onChange={(background) => onChange({background})}\n onClear={() => onChange({background: undefined})}\n title=\"Background\"\n value={resolved.background}\n />\n\n <Stack gap={3}>\n <Flex align=\"center\" gap={2}>\n <Stack flex={1} gap={2}>\n <Text size={1}>Contrast</Text>\n <Text muted size={0}>\n {resolved.contrast}\n {options.contrast === undefined ? ' · auto' : ''}\n </Text>\n </Stack>\n {options.contrast !== undefined && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={() => onChange({contrast: undefined})}\n padding={2}\n tooltip=\"Reset to auto\"\n />\n )}\n </Flex>\n <input\n aria-label={`${name} contrast`}\n className={range}\n max={100}\n min={15}\n onChange={(event) => onChange({contrast: Number(event.currentTarget.value)})}\n step={1}\n type=\"range\"\n value={resolved.contrast}\n />\n <Text muted size={0}>\n 100 keeps text and borders neutral — lower values blend in the accent\n </Text>\n </Stack>\n </Stack>\n </Card>\n )\n}\n\n/** One color of a scheme: a swatch with its value and an optional reset button */\nfunction ColorRow(props: {\n /** The color the generator actually applied, when it may differ from the input */\n adjusted?: string\n /** Whether the value is derived or a default rather than explicitly set */\n auto?: boolean\n /** What the value is when it is not set: derived (`auto`) or the stock one (`default`) */\n autoLabel?: 'auto' | 'default'\n /** The accessible name of the picker */\n label: string\n onChange: (value: string) => void\n onClear?: () => void\n title: string\n value: string\n}) {\n const {adjusted, auto, autoLabel = 'auto', label, onChange, onClear, title, value} = props\n\n return (\n <Flex align=\"center\" gap={2}>\n <input\n aria-label={label}\n className={swatch}\n onChange={(event) => onChange(event.currentTarget.value)}\n type=\"color\"\n value={value}\n />\n <Stack className={colorRowText} flex={1} gap={2}>\n <Text size={1}>{title}</Text>\n <Text muted size={0} textOverflow=\"ellipsis\">\n {value}\n {adjusted !== undefined && adjusted !== value ? ` → ${adjusted}` : ''}\n {auto ? ` · ${autoLabel}` : ''}\n </Text>\n </Stack>\n {onClear && !auto && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={onClear}\n padding={2}\n tooltip={`Reset to ${autoLabel}`}\n />\n )}\n </Flex>\n )\n}\n","import {isColor} from '../lib/mix'\nimport {\n BuildThemeOptions,\n MAXIMUM_CONTRAST,\n MINIMUM_CONTRAST,\n SCHEMES,\n SchemeThemeOptions,\n} from '../theme/options'\nimport {displayTitle} from './themes'\n\n/** What a theme code carries: a theme's title and its options @internal */\nexport interface SharedTheme {\n title: string\n options: BuildThemeOptions\n}\n\n/**\n * The version of the code format, the first byte of every code — bumped if\n * the layout below ever changes, so older codes can still be told apart\n */\nconst VERSION = 1\n\n/** The fields a scheme's options can set, in the order their bytes go */\nconst FIELDS = ['accent', 'text', 'background', 'contrast'] as const\n\n/**\n * Turns a theme into a short code to share in a message: `title`, and the\n * colors and contrasts of both schemes, packed into bytes — one flag byte says\n * which of the eight fields are set, each color is its three RGB bytes and a\n * contrast one byte, and the title takes up the rest — then base64url'd, so\n * it is one word without padding, safe in URLs and chat. A theme setting\n * everything comes to about 30 bytes plus its title.\n *\n * @internal\n */\nexport function encodeTheme(theme: SharedTheme): string {\n const bytes: number[] = [VERSION, 0]\n let flags = 0\n\n SCHEMES.forEach((scheme, schemeIndex) => {\n const options: SchemeThemeOptions = theme.options[scheme] ?? {}\n\n FIELDS.forEach((field, fieldIndex) => {\n const value = options[field]\n\n if (value === undefined) return\n\n if (field === 'contrast') {\n if (typeof value !== 'number' || !Number.isFinite(value)) return\n\n bytes.push(Math.round(Math.min(MAXIMUM_CONTRAST, Math.max(MINIMUM_CONTRAST, value))))\n } else {\n if (typeof value !== 'string' || !isColor(value)) return\n\n bytes.push(...rgbBytes(value))\n }\n\n flags |= 1 << (schemeIndex * FIELDS.length + fieldIndex)\n })\n })\n\n bytes[1] = flags\n bytes.push(...new TextEncoder().encode(theme.title.trim()))\n\n return toBase64Url(Uint8Array.from(bytes))\n}\n\n/**\n * Reads a theme back out of a code — as pasted, so surrounding whitespace and\n * words around the code are fine: the first word that is a code counts. `null`\n * when nothing in the text is one.\n *\n * @internal\n */\nexport function decodeTheme(text: string): SharedTheme | null {\n for (const word of text.trim().split(/\\s+/)) {\n const theme = decodeCode(word)\n\n if (theme) return theme\n }\n\n return null\n}\n\nfunction decodeCode(code: string): SharedTheme | null {\n const bytes = fromBase64Url(code)\n\n if (!bytes || bytes.length < 2 || bytes[0] !== VERSION) return null\n\n const flags = bytes[1]\n const options: BuildThemeOptions = {}\n let offset = 2\n\n for (const [schemeIndex, scheme] of SCHEMES.entries()) {\n const schemeOptions: SchemeThemeOptions = {}\n\n for (const [fieldIndex, field] of FIELDS.entries()) {\n if (!(flags & (1 << (schemeIndex * FIELDS.length + fieldIndex)))) continue\n\n if (field === 'contrast') {\n if (offset + 1 > bytes.length) return null\n\n schemeOptions.contrast = Math.min(\n MAXIMUM_CONTRAST,\n Math.max(MINIMUM_CONTRAST, bytes[offset]),\n )\n offset += 1\n } else {\n if (offset + 3 > bytes.length) return null\n\n schemeOptions[field] = hexColor(bytes.subarray(offset, offset + 3))\n offset += 3\n }\n }\n\n if (Object.keys(schemeOptions).length > 0) options[scheme] = schemeOptions\n }\n\n let title: string\n\n try {\n title = new TextDecoder('utf-8', {fatal: true}).decode(bytes.subarray(offset))\n } catch {\n return null\n }\n\n return {title: displayTitle(title), options}\n}\n\n/** The three RGB bytes of a `#rgb` or `#rrggbb` color */\nfunction rgbBytes(color: string): number[] {\n const hex = color.length === 4 ? color.slice(1).replaceAll(/./g, '$&$&') : color.slice(1)\n\n return [0, 2, 4].map((start) => parseInt(hex.slice(start, start + 2), 16))\n}\n\nfunction hexColor(rgb: Uint8Array): string {\n return `#${[...rgb].map((byte) => byte.toString(16).padStart(2, '0')).join('')}`\n}\n\nfunction toBase64Url(bytes: Uint8Array): string {\n let binary = ''\n\n for (const byte of bytes) binary += String.fromCharCode(byte)\n\n return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '')\n}\n\nfunction fromBase64Url(code: string): Uint8Array | null {\n if (!/^[A-Za-z0-9_-]+$/.test(code)) return null\n\n const base64 = code.replaceAll('-', '+').replaceAll('_', '/')\n\n try {\n return Uint8Array.from(atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, '=')), (char) =>\n char.charCodeAt(0),\n )\n } catch {\n return null\n }\n}\n","import {useToast} from '@sanity/ui/toast'\nimport {useEffect} from 'react'\n\nimport {useThemer} from './context'\nimport {decodeTheme, encodeTheme, SharedTheme} from './share'\nimport {displayTitle} from './themes'\n\n/**\n * Copying a theme as a code to share, and adding a theme from a code someone\n * shared — with toasts telling how it went.\n *\n * @internal\n */\nexport function useThemeCodes(): {\n copyTheme: (theme: SharedTheme) => Promise<void>\n /** Adds the theme in the text, if there is one — the result says whether there was */\n addThemeFromText: (text: string) => boolean\n /** Adds the theme on the clipboard, if there is one and the browser hands the clipboard over */\n addThemeFromClipboard: () => Promise<'added' | 'no-theme' | 'no-clipboard'>\n} {\n const {send} = useThemer()\n const toast = useToast()\n\n const copyTheme = async (theme: SharedTheme) => {\n const title = displayTitle(theme.title)\n\n try {\n await navigator.clipboard.writeText(encodeTheme(theme))\n toast.push({\n status: 'success',\n title: `Copied ${title} as a code`,\n description: 'Paste it in the themer of another Studio to add the theme there',\n })\n } catch {\n toast.push({status: 'error', title: `Could not copy ${title}`})\n }\n }\n\n const addThemeFromText = (text: string) => {\n const theme = decodeTheme(text)\n\n if (!theme) return false\n\n send({type: 'theme.import', title: theme.title, options: theme.options})\n toast.push({status: 'success', title: `Added ${theme.title}`})\n\n return true\n }\n\n const addThemeFromClipboard = async () => {\n let text: string\n\n try {\n text = await navigator.clipboard.readText()\n } catch {\n return 'no-clipboard'\n }\n\n return addThemeFromText(text) ? 'added' : 'no-theme'\n }\n\n return {copyTheme, addThemeFromText, addThemeFromClipboard}\n}\n\n/**\n * Adds a theme pasted anywhere in the Studio while the themer's list is\n * showing — pastes into inputs and other editable things are theirs.\n *\n * @internal\n */\nexport function usePasteThemeCodes(): void {\n const {open, send} = useThemer()\n const toast = useToast()\n\n useEffect(() => {\n if (!open) return undefined\n\n const handlePaste = (event: ClipboardEvent) => {\n if (isEditable(event.target)) return\n\n const theme = decodeTheme(event.clipboardData?.getData('text/plain') ?? '')\n\n if (!theme) return\n\n event.preventDefault()\n send({type: 'theme.import', title: theme.title, options: theme.options})\n toast.push({status: 'success', title: `Added ${theme.title}`})\n }\n\n document.addEventListener('paste', handlePaste)\n\n return () => document.removeEventListener('paste', handlePaste)\n }, [open, send, toast])\n}\n\nfunction isEditable(target: EventTarget | null): boolean {\n return (\n target instanceof HTMLElement &&\n (target.isContentEditable || target.matches('input, textarea, select'))\n )\n}\n","import {Button, Card, Dialog, Flex, Stack, Text, TextInput} from '@sanity/ui'\nimport {useState} from 'react'\n\nimport {useThemeCodes} from './useThemeCodes'\n\n/**\n * Where a theme code someone shared gets pasted by hand — for browsers that\n * do not hand the clipboard over, and for codes that are not on it.\n *\n * @internal\n */\nexport function PasteThemeDialog(props: {onClose: () => void}) {\n const {onClose} = props\n const {addThemeFromText} = useThemeCodes()\n const [text, setText] = useState('')\n const [invalid, setInvalid] = useState(false)\n\n const add = () => {\n if (addThemeFromText(text)) {\n onClose()\n } else {\n setInvalid(true)\n }\n }\n\n return (\n <Dialog\n animate\n footer={\n <Flex gap={2} justify=\"flex-end\" padding={3}>\n <Button mode=\"ghost\" onClick={onClose} padding={2} text=\"Cancel\" />\n <Button\n disabled={text.trim() === ''}\n onClick={add}\n padding={2}\n text=\"Add theme\"\n tone=\"primary\"\n />\n </Flex>\n }\n header=\"Add a shared theme\"\n id=\"themer-paste\"\n onClickOutside={onClose}\n onClose={onClose}\n width={0}\n >\n <form\n onSubmit={(event) => {\n event.preventDefault()\n add()\n }}\n >\n <Stack gap={3} padding={4}>\n <Text muted size={1}>\n Paste the code someone copied from their themer — the theme is added to your list and\n applied.\n </Text>\n <TextInput\n aria-describedby={invalid ? 'themer-paste-error' : undefined}\n aria-invalid={invalid}\n aria-label=\"Theme code\"\n autoFocus\n customValidity={invalid ? 'That is not a theme code' : undefined}\n fontSize={1}\n onChange={(event) => {\n setText(event.currentTarget.value)\n setInvalid(false)\n }}\n placeholder=\"AQ…\"\n value={text}\n />\n {invalid && (\n <Card id=\"themer-paste-error\" padding={3} radius={2} role=\"alert\" tone=\"critical\">\n <Text size={1}>\n That is not a theme code — codes are one word, copied from a theme's menu.\n </Text>\n </Card>\n )}\n </Stack>\n </form>\n </Dialog>\n )\n}\n","import {style} from '@vanilla-extract/css'\n\nexport const root = style({\n position: 'relative',\n})\n\n/** A bare button, so that the thumbnail and the title can carry the styling */\nexport const pickButton = style({\n 'appearance': 'none',\n 'display': 'block',\n 'boxSizing': 'border-box',\n 'width': '100%',\n 'margin': 0,\n 'padding': 0,\n 'border': 0,\n 'background': 'none',\n 'color': 'inherit',\n 'font': 'inherit',\n 'textAlign': 'center',\n 'cursor': 'pointer',\n ':focus': {\n outline: 'none',\n },\n})\n\n/**\n * Wraps the thumbnail with room for the ring that marks the applied theme —\n * hovering shows a faint ring, the applied theme the focus ring color, and\n * keyboard focus an outline. The rings are drawn inside the frame's padding,\n * so nothing sticks out to be clipped by the scrolling list.\n */\nexport const frame = style({\n display: 'block',\n padding: 4,\n borderRadius: 10,\n transition: 'box-shadow 100ms',\n selectors: {\n [`${pickButton}:hover &`]: {\n boxShadow: 'inset 0 0 0 2px var(--card-border-color)',\n },\n [`${pickButton}[aria-pressed='true'] &`]: {\n boxShadow: 'inset 0 0 0 2px var(--card-focus-ring-color)',\n },\n [`${pickButton}:focus-visible &`]: {\n outline: '2px solid var(--card-focus-ring-color)',\n outlineOffset: -2,\n },\n },\n})\n\n/**\n * The actions menu sits on the thumbnail's top right corner, like the actions\n * of an image input, and only shows on hover, on keyboard focus, and while it\n * is open — its popover is portaled, so an open menu does not count as focus\n * within\n */\nexport const menuSlot = style({\n position: 'absolute',\n top: 10,\n right: 10,\n opacity: 0,\n transition: 'opacity 100ms',\n selectors: {\n [`${root}:hover &, ${root}:focus-within &, &[data-visible='true']`]: {\n opacity: 1,\n },\n },\n})\n\n/** The dark card behind the menu button sizes to the button, not the slot */\nexport const menuCard = style({\n display: 'inline-block',\n})\n","import {ArrowDownIcon} from '@sanity/icons/ArrowDown'\nimport {ArrowUpIcon} from '@sanity/icons/ArrowUp'\nimport {CopyIcon} from '@sanity/icons/Copy'\nimport {EditIcon} from '@sanity/icons/Edit'\nimport {EllipsisHorizontalIcon} from '@sanity/icons/EllipsisHorizontal'\nimport {ShareIcon} from '@sanity/icons/Share'\nimport {TrashIcon} from '@sanity/icons/Trash'\nimport {Box, Button, Card, Text, useRootTheme} from '@sanity/ui'\nimport {Menu, MenuButton, MenuDivider, MenuItem} from '@sanity/ui/menu'\nimport {Tooltip} from '@sanity/ui/tooltip'\nimport {Reorder, useDragControls} from 'motion/react'\nimport {useRef, useState} from 'react'\nimport scrollIntoView from 'scroll-into-view-if-needed'\n\nimport {useThemer} from './context'\nimport {displayTitle, ThemerTheme} from './themes'\nimport {ThemeThumbnail} from './ThemeThumbnail'\nimport {useThemeCodes} from './useThemeCodes'\n\nimport {frame, menuCard, menuSlot, pickButton, root} from './ThemeCard.css'\n\n/**\n * Scrolls the applied theme's card into view when it is not already, as a\n * ref: the list scrolls to it as it mounts — coming back from the editor or\n * the removed themes — and when another theme gets applied\n */\nfunction scrollRefIntoViewIfNeeded(node: HTMLElement | null) {\n if (node) scrollIntoView(node, {block: 'nearest', scrollMode: 'if-needed'})\n}\n\n/**\n * One theme in the list: the floating preview with the title below it, like\n * an appearance option in macOS System Settings. Clicking it applies the\n * theme to the whole Studio, dragging it moves the theme to another place in\n * the list; its menu holds the move, edit, duplicate and remove flows that\n * apply to the theme.\n *\n * @internal\n */\nexport function ThemeCard(props: {active: boolean; theme: ThemerTheme}) {\n const {active, theme} = props\n const {themes, send} = useThemer()\n const {copyTheme} = useThemeCodes()\n const {scheme} = useRootTheme()\n const [menuOpen, setMenuOpen] = useState(false)\n const dragControls = useDragControls()\n // Letting go of a drag also clicks the card, and that click must not pick\n const dragged = useRef(false)\n const title = displayTitle(theme.title)\n const index = themes.findIndex((candidate) => candidate.slug === theme.slug)\n\n const moveTo = (to: number) => {\n const order = themes.map((candidate) => candidate.slug)\n\n order.splice(to, 0, ...order.splice(index, 1))\n send({type: 'theme.reorder', order})\n }\n\n return (\n <Reorder.Item\n as=\"div\"\n className={root}\n dragControls={dragControls}\n dragListener={false}\n onDragEnd={() => {\n // The click lands before the next frame\n requestAnimationFrame(() => {\n dragged.current = false\n })\n }}\n onDragStart={() => {\n dragged.current = true\n }}\n ref={active ? scrollRefIntoViewIfNeeded : undefined}\n value={theme.slug}\n whileDrag={{scale: 1.04}}\n >\n <button\n aria-pressed={active}\n className={pickButton}\n onClick={() => {\n if (!dragged.current) send({type: 'theme.pick', slug: theme.slug})\n }}\n onPointerDown={(event) => {\n // A mouse or pen drags the card off the button; a finger scrolls the\n // list instead, and moves themes from the menu\n if (event.pointerType !== 'touch') dragControls.start(event)\n }}\n type=\"button\"\n >\n <span className={frame}>\n <ThemeThumbnail options={theme.options} />\n </span>\n <Box as=\"span\" display=\"block\" paddingTop={2} paddingX={2}>\n <Text\n align=\"center\"\n as=\"span\"\n size={1}\n textOverflow=\"ellipsis\"\n weight={active ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>\n </button>\n\n {/* The tooltip wraps a card rather than the button: the menu button\n clones its button to wire it up, which a tooltip in between would\n swallow. The card keeps the button dark, as it sits on the dark\n half of the thumbnail, while the menu opens in the sidebar's scheme */}\n <div className={menuSlot} data-visible={menuOpen}>\n <Tooltip animate content={<Text size={1}>Show more</Text>} placement=\"bottom\" portal>\n <Card className={menuCard} radius={2} scheme=\"dark\">\n <MenuButton\n button={\n <Button\n aria-label={`Show more for ${title}`}\n fontSize={1}\n icon={EllipsisHorizontalIcon}\n mode=\"ghost\"\n padding={1}\n radius={2}\n />\n }\n id={`themer-theme-${theme.slug}`}\n menu={\n <Menu>\n <MenuItem\n disabled={index <= 0}\n icon={ArrowUpIcon}\n onClick={() => moveTo(index - 1)}\n text=\"Move up\"\n />\n <MenuItem\n disabled={index >= themes.length - 1}\n icon={ArrowDownIcon}\n onClick={() => moveTo(index + 1)}\n text=\"Move down\"\n />\n <MenuDivider />\n {theme.source === 'custom' && (\n <MenuItem\n icon={EditIcon}\n onClick={() => send({type: 'theme.edit', slug: theme.slug})}\n text=\"Edit\"\n />\n )}\n <MenuItem\n icon={CopyIcon}\n onClick={() => send({type: 'theme.duplicate', slug: theme.slug})}\n text={theme.source === 'custom' ? 'Duplicate' : 'Duplicate to edit'}\n />\n <MenuItem\n icon={ShareIcon}\n onClick={() => void copyTheme(theme)}\n text=\"Copy to share\"\n />\n {theme.source !== 'config' && (\n <>\n <MenuDivider />\n <MenuItem\n icon={TrashIcon}\n onClick={() => send({type: 'theme.remove', slug: theme.slug})}\n text=\"Remove\"\n tone=\"critical\"\n />\n </>\n )}\n </Menu>\n }\n onClose={() => setMenuOpen(false)}\n onOpen={() => setMenuOpen(true)}\n popover={{animate: true, placement: 'bottom-end', portal: true, scheme}}\n />\n </Card>\n </Tooltip>\n </div>\n </Reorder.Item>\n )\n}\n","import {style} from '@vanilla-extract/css'\n\nimport {MAXIMUM_WIDTH, MINIMUM_WIDTH} from './sidebarWidth'\n\nimport {sidebarContent} from './ResizableSidebar.css'\n\nconst CARD_GAP = 16\n\n/**\n * The theme cards flow into two columns once the sidebar has been dragged a\n * third of the way from its narrowest to its widest. The breakpoints are\n * container queries on the sidebar's content (`ResizableSidebar`), which is\n * as wide as the sidebar itself.\n */\nconst TWO_COLUMNS_FROM = MINIMUM_WIDTH + (MAXIMUM_WIDTH - MINIMUM_WIDTH) * 0.3\n\n/** Three columns on the wider small screens the sidebar covers, where two would be huge */\nconst THREE_COLUMNS_FROM = 480\n\n/**\n * The columns share the width equally whatever the titles: a plain `1fr` may\n * not shrink below a card's min-content width, and a card's title does not\n * wrap, so one long title would widen its column past the sidebar\n */\nconst COLUMN = 'minmax(0, 1fr)'\n\nexport const cardGrid = style({\n 'display': 'grid',\n 'gridTemplateColumns': COLUMN,\n 'gap': CARD_GAP,\n '@container': {\n [`${sidebarContent} (min-width: ${TWO_COLUMNS_FROM}px)`]: {\n gridTemplateColumns: `repeat(2, ${COLUMN})`,\n },\n [`${sidebarContent} (min-width: ${THREE_COLUMNS_FROM}px)`]: {\n gridTemplateColumns: `repeat(3, ${COLUMN})`,\n },\n },\n})\n","import {AddIcon} from '@sanity/icons/Add'\nimport {ClipboardIcon} from '@sanity/icons/Clipboard'\nimport {RestoreIcon} from '@sanity/icons/Restore'\nimport {Box, Button, Card, Flex, Stack} from '@sanity/ui'\nimport {MotionConfig, Reorder} from 'motion/react'\nimport {useState} from 'react'\n\nimport {useThemer} from './context'\nimport {ImageFileButton} from './ImageFileButton'\nimport {optionsFromImagePalette, titleFromFileName} from './imagePalette'\nimport {PasteThemeDialog} from './PasteThemeDialog'\nimport {ScrollArea} from './ScrollArea'\nimport {ThemeCard} from './ThemeCard'\nimport {TooltipButton} from './TooltipButton'\nimport {useImagePalette} from './useImagePalette'\nimport {usePasteThemeCodes, useThemeCodes} from './useThemeCodes'\n\nimport {cardGrid} from './ThemeList.css'\n\n/**\n * One column at the sidebar's default width; wider sidebars — and the\n * overlay on small screens — fit more cards per row\n */\n/**\n * The flow for picking a theme: a grid of theme cards — the configured theme,\n * the presets and the user's own themes — that drag into the order the user\n * wants, with the entry points to the add and restore flows below. A theme\n * code someone shared pastes right into the list, or through the paste button.\n *\n * @internal\n */\nexport function ThemeList() {\n const {themes, removed, active, send} = useThemer()\n const {addThemeFromClipboard} = useThemeCodes()\n const [pasting, setPasting] = useState(false)\n\n usePasteThemeCodes()\n\n const {busy, pickImage} = useImagePalette((palette, file) =>\n send({\n type: 'theme.add',\n title: titleFromFileName(file.name),\n options: optionsFromImagePalette(palette),\n palette,\n imageUrl: URL.createObjectURL(file),\n }),\n )\n\n return (\n <>\n <ScrollArea padding={3}>\n <Stack gap={4}>\n {/* The group tells a column from a grid by measuring the cards, and\n lets go of the layout animations for users who prefer reduced motion */}\n <MotionConfig reducedMotion=\"user\">\n <Reorder.Group\n as=\"div\"\n className={cardGrid}\n onReorder={(order: string[]) => send({type: 'theme.reorder', order})}\n values={themes.map((theme) => theme.slug)}\n >\n {themes.map((theme) => (\n <ThemeCard active={theme.slug === active.slug} key={theme.slug} theme={theme} />\n ))}\n </Reorder.Group>\n </MotionConfig>\n {/* Trails the list rather than sitting in the footer, so the footer\n does not shift when the first theme gets removed */}\n {removed.length > 0 && (\n <Button\n gap={2}\n icon={RestoreIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.removed'})}\n padding={2}\n text={`Show removed (${removed.length})`}\n width=\"fill\"\n />\n )}\n </Stack>\n </ScrollArea>\n\n <Card borderTop padding={3}>\n <Flex gap={2}>\n <Box flex={1}>\n <TooltipButton\n icon={AddIcon}\n mode=\"ghost\"\n onClick={() => send({type: 'theme.add'})}\n text=\"Add theme\"\n tooltip=\"Add a theme based on the applied one\"\n width=\"fill\"\n />\n </Box>\n <ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n tooltip=\"Add a theme from the colors of an image\"\n />\n <TooltipButton\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={async () => {\n // Straight from the clipboard where the browser allows; by hand otherwise\n if ((await addThemeFromClipboard()) !== 'added') setPasting(true)\n }}\n tooltip=\"Add a theme from a code someone shared\"\n />\n </Flex>\n </Card>\n\n {pasting && <PasteThemeDialog onClose={() => setPasting(false)} />}\n </>\n )\n}\n","import {BuildThemeOptions, SCHEMES, SchemeThemeOptions} from '../theme/options'\nimport {minimizeOptions} from './options'\n\n/**\n * Serializes theme options into the `buildTheme` call to paste into\n * `sanity.config.ts`, keeping only what differs from the derived defaults in\n * each scheme.\n *\n * Options that boil down to the stock Studio theme need nothing from this\n * package — so they serialize to a bare `buildTheme()` from `@sanity/ui/theme`\n * instead.\n *\n * @internal\n */\nexport function createThemeSnippet(options: BuildThemeOptions): string {\n const minimized = minimizeOptions(options)\n\n if (minimized === null) {\n return \"import {buildTheme} from '@sanity/ui/theme'\\n\\nexport const theme = buildTheme()\\n\"\n }\n\n const lines: string[] = []\n\n for (const scheme of SCHEMES) {\n const values = minimized[scheme]\n\n if (values) lines.push(serializeScheme(scheme, values))\n }\n\n return `import {buildTheme} from '@sanity/themer'\\n\\nexport const theme = buildTheme({\\n${lines.join('\\n')}\\n})\\n`\n}\n\n/** A scheme with up to two options fits on one line, more spread out */\nfunction serializeScheme(scheme: string, values: SchemeThemeOptions): string {\n const entries: string[] = []\n\n if (values.accent !== undefined) entries.push(`accent: '${values.accent}'`)\n if (values.text !== undefined) entries.push(`text: '${values.text}'`)\n if (values.background !== undefined) entries.push(`background: '${values.background}'`)\n if (values.contrast !== undefined) entries.push(`contrast: ${values.contrast}`)\n\n if (entries.length <= 2) {\n return ` ${scheme}: {${entries.join(', ')}},`\n }\n\n return ` ${scheme}: {\\n${entries.map((entry) => ` ${entry},`).join('\\n')}\\n },`\n}\n","import {ClipboardIcon} from '@sanity/icons/Clipboard'\nimport {Box, Button, Card, Dialog, Flex, Stack, Text} from '@sanity/ui'\nimport {Code} from '@sanity/ui/code'\nimport {useToast} from '@sanity/ui/toast'\nimport {registerLanguage} from 'react-refractor'\nimport typescript from 'refractor/typescript'\n\nimport {createThemeSnippet} from './snippet'\nimport {displayTitle, ThemerTheme} from './themes'\n\n// `Code` only highlights languages the surrounding app has registered with\n// react-refractor, and the Studio registers its own set from an async import\n// during startup — a race this dialog keeps losing. Registering the one\n// language the snippet needs keeps it highlighted from the first render.\nregisterLanguage(typescript)\n\n/**\n * Shows the `buildTheme` snippet that makes a theme permanent, in a dialog so\n * that it does not take up sidebar space or make the sidebar scroll as the\n * snippet changes shape with the theme.\n *\n * @internal\n */\nexport function ThemeSnippetDialog(props: {onClose: () => void; theme: ThemerTheme}) {\n const {onClose, theme} = props\n const toast = useToast()\n const snippet = createThemeSnippet(theme.options)\n\n const handleCopy = async () => {\n try {\n await navigator.clipboard.writeText(snippet)\n toast.push({status: 'success', title: 'Copied the theme to the clipboard'})\n } catch {\n toast.push({status: 'error', title: 'Could not copy the theme'})\n }\n }\n\n return (\n <Dialog\n animate\n footer={\n <Flex gap={2} justify=\"flex-end\" padding={3}>\n <Button\n gap={2}\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={() => void handleCopy()}\n padding={2}\n text=\"Copy\"\n />\n </Flex>\n }\n header=\"Add to your config\"\n id=\"themer-snippet\"\n onClickOutside={onClose}\n onClose={onClose}\n width={1}\n >\n <Box padding={4}>\n <Stack gap={4}>\n <Text muted size={1}>\n Export this from <code>sanity.config.ts</code> and pass it as the <code>theme</code> of{' '}\n <code>defineConfig</code> to make “{displayTitle(theme.title)}” the Studio theme without\n this tool.\n </Text>\n <Card border overflow=\"auto\" padding={3} radius={2} tone=\"transparent\">\n <Code language=\"ts\" size={1}>\n {snippet}\n </Code>\n </Card>\n </Stack>\n </Box>\n </Dialog>\n )\n}\n","import {style} from '@vanilla-extract/css'\n\n/** Lets the title shrink and truncate instead of pushing the buttons out */\nexport const title = style({\n minWidth: 0,\n})\n","import {ArrowLeftIcon} from '@sanity/icons/ArrowLeft'\nimport {CloseIcon} from '@sanity/icons/Close'\nimport {CodeBlockIcon} from '@sanity/icons/CodeBlock'\nimport {SplitVerticalIcon} from '@sanity/icons/SplitVertical'\nimport {Box, Card, Flex, Text} from '@sanity/ui'\nimport {useState} from 'react'\n\nimport {useThemer} from './context'\nimport {RemovedThemes} from './RemovedThemes'\nimport {ThemeEditor} from './ThemeEditor'\nimport {ThemeList} from './ThemeList'\nimport {ThemeSnippetDialog} from './ThemeSnippetDialog'\nimport {TooltipButton} from './TooltipButton'\n\nimport {title} from './ThemerSidebar.css'\n\nconst VIEW_TITLES = {\n list: 'Themer',\n edit: 'Edit theme',\n removed: 'Removed themes',\n} as const\n\n/**\n * The themer sidebar: a header that navigates between the flows and toggles\n * the split preview, the flow itself — picking a theme, editing one, or\n * restoring removed ones — and the dialog with the `buildTheme` snippet of\n * the applied theme.\n *\n * @internal\n */\nexport function ThemerSidebar() {\n const {active, split, view, navbarHeight, send} = useThemer()\n const [snippetOpen, setSnippetOpen] = useState(false)\n const inList = view.name === 'list'\n\n return (\n <Card height=\"fill\">\n <Flex direction=\"column\" height=\"fill\">\n {/* The header sits next to the Studio navbar and takes its height, so\n the two bottom borders line up — whatever height the Studio, its\n breakpoint or a custom navbar gives it */}\n <Card\n borderBottom\n padding={3}\n sizing=\"border\"\n style={navbarHeight === null ? undefined : {height: navbarHeight}}\n >\n <Flex align=\"center\" gap={1} height=\"fill\">\n {!inList && (\n <TooltipButton\n icon={ArrowLeftIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.list'})}\n padding={2}\n tooltip=\"Back to the themes\"\n />\n )}\n <Box className={title} flex={1}>\n <Text size={1} textOverflow=\"ellipsis\" weight=\"semibold\">\n {VIEW_TITLES[view.name]}\n </Text>\n </Box>\n {/* Packed without gaps, so the title still fits next to them at the\n narrowest sidebar width */}\n <Flex>\n {view.name !== 'removed' && (\n <>\n <TooltipButton\n aria-pressed={split}\n icon={SplitVerticalIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'preview.toggle'})}\n padding={2}\n selected={split}\n tooltip=\"Show light and dark side by side\"\n />\n <TooltipButton\n icon={CodeBlockIcon}\n mode=\"bleed\"\n onClick={() => setSnippetOpen(true)}\n padding={2}\n tooltip=\"Show the code for the applied theme\"\n />\n </>\n )}\n <TooltipButton\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.close'})}\n padding={2}\n tooltip=\"Close themer\"\n />\n </Flex>\n </Flex>\n </Card>\n\n {view.name === 'list' && <ThemeList />}\n {view.name === 'edit' && <ThemeEditor focusTitle={view.focusTitle} slug={view.slug} />}\n {view.name === 'removed' && <RemovedThemes />}\n </Flex>\n\n {snippetOpen && <ThemeSnippetDialog onClose={() => setSnippetOpen(false)} theme={active} />}\n </Card>\n )\n}\n","import {createContainer, createViewTransition, style} from '@vanilla-extract/css'\n\n/**\n * The view transition group of the sidebar, which the split preview's\n * transition (see `ThemerLayout.css.ts`) leaves alone: it never animates, and\n * the Studio copies animating underneath never paint over it\n */\nexport const sidebarTransition = createViewTransition()\n\n/** The sidebar's content, which the content's layout queries — as wide as the sidebar itself */\nexport const sidebarContent = createContainer()\n\nexport const sidebar = style({\n flex: 'none',\n viewTransitionName: sidebarTransition,\n selectors: {\n // On small screens the sidebar covers the Studio instead of standing next to it\n \"&[data-overlay='true']\": {\n position: 'absolute',\n inset: 0,\n width: 'auto',\n },\n },\n})\n\n/**\n * Draws the sidebar's border and gives the resize handle its card colors. It\n * does not clip, so the handle can straddle the border — the content is\n * clipped one level down instead.\n */\nexport const frame = style({\n position: 'relative',\n})\n\n/**\n * Clips the content, and is the container its layout queries: as wide as\n * the sidebar, whether or not a scrollbar takes room inside — see the theme\n * grid's columns in `ThemeList.css.ts`\n */\nexport const content = style({\n containerType: 'inline-size',\n containerName: sidebarContent,\n})\n\n/**\n * The grab area along the sidebar's left edge. It straddles the border so it\n * is easy to hit, and highlights while hovered, focused or dragged.\n */\nexport const resizeHandle = style({\n 'position': 'absolute',\n 'top': 0,\n 'bottom': 0,\n 'left': -3,\n 'zIndex': 1,\n 'width': 7,\n 'cursor': 'col-resize',\n 'touchAction': 'none',\n '::after': {\n content: '',\n position: 'absolute',\n top: 0,\n bottom: 0,\n left: 2,\n width: 3,\n background: 'var(--card-focus-ring-color)',\n opacity: 0,\n transition: 'opacity 100ms',\n },\n ':focus': {\n outline: 'none',\n },\n 'selectors': {\n \"&:hover::after, &:focus-visible::after, &[data-dragging='true']::after\": {\n opacity: 1,\n },\n },\n})\n","import {Box, Card, Layer} from '@sanity/ui'\nimport {useCallback, useEffect, useRef, useState} from 'react'\n\nimport {MAXIMUM_WIDTH, MINIMUM_WIDTH} from './sidebarWidth'\nimport {ThemerSidebar} from './ThemerSidebar'\n\nimport {content, frame, resizeHandle, sidebar} from './ResizableSidebar.css'\n\n/** How far one arrow key press resizes the sidebar */\nconst KEYBOARD_STEP = 16\n\nconst WIDTH_STORAGE_KEY = 'sanityStudio:themer:width'\n\nfunction clampWidth(width: number): number {\n return Math.min(MAXIMUM_WIDTH, Math.max(MINIMUM_WIDTH, Math.round(width)))\n}\n\nfunction readStoredWidth(): number {\n try {\n const stored = Number(localStorage.getItem(WIDTH_STORAGE_KEY))\n\n return Number.isFinite(stored) && stored > 0 ? clampWidth(stored) : MINIMUM_WIDTH\n } catch {\n return MINIMUM_WIDTH\n }\n}\n\nfunction writeStoredWidth(width: number): void {\n try {\n if (width === MINIMUM_WIDTH) {\n localStorage.removeItem(WIDTH_STORAGE_KEY)\n } else {\n localStorage.setItem(WIDTH_STORAGE_KEY, String(width))\n }\n } catch {\n // Storage can be unavailable (e.g. private browsing) — the width just won't persist\n }\n}\n\n/**\n * The themer sidebar, in a layer along the right edge of the Studio. Its left\n * edge drags (or arrow-keys) it wider, up to twice its default width — the\n * width sticks between sessions. As an overlay (small screens) it covers the\n * Studio edge to edge instead, with nothing to resize.\n *\n * @internal\n */\nexport function ResizableSidebar(props: {overlay: boolean}) {\n const {overlay} = props\n const [width, setWidth] = useState(readStoredWidth)\n const [dragging, setDragging] = useState(false)\n const drag = useRef<{pointerId: number; startX: number; startWidth: number} | null>(null)\n\n useEffect(() => writeStoredWidth(width), [width])\n\n const handlePointerDown = useCallback(\n (event: React.PointerEvent<HTMLDivElement>) => {\n if (event.button !== 0) return\n\n event.preventDefault()\n event.currentTarget.setPointerCapture(event.pointerId)\n drag.current = {pointerId: event.pointerId, startX: event.clientX, startWidth: width}\n setDragging(true)\n },\n [width],\n )\n\n const handlePointerMove = useCallback((event: React.PointerEvent<HTMLDivElement>) => {\n if (!drag.current || drag.current.pointerId !== event.pointerId) return\n\n // The sidebar sits on the right, so dragging left makes it wider\n setWidth(clampWidth(drag.current.startWidth - (event.clientX - drag.current.startX)))\n }, [])\n\n const handlePointerUp = useCallback((event: React.PointerEvent<HTMLDivElement>) => {\n if (!drag.current || drag.current.pointerId !== event.pointerId) return\n\n drag.current = null\n setDragging(false)\n }, [])\n\n const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {\n const steps: Record<string, number> = {\n ArrowLeft: KEYBOARD_STEP,\n ArrowRight: -KEYBOARD_STEP,\n Home: MAXIMUM_WIDTH,\n End: -MAXIMUM_WIDTH,\n }\n const step = steps[event.key]\n\n if (step === undefined) return\n\n event.preventDefault()\n setWidth((current) => clampWidth(current + step))\n }, [])\n\n return (\n <Layer\n className={sidebar}\n data-overlay={overlay}\n style={overlay ? undefined : {width}}\n zOffset={100}\n >\n <Card borderLeft={!overlay} className={frame} height=\"fill\">\n {!overlay && (\n <div\n aria-label=\"Resize the themer\"\n className={resizeHandle}\n aria-orientation=\"vertical\"\n aria-valuemax={MAXIMUM_WIDTH}\n aria-valuemin={MINIMUM_WIDTH}\n aria-valuenow={width}\n data-dragging={dragging}\n onDoubleClick={() => setWidth(MINIMUM_WIDTH)}\n onKeyDown={handleKeyDown}\n onPointerCancel={handlePointerUp}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n // oxlint-disable-next-line prefer-tag-over-role -- a window splitter is a focusable, draggable separator, which an hr is not\n role=\"separator\"\n tabIndex={0}\n />\n )}\n <Box className={content} height=\"fill\" overflow=\"hidden\">\n <ThemerSidebar />\n </Box>\n </Card>\n </Layer>\n )\n}\n","import {useSyncExternalStore} from 'react'\n\nconst QUERY = '(prefers-reduced-motion: reduce)'\n\nfunction subscribe(onChange: () => void): () => void {\n const media = matchMedia(QUERY)\n\n media.addEventListener('change', onChange)\n\n return () => media.removeEventListener('change', onChange)\n}\n\nfunction getSnapshot(): boolean {\n return matchMedia(QUERY).matches\n}\n\nfunction getServerSnapshot(): boolean {\n return false\n}\n\n/**\n * Whether the user prefers reduced motion — live, so that turning the\n * setting on while the Studio is open takes effect right away.\n *\n * @internal\n */\nexport function usePrefersReducedMotion(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n}\n","import {type RefObject, useEffect, useState} from 'react'\n\n/**\n * The Studio navbar's root, the `Card` that draws its bottom border — its\n * test id is stable and unique to the navbar, unlike its `data-ui` name\n */\nconst NAVBAR_SELECTOR = '[data-testid=\"studio-navbar\"]'\n\n/**\n * The height of the Studio navbar inside the given element, in pixels — kept\n * up to date as the navbar renders, remounts and resizes, and `null` until\n * there is one. The themer's header takes this height, so that its bottom\n * border lines up with the navbar's whatever height the Studio version, its\n * breakpoint or a custom navbar gives it.\n *\n * @internal\n */\nexport function useStudioNavbarHeight(ref: RefObject<HTMLElement | null>): number | null {\n const [height, setHeight] = useState<number | null>(null)\n\n useEffect(() => {\n const root = ref.current\n\n if (!root) return undefined\n\n let navbar: Element | null = null\n const resizeObserver = new ResizeObserver(() => {\n if (navbar) setHeight(navbar.getBoundingClientRect().height)\n })\n\n // The navbar can render after the layout (the Studio suspends while it\n // loads) and remount (switching workspaces), so it is looked up again\n // whenever the Studio's DOM changes — cheap, as it sits near the top\n const observeNavbar = () => {\n const next = root.querySelector(NAVBAR_SELECTOR)\n\n if (next === navbar) return\n\n if (navbar) resizeObserver.unobserve(navbar)\n\n navbar = next\n\n if (navbar) {\n resizeObserver.observe(navbar)\n setHeight(navbar.getBoundingClientRect().height)\n } else {\n setHeight(null)\n }\n }\n\n const mutationObserver = new MutationObserver(observeNavbar)\n\n mutationObserver.observe(root, {childList: true, subtree: true})\n observeNavbar()\n\n return () => {\n mutationObserver.disconnect()\n resizeObserver.disconnect()\n }\n }, [ref])\n\n return height\n}\n","import {\n createViewTransition,\n globalStyle,\n keyframes,\n style,\n styleVariants,\n} from '@vanilla-extract/css'\n\nimport {sidebarTransition} from './ResizableSidebar.css'\n\n/**\n * The view transition classes of the split preview — what React puts in\n * `view-transition-class`, which the pseudo-element rules below select on.\n * Scoped identifiers, so nothing else on the page can mean the same.\n */\nexport const splitTransitionClasses = {\n /** The Studio the user was looking at cross-fades between its two widths */\n resize: createViewTransition('splitResize'),\n /** The Studio and the panel cross-fade to another theme, in place */\n crossfade: createViewTransition('themeCrossfade'),\n /** The split copy slides in from the side, or out to it */\n slideIn: createViewTransition('splitSlideIn'),\n slideOut: createViewTransition('splitSlideOut'),\n /** The split copy drops in from the top, or out to it, where the copies stack */\n dropIn: createViewTransition('splitDropIn'),\n dropOut: createViewTransition('splitDropOut'),\n}\n\n/** The view transition classes of the panel, which slides in from its edge and out to it */\nexport const panelTransitionClasses = {\n slideIn: createViewTransition('panelSlideIn'),\n slideOut: createViewTransition('panelSlideOut'),\n}\n\n/** The layout's `Flex` positions the sidebar overlay on small screens */\nexport const layout = style({\n position: 'relative',\n})\n\n/** A copy of the Studio in a color scheme of its own, for `color-scheme` */\nexport const studioScheme = styleVariants({\n light: {colorScheme: 'light'},\n dark: {colorScheme: 'dark'},\n})\n\nconst slideIn = keyframes({from: {transform: 'translateX(-100%)'}})\nconst slideOut = keyframes({to: {transform: 'translateX(-100%)'}})\nconst dropIn = keyframes({from: {transform: 'translateY(-100%)'}})\nconst dropOut = keyframes({to: {transform: 'translateY(-100%)'}})\nconst slideInFromEnd = keyframes({from: {transform: 'translateX(100%)'}})\nconst slideOutToEnd = keyframes({to: {transform: 'translateX(100%)'}})\n\n/**\n * How the panel and the split preview animate, through React's view\n * transitions: the panel slides in from its edge and the split copy from the\n * far one — a transform, nothing fades — and out the same way; the Studio the\n * user was looking at cross-fades between its two widths, its old and new\n * snapshots stretched to the group's box so it keeps its height. Everything\n * shares one duration and easing, so the edge a panel slides in on and the\n * edge the Studio gives way with stay together.\n */\nglobalStyle(\n [\n `::view-transition-group(.${splitTransitionClasses.resize})`,\n `::view-transition-old(.${splitTransitionClasses.resize})`,\n `::view-transition-new(.${splitTransitionClasses.resize})`,\n `::view-transition-new(.${splitTransitionClasses.slideIn})`,\n `::view-transition-old(.${splitTransitionClasses.slideOut})`,\n `::view-transition-new(.${splitTransitionClasses.dropIn})`,\n `::view-transition-old(.${splitTransitionClasses.dropOut})`,\n `::view-transition-new(.${panelTransitionClasses.slideIn})`,\n `::view-transition-old(.${panelTransitionClasses.slideOut})`,\n `::view-transition-old(.${splitTransitionClasses.crossfade})`,\n `::view-transition-new(.${splitTransitionClasses.crossfade})`,\n ].join(', '),\n {\n animationDuration: '320ms',\n animationTimingFunction: 'cubic-bezier(0.2, 0, 0, 1)',\n },\n)\n\nglobalStyle(\n [\n `::view-transition-old(.${splitTransitionClasses.resize})`,\n `::view-transition-new(.${splitTransitionClasses.resize})`,\n ].join(', '),\n {\n inlineSize: '100%',\n blockSize: '100%',\n objectFit: 'fill',\n },\n)\n\nglobalStyle(`::view-transition-new(.${splitTransitionClasses.slideIn})`, {animationName: slideIn})\nglobalStyle(`::view-transition-old(.${splitTransitionClasses.slideOut})`, {\n animationName: slideOut,\n})\nglobalStyle(`::view-transition-new(.${splitTransitionClasses.dropIn})`, {animationName: dropIn})\nglobalStyle(`::view-transition-old(.${splitTransitionClasses.dropOut})`, {animationName: dropOut})\nglobalStyle(`::view-transition-new(.${panelTransitionClasses.slideIn})`, {\n animationName: slideInFromEnd,\n})\nglobalStyle(`::view-transition-old(.${panelTransitionClasses.slideOut})`, {\n animationName: slideOutToEnd,\n})\n\n/**\n * While the split preview toggles, the sidebar — a group of its own, which\n * `ResizableSidebar` names — does not animate at all: its new snapshot simply\n * shows, stacked above the Studio copies where it covers the Studio (groups of\n * elements that only exist in the new state, the arriving copy, would\n * otherwise be stacked last, over it). While the panel itself opens or closes,\n * or cross-fades to another theme, React names it instead, and the rules\n * above apply.\n *\n */\nglobalStyle(`::view-transition-group(${sidebarTransition})`, {zIndex: 1})\nglobalStyle(\n [\n `::view-transition-group(${sidebarTransition})`,\n `::view-transition-image-pair(${sidebarTransition})`,\n `::view-transition-old(${sidebarTransition})`,\n `::view-transition-new(${sidebarTransition})`,\n ].join(', '),\n {animation: 'none'},\n)\n\n/**\n * Someone who prefers reduced motion sees no motion here: the layout does not\n * start these transitions in the first place (see `ThemerLayout`), and should\n * one run anyway — the preference changed after the layout read it, the\n * Studio ran one of its own over these groups — none of their animations does\n * anything: the new state simply shows.\n */\nglobalStyle(\n ['group', 'image-pair', 'old', 'new']\n .flatMap((part) => {\n const groups = [\n ...Object.values(splitTransitionClasses).map((className) => `.${className}`),\n ...Object.values(panelTransitionClasses).map((className) => `.${className}`),\n sidebarTransition,\n ]\n\n return groups.map((group) => `::view-transition-${part}(${group})`)\n })\n .join(', '),\n {\n '@media': {\n '(prefers-reduced-motion: reduce)': {\n animation: 'none',\n },\n },\n },\n)\n","import {Card, Flex, ThemeProvider, useMediaIndex} from '@sanity/ui'\nimport {type RootTheme, type ThemeColorSchemeKey} from '@sanity/ui/theme'\nimport {useActor, useSelector} from '@xstate/react'\nimport {\n Activity,\n useDeferredValue,\n useEffect,\n useMemo,\n useRef,\n useState,\n ViewTransition,\n} from 'react'\nimport {type LayoutProps, useColorSchemeValue} from 'sanity'\n\nimport {buildTheme} from '../theme/buildTheme'\nimport {BuildThemeOptions} from '../theme/options'\nimport {ThemerContext, ThemerContextValue, ThemerView} from './context'\nimport {selectStoredState, ThemerInput, themerMachine, ThemerSnapshot} from './machine'\nimport {ResizableSidebar} from './ResizableSidebar'\nimport {readStoredState} from './storage'\nimport {resolveThemes, ThemerState} from './themes'\nimport {usePrefersReducedMotion} from './usePrefersReducedMotion'\nimport {useStudioNavbarHeight} from './useStudioNavbarHeight'\n\nimport {\n layout,\n panelTransitionClasses,\n splitTransitionClasses,\n studioScheme,\n} from './ThemerLayout.css'\n\n/**\n * Below this media index the Studio collapses its navbar into a drawer, and\n * the themer follows suit: the sidebar covers the Studio instead of standing\n * next to it, and the split preview stacks instead of sitting side by side.\n */\nconst MOBILE_MEDIA_INDEX = 1\n\nfunction sameStoredState(a: ThemerState, b: ThemerState): boolean {\n return (\n a.active === b.active && a.custom === b.custom && a.removed === b.removed && a.order === b.order\n )\n}\n\nfunction selectView(snapshot: ThemerSnapshot): ThemerView {\n const {editing} = snapshot.context\n\n if (snapshot.matches({flow: 'edit'}) && editing) {\n return {name: 'edit', slug: editing.slug, focusTitle: editing.focusTitle}\n }\n\n if (snapshot.matches({flow: 'removed'})) {\n return {name: 'removed'}\n }\n\n return {name: 'list'}\n}\n\nfunction sameView(a: ThemerView, b: ThemerView): boolean {\n if (a.name !== b.name) return false\n\n return (\n a.name !== 'edit' || b.name !== 'edit' || (a.slug === b.slug && a.focusTitle === b.focusTitle)\n )\n}\n\n/**\n * Wraps the whole Studio so that the theme picked in the themer sidebar\n * applies everywhere while the user browses around, runs the themer machine\n * that the navbar toggle and the sidebar share — the user's themes, which one\n * is applied, which flow the sidebar is in and how the Studio is previewed —\n * and renders the sidebar next to the Studio. The sidebar sits at the\n * `layout` level rather than in `activeToolLayout` because the split preview\n * renders the Studio twice, and the sidebar must not come along.\n *\n * The Studio next to the sidebar always follows the appearance setting\n * (light/dark/system) and the picked theme like any other theme would. The\n * split preview adds a second copy in the opposite scheme on the far side —\n * or on top, on small screens — through React's view transitions (React\n * 19.3), styled in `ThemerLayout.css.ts`.\n *\n * The machine says what shows and when the layout is in motion (its `panel`,\n * `split` and `moving` tags); the layout defers what shows, which puts the\n * panel's and the copy's mounts in a transition — what lets React animate\n * them — and picks the view transition classes from the tags.\n *\n * @internal\n */\nexport function ThemerLayout(props: LayoutProps & {baseOptions: BuildThemeOptions}) {\n const {baseOptions, ...layoutProps} = props\n const [input] = useState<ThemerInput>(() => ({baseOptions, stored: readStoredState()}))\n const [snapshot, send, actorRef] = useActor(themerMachine, {input})\n const stored = useSelector(actorRef, selectStoredState, sameStoredState)\n const view = useSelector(actorRef, selectView, sameView)\n const open = snapshot.hasTag('panel')\n const split = snapshot.hasTag('split')\n const moving = snapshot.hasTag('moving')\n const switching = snapshot.hasTag('switching')\n const {images} = snapshot.context\n const mobile = useMediaIndex() <= MOBILE_MEDIA_INDEX\n const studioRef = useRef<HTMLDivElement | null>(null)\n const navbarHeight = useStudioNavbarHeight(studioRef)\n\n // The machine publishes synchronously, which React does not animate. What\n // shows is deferred: that renders the panel's and the copy's mounts in a\n // transition — once the sidebar's own changes (the toggle's pressed state)\n // have committed, so nothing in the sidebar changes while it runs. Someone\n // who prefers reduced motion gets no view transition at all: nothing is\n // deferred, so there is no transition render for React to animate, no\n // boundary has a class, and what shows changes along with the machine —\n // and should one run anyway, the stylesheet gives its animations nothing\n // to do\n const reduceMotion = usePrefersReducedMotion()\n const deferredOpen = useDeferredValue(reduceMotion ? null : open)\n const deferredSplit = useDeferredValue(reduceMotion ? null : split)\n const shownOpen = deferredOpen ?? open\n const shownSplit = deferredSplit ?? split\n\n const {themes, removed, active} = useMemo(\n () => resolveThemes(stored, baseOptions),\n [stored, baseOptions],\n )\n\n // The theme identity must be stable between renders: it feeds the\n // styled-components theme context for the whole Studio, and rebuilding it\n // would re-render everything. Keying on the options object keeps it stable\n // across unrelated changes, like renaming a theme.\n const activeOptions = active.source === 'config' ? null : active.options\n const theme = useMemo(\n () => (activeOptions === null ? null : buildTheme(activeOptions)),\n [activeOptions],\n )\n\n // The applied theme is deferred the same way, and the Studio and the panel\n // cross-fade to it while the machine says a theme is being switched to —\n // edits to the applied theme's colors render deferred too, which keeps the\n // pickers responsive, but with nothing to animate them they simply show\n const deferredTheme = useDeferredValue(reduceMotion ? undefined : theme)\n const shownTheme = deferredTheme === undefined ? theme : deferredTheme\n\n // The Studio paints the body with its configured theme from above this\n // layout, and Safari tints its chrome from the body — so the applied theme\n // has to reach the body too, in the scheme the Studio is showing\n const scheme = useColorSchemeValue()\n const oppositeScheme: ThemeColorSchemeKey = scheme === 'dark' ? 'light' : 'dark'\n\n useEffect(() => {\n const background = shownTheme?.v2?.color[scheme].default.bg\n\n if (background === undefined) return undefined\n\n const {style} = document.body\n const previous = style.backgroundColor\n\n style.backgroundColor = background\n\n return () => {\n style.backgroundColor = previous\n }\n }, [scheme, shownTheme])\n\n // The machine releases the image of a palette as it is replaced or its\n // theme deleted; whatever it still holds goes with the layout — the actor\n // keeps its last snapshot once stopped\n useEffect(\n () => () => {\n for (const url of Object.values(actorRef.getSnapshot().context.images)) {\n URL.revokeObjectURL(url)\n }\n },\n [actorRef],\n )\n\n const context = useMemo<ThemerContextValue>(\n () => ({\n baseOptions,\n themes,\n removed,\n active,\n images,\n view,\n open,\n split,\n mobile,\n navbarHeight,\n send,\n }),\n [active, baseOptions, images, mobile, navbarHeight, open, removed, send, split, themes, view],\n )\n\n const studio = layoutProps.renderDefault(layoutProps)\n\n // Every view transition of the layout tells the machine its motion is under way\n const transitioned = () => send({type: 'layout.transitioned'})\n\n // What the view transitions do this render, from what the machine says\n const classes = reduceMotion\n ? {\n copyEnter: 'none',\n copyExit: 'none',\n panelEnter: 'none',\n panelExit: 'none',\n update: 'none',\n studioUpdate: 'none',\n }\n : {\n copyEnter: mobile ? splitTransitionClasses.dropIn : splitTransitionClasses.slideIn,\n copyExit: mobile ? splitTransitionClasses.dropOut : splitTransitionClasses.slideOut,\n panelEnter: panelTransitionClasses.slideIn,\n panelExit: panelTransitionClasses.slideOut,\n // The copy and the panel cross-fade to another theme, in place\n update: switching ? splitTransitionClasses.crossfade : 'none',\n // The Studio too — unless it is giving way or taking room, which cross-fades as it goes\n studioUpdate: moving\n ? splitTransitionClasses.resize\n : switching\n ? splitTransitionClasses.crossfade\n : 'none',\n }\n\n return (\n <ThemerContext.Provider value={context}>\n <Flex className={layout} direction={mobile ? 'column' : 'row'} height=\"fill\" sizing=\"border\">\n {/* The opposite scheme comes first — on the far side of the sidebar,\n or on top on small screens — so the Studio the user was looking at\n stays where it is, mounted, in its own scheme. A whole second\n Studio is too costly to keep around (its styled-components alone\n insert CSS as they render), so it only exists while the sidebar is\n open — hidden until the split shows, warmed up for the transition */}\n {shownOpen && (\n <Activity mode={shownSplit ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"opposite\"\n enter={classes.copyEnter}\n exit={classes.copyExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <StudioPreview\n borderBottom={mobile}\n borderRight={!mobile}\n scheme={oppositeScheme}\n theme={shownTheme}\n >\n {studio}\n </StudioPreview>\n </ViewTransition>\n </Activity>\n )}\n {/* The Studio gives way and takes room while the machine says the\n layout is moving, and cross-fades while it says a theme is being\n switched to — the Studio updates in transitions of its own all the\n time, and none of those may animate it. Once a transition is under\n way the machine hears of it, and the Studio is its own again before\n the next commit */}\n <ViewTransition key=\"primary\" onUpdate={transitioned} update={classes.studioUpdate}>\n <StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>\n </ViewTransition>\n\n {/* The sidebar is small and cheap: it stays mounted, hidden while\n closed, keeping its state and ready to show */}\n <Activity mode={shownOpen ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n <ResizableSidebar overlay={mobile} />\n </ThemeProvider>\n </ViewTransition>\n </Activity>\n </Flex>\n </ThemerContext.Provider>\n )\n}\n\n/**\n * One copy of the Studio, in the given color scheme — or, without one, in the\n * scheme the Studio is showing. The configured theme (`null`) goes through\n * the provider too, inheriting the Studio's, so picking a theme swaps it\n * instead of remounting the Studio under a new provider.\n *\n * The two copies of the split preview share the router, the document store\n * and every other provider above the layout — only the scheme differs — so\n * they stay in sync while navigating. The `color-scheme` of a forced scheme\n * keeps native form controls and scrollbars in step with it.\n */\nfunction StudioPreview(props: {\n borderBottom?: boolean\n borderRight?: boolean\n children: React.ReactNode\n ref?: React.Ref<HTMLDivElement>\n scheme?: ThemeColorSchemeKey\n theme: RootTheme | null\n}) {\n const {borderBottom, borderRight, children, ref, scheme, theme} = props\n\n return (\n <ThemeProvider scheme={scheme} theme={theme ?? undefined}>\n <Card\n borderBottom={borderBottom}\n borderRight={borderRight}\n className={scheme ? studioScheme[scheme] : undefined}\n flex={1}\n height=\"fill\"\n overflow=\"hidden\"\n ref={ref}\n >\n {children}\n </Card>\n </ThemeProvider>\n )\n}\n","import {ColorWheelIcon} from '@sanity/icons/ColorWheel'\nimport {Button, Text} from '@sanity/ui'\nimport {Tooltip} from '@sanity/ui/tooltip'\nimport {type NavbarProps} from 'sanity'\n\nimport {useThemer} from './context'\n\nfunction ThemerNavbarButton() {\n const {open, send} = useThemer()\n\n return (\n <Tooltip animate content={<Text size={1}>Themer</Text>} portal>\n <Button\n aria-label=\"Themer\"\n icon={ColorWheelIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.toggle'})}\n // The Studio's own navbar buttons go through a wrapper that pins them\n // to this padding, where `@sanity/ui` defaults to a roomier 3\n padding={2}\n selected={open}\n />\n </Tooltip>\n )\n}\n\n/**\n * Adds the toggle that opens and closes the themer sidebar to the Studio\n * navbar — an icon button with a tooltip in the top bar (like the Tasks\n * toggle), and a regular titled action in the narrow-screen sidebar menu.\n *\n * In the split preview every Studio copy renders its own navbar, so the\n * toggle shows up in both — they drive the same sidebar.\n *\n * @internal\n */\nexport function ThemerNavbar(props: NavbarProps) {\n const {open, send} = useThemer()\n\n return props.renderDefault({\n ...props,\n __internal_actions: [\n ...(props.__internal_actions ?? []),\n {\n location: 'topbar',\n name: 'themer-topbar',\n render: () => <ThemerNavbarButton />,\n },\n {\n icon: ColorWheelIcon,\n location: 'sidebar',\n name: 'themer-sidebar',\n onAction: () => send({type: 'sidebar.toggle'}),\n selected: open,\n title: 'Themer',\n },\n ],\n })\n}\n","import {definePlugin, type LayoutProps} from 'sanity'\n\nimport {BuildThemeOptions} from '../theme/options'\nimport {ThemerLayout} from './ThemerLayout'\nimport {ThemerNavbar} from './ThemerNavbar'\n\n/**\n * Options for the {@link themerTool} plugin.\n *\n * This is experimental and may change or be removed in any release without\n * notice — use at your own risk.\n *\n * @alpha\n */\nexport interface ThemerToolOptions {\n /**\n * The `buildTheme` options that the Studio's configured theme was generated\n * from — the themer starts editing from these, so pass the same object that\n * the `theme` in the Studio config uses:\n *\n * ```ts\n * const config: BuildThemeOptions = {light: {accent: '#1cb485'}, dark: {accent: '#22fca8'}}\n *\n * export default defineConfig({\n * theme: buildTheme(config),\n * plugins: [themerTool({config})],\n * })\n * ```\n */\n config?: BuildThemeOptions\n}\n\n/**\n * A Studio plugin that adds a themer sidebar for `buildTheme` themes: a\n * navbar toggle opens the sidebar next to the Studio, with a list of themes —\n * the configured theme, the presets and your own — each previewed as a tiny\n * Studio in both color schemes. Picking one applies it live to the whole\n * Studio while you browse around; your own themes can be edited with\n * accent/text/background pickers and a contrast slider per scheme, added,\n * duplicated, given the colors of an image (its palette is read on device),\n * removed and restored, and a dialog shows the `buildTheme` snippet that\n * makes the applied theme permanent. Toggle between light and dark mode with\n * the regular appearance menu — the preview follows it — or split the\n * preview to see the whole Studio in light and dark side by side.\n *\n * ```ts\n * import {themerTool} from '@sanity/themer/tool'\n * import {defineConfig} from 'sanity'\n *\n * export default defineConfig({\n * plugins: [themerTool()],\n * // ...rest of the config\n * })\n * ```\n *\n * This is experimental and may change or be removed in any release without\n * notice — use at your own risk.\n *\n * @alpha\n */\nexport const themerTool = definePlugin<ThemerToolOptions | void>((options) => {\n // No options generate the stock theme, which is what a Studio without a\n // `theme` in its config gets\n const baseOptions = options?.config ?? {}\n\n function ThemerLayoutWithOptions(props: LayoutProps) {\n return <ThemerLayout {...props} baseOptions={baseOptions} />\n }\n\n return {\n name: '@sanity/themer/tool',\n studio: {\n components: {\n layout: ThemerLayoutWithOptions,\n navbar: ThemerNavbar,\n },\n },\n }\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tool.js","names":["root","() => lastGutter","() => {\n const element = ref.current\n\n if (!element) return undefined\n\n // The reserved gutter is the part of the box the content cannot use\n const measure = () => {\n lastGutter = element.offsetWidth - element.clientWidth\n setGutter(lastGutter)\n }\n\n measure()\n\n const observer = new ResizeObserver(measure)\n\n observer.observe(element)\n\n return () => observer.disconnect()\n }","[]","{\n left: space[padding],\n right: Math.max(0, space[padding] - gutter),\n bottom: space[padding],\n }","space[padding]","Math.max(0, space[padding] - gutter)","useMemo<ScrollAreaInset>(\n () => ({\n left: space[padding],\n right: Math.max(0, space[padding] - gutter),\n bottom: space[padding],\n }),\n [gutter, padding, space],\n )","{paddingRight: inset.right}","right","<ScrollAreaInsetContext.Provider value={inset}>{children}</ScrollAreaInsetContext.Provider>","ScrollAreaInsetContext.Provider","<Box\n className={root}\n flex={1}\n overflow=\"auto\"\n padding={padding}\n ref={ref}\n style={{paddingRight: inset.right}}\n >\n <ScrollAreaInsetContext.Provider value={inset}>{children}</ScrollAreaInsetContext.Provider>\n </Box>","{marginLeft: -left, marginRight: -right, marginBottom: -bottom}","-left","-right","-bottom","<div style={{marginLeft: -left, marginRight: -right, marginBottom: -bottom}}>\n {props.children}\n </div>","children","_c","_temp","root","root","buildTheme(options)","useMemo(() => buildTheme(options), [options])","<MockStudio scheme=\"light\" />","<ThemeProvider scheme=\"light\" theme={theme} tone=\"default\">\n <MockStudio scheme=\"light\" />\n </ThemeProvider>","<MockStudio scheme=\"dark\" />","<ThemeProvider scheme=\"dark\" theme={theme} tone=\"default\">\n <MockStudio scheme=\"dark\" />\n </ThemeProvider>","<div aria-hidden className={root}>\n <ThemeProvider scheme=\"light\" theme={theme} tone=\"default\">\n <MockStudio scheme=\"light\" />\n </ThemeProvider>\n <ThemeProvider scheme=\"dark\" theme={theme} tone=\"default\">\n <MockStudio scheme=\"dark\" />\n </ThemeProvider>\n </div>","assignInlineVars({\n [colors.bg]: color.bg,\n [colors.fg]: color.fg,\n [colors.mutedFg]: color.muted.fg,\n [colors.border]: color.border,\n [colors.accent]: color.button.default.primary.enabled.bg,\n [colors.avatar]: color.avatar.blue.bg,\n [colors.selectedBg]: selected.bg,\n [colors.selectedFg]: selected.fg,\n [colors.inputBg]: inputColor.bg,\n [colors.inputBorder]: inputColor.border,\n })","bg","border","fg","<div className={logo} />","<div className={navbar}>\n <div className={logo} />\n <div className={line.fg} style={{width: '22%'}} />\n <div className={spacer} />\n <div className={avatar} />\n </div>","<div className={list}>\n {LIST_ROWS.map((width, index) => {\n const isSelected = index === 1\n\n return (\n <div className={listRow} data-selected={isSelected} key={width}>\n <div className={isSelected ? line.selected : line.muted} style={{width}} />\n </div>\n )\n })}\n </div>","(width, index) => {\n const isSelected = index === 1\n\n return (\n <div className={listRow} data-selected={isSelected} key={width}>\n <div className={isSelected ? line.selected : line.muted} style={{width}} />\n </div>\n )\n }","<div className={form}>\n <div className={line.fg} style={{width: '45%'}} />\n {FIELDS.map((width) => (\n <div className={field} key={width}>\n <div className={line.muted} style={{width}} />\n <div className={input} />\n </div>\n ))}\n </div>","(width) => (\n <div className={field} key={width}>\n <div className={line.muted} style={{width}} />\n <div className={input} />\n </div>\n )","<div className={body}>\n <div className={list}>\n {LIST_ROWS.map((width, index) => {\n const isSelected = index === 1\n\n return (\n <div className={listRow} data-selected={isSelected} key={width}>\n <div className={isSelected ? line.selected : line.muted} style={{width}} />\n </div>\n )\n })}\n </div>\n\n <div className={pane}>\n <div className={form}>\n <div className={line.fg} style={{width: '45%'}} />\n {FIELDS.map((width) => (\n <div className={field} key={width}>\n <div className={line.muted} style={{width}} />\n <div className={input} />\n </div>\n ))}\n </div>\n <div className={footer}>\n <div className={button} />\n </div>\n </div>\n </div>","<div\n className={studio[scheme]}\n style={assignInlineVars({\n [colors.bg]: color.bg,\n [colors.fg]: color.fg,\n [colors.mutedFg]: color.muted.fg,\n [colors.border]: color.border,\n [colors.accent]: color.button.default.primary.enabled.bg,\n [colors.avatar]: color.avatar.blue.bg,\n [colors.selectedBg]: selected.bg,\n [colors.selectedFg]: selected.fg,\n [colors.inputBg]: inputColor.bg,\n [colors.inputBorder]: inputColor.border,\n })}\n >\n <div className={navbar}>\n <div className={logo} />\n <div className={line.fg} style={{width: '22%'}} />\n <div className={spacer} />\n <div className={avatar} />\n </div>\n\n <div className={body}>\n <div className={list}>\n {LIST_ROWS.map((width, index) => {\n const isSelected = index === 1\n\n return (\n <div className={listRow} data-selected={isSelected} key={width}>\n <div className={isSelected ? line.selected : line.muted} style={{width}} />\n </div>\n )\n })}\n </div>\n\n <div className={pane}>\n <div className={form}>\n <div className={line.fg} style={{width: '45%'}} />\n {FIELDS.map((width) => (\n <div className={field} key={width}>\n <div className={line.muted} style={{width}} />\n <div className={input} />\n </div>\n ))}\n </div>\n <div className={footer}>\n <div className={button} />\n </div>\n </div>\n </div>\n </div>","studio[scheme]","width","FIELDS","_c","root","_temp","<Card overflow=\"hidden\" radius={3} shadow={1}>\n <ThemePreview options={props.options} />\n </Card>","options","_c","removed.map((theme) => (\n <Stack gap={3} key={theme.slug}>\n <Box className={removedThumbnail}>\n <ThemeThumbnail options={theme.options} />\n </Box>\n <Text align=\"center\" size={1} textOverflow=\"ellipsis\">\n {displayTitle(theme.title)}\n </Text>\n <Flex gap={2} justify=\"center\">\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.restore', slug: theme.slug})}\n padding={2}\n text=\"Restore\"\n />\n {theme.source === 'custom' && (\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.delete', slug: theme.slug})}\n padding={2}\n text=\"Delete\"\n tone=\"critical\"\n />\n )}\n </Flex>\n </Stack>\n ))","(theme) => (\n <Stack gap={3} key={theme.slug}>\n <Box className={removedThumbnail}>\n <ThemeThumbnail options={theme.options} />\n </Box>\n <Text align=\"center\" size={1} textOverflow=\"ellipsis\">\n {displayTitle(theme.title)}\n </Text>\n <Flex gap={2} justify=\"center\">\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.restore', slug: theme.slug})}\n padding={2}\n text=\"Restore\"\n />\n {theme.source === 'custom' && (\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.delete', slug: theme.slug})}\n padding={2}\n text=\"Delete\"\n tone=\"critical\"\n />\n )}\n </Flex>\n </Stack>\n )","<ScrollArea padding={3}>\n <Stack gap={5}>\n {removed.map((theme) => (\n <Stack gap={3} key={theme.slug}>\n <Box className={removedThumbnail}>\n <ThemeThumbnail options={theme.options} />\n </Box>\n <Text align=\"center\" size={1} textOverflow=\"ellipsis\">\n {displayTitle(theme.title)}\n </Text>\n <Flex gap={2} justify=\"center\">\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.restore', slug: theme.slug})}\n padding={2}\n text=\"Restore\"\n />\n {theme.source === 'custom' && (\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.delete', slug: theme.slug})}\n padding={2}\n text=\"Delete\"\n tone=\"critical\"\n />\n )}\n </Flex>\n </Stack>\n ))}\n </Stack>\n </ScrollArea>","_c","<Text size={1}>{tooltip}</Text>","<Button\n aria-label={buttonProps.text === undefined ? tooltip : undefined}\n gap={2}\n padding={2}\n {...buttonProps}\n />","buttonProps.text === undefined ? tooltip : undefined","<Tooltip animate content={<Text size={1}>{tooltip}</Text>} placement=\"bottom\" portal>\n <Button\n aria-label={buttonProps.text === undefined ? tooltip : undefined}\n gap={2}\n padding={2}\n {...buttonProps}\n />\n </Tooltip>","_c","() => inputRef.current?.click()","<TooltipButton {...buttonProps} icon={ImageIcon} onClick={() => inputRef.current?.click()} />","<input\n accept=\"image/*\"\n hidden\n onChange={(event) => {\n const file = event.currentTarget.files?.[0]\n\n // Picking the same file again should work, so the input forgets it\n event.currentTarget.value = ''\n\n if (file) onFile(file)\n }}\n ref={inputRef}\n type=\"file\"\n />","<>\n <TooltipButton {...buttonProps} icon={ImageIcon} onClick={() => inputRef.current?.click()} />\n <input\n accept=\"image/*\"\n hidden\n onChange={(event) => {\n const file = event.currentTarget.files?.[0]\n\n // Picking the same file again should work, so the input forgets it\n event.currentTarget.value = ''\n\n if (file) onFile(file)\n }}\n ref={inputRef}\n type=\"file\"\n />\n </>","_c","_Fragment","() => {\n onPaletteRef.current = onPalette\n }","[onPalette]","() => {\n mountedRef.current = true\n\n return () => {\n mountedRef.current = false\n }\n }","[]","(file: File) => {\n const pick = ++latestPickRef.current\n\n setBusy(true)\n\n // Whether this pick still matters: it is the latest one and the\n // component that asked for it is still around\n const isCurrent = () => mountedRef.current && latestPickRef.current === pick\n\n const extract = async () => {\n const palette = extractImagePalette(await readImagePixels(file))\n\n if (!isCurrent()) return\n\n if (Object.values(palette).every((swatch) => swatch === null)) {\n toast.push({status: 'warning', title: 'Found no colors in that image'})\n } else {\n onPaletteRef.current(palette, file)\n }\n }\n\n extract()\n .catch(() => {\n if (isCurrent()) toast.push({status: 'error', title: 'Could not read that image'})\n })\n .finally(() => {\n if (isCurrent()) setBusy(false)\n })\n }","(swatch) => swatch === null","useCallback(\n (file: File) => {\n const pick = ++latestPickRef.current\n\n setBusy(true)\n\n // Whether this pick still matters: it is the latest one and the\n // component that asked for it is still around\n const isCurrent = () => mountedRef.current && latestPickRef.current === pick\n\n const extract = async () => {\n const palette = extractImagePalette(await readImagePixels(file))\n\n if (!isCurrent()) return\n\n if (Object.values(palette).every((swatch) => swatch === null)) {\n toast.push({status: 'warning', title: 'Found no colors in that image'})\n } else {\n onPaletteRef.current(palette, file)\n }\n }\n\n extract()\n .catch(() => {\n if (isCurrent()) toast.push({status: 'error', title: 'Could not read that image'})\n })\n .finally(() => {\n if (isCurrent()) setBusy(false)\n })\n },\n [toast],\n )","{busy, pickImage}","_c","_temp","palette ? currentImageVariant(options, palette) : null","() => {\n const row = rowRef.current\n\n if (!row) return undefined\n\n const measure = () => {\n const visible =\n row.clientWidth - ROW_PADDING - (Math.ceil(VISIBLE_VARIANTS) - 1) * VARIANT_GAP\n\n // Each variant is its thumbnail plus the frame on either side\n setThumbnailWidth(Math.max(24, Math.floor(visible / VISIBLE_VARIANTS) - VARIANT_FRAME * 2))\n }\n\n measure()\n\n const observer = new ResizeObserver(measure)\n\n observer.observe(row)\n\n return () => observer.disconnect()\n }","[palette]","useMemo(() => {\n if (!palette) return []\n\n return IMAGE_PALETTE_VARIANTS.flatMap((variant) =>\n palette[variant] === null\n ? []\n : [\n {\n variant,\n title: IMAGE_PALETTE_TITLES[variant],\n options: applyImagePalette(options, palette, variant),\n },\n ],\n )\n }, [options, palette])","[]","IMAGE_PALETTE_VARIANTS.flatMap((variant) =>\n palette[variant] === null\n ? []\n : [\n {\n variant,\n title: IMAGE_PALETTE_TITLES[variant],\n options: applyImagePalette(options, palette, variant),\n },\n ],\n )","(event) => {\n event.preventDefault()\n dragDepth.current++\n setDragging(true)\n }","() => {\n dragDepth.current = Math.max(0, dragDepth.current - 1)\n\n if (dragDepth.current === 0) setDragging(false)\n }","(event) => {\n event.preventDefault()\n dragDepth.current = 0\n setDragging(false)\n\n const file = event.dataTransfer.files[0]\n\n if (file) pickImage(file)\n }","event","<Box flex={1}>\n <Text size={1} weight=\"medium\">\n Image palette\n </Text>\n </Box>","palette && (\n <ImageFileButton\n loading={busy}\n mode=\"bleed\"\n onFile={pickImage}\n padding={2}\n tooltip=\"Pick another image\"\n />\n )","<Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n Image palette\n </Text>\n </Box>\n {palette && (\n <ImageFileButton\n loading={busy}\n mode=\"bleed\"\n onFile={pickImage}\n padding={2}\n tooltip=\"Pick another image\"\n />\n )}\n </Flex>","palette ? (\n <>\n <div className={paletteGrid} data-with-image={imageUrl !== undefined}>\n {imageUrl && <img alt=\"\" className={imageTile} src={imageUrl} />}\n {TILES.map((key) => {\n const hex = palette[key]\n const title = IMAGE_PALETTE_TITLES[key]\n\n return (\n <Tooltip\n animate\n content={\n <Text size={1}>\n {hex ? `${title} · ${hex}` : `No ${title.toLowerCase()} color`}\n </Text>\n }\n key={key}\n placement=\"bottom\"\n portal\n >\n <span\n className={tile}\n data-empty={hex === null}\n style={{background: hex ?? undefined}}\n />\n </Tooltip>\n )\n })}\n </div>\n\n <Stack gap={2}>\n <Text muted size={0}>\n Pick the swatch to build the theme around\n </Text>\n <div\n className={variantRow}\n ref={rowRef}\n style={assignInlineVars({\n [variantWidth]: `${thumbnailWidth}px`,\n [variantScale]: String(thumbnailWidth / VARIANT_WIDTH),\n })}\n >\n {variants.map(({variant, title, options: variantOptions}) => (\n // oxlint-disable-next-line control-has-associated-label -- the title below the thumbnail is the label, deeper than the rule looks\n <button\n aria-pressed={variant === current}\n className={variantButton}\n key={variant}\n onClick={() => onVariant(variant)}\n type=\"button\"\n >\n <span className={variantFrame}>\n <span className={variantScaleBox}>\n <ThemeThumbnail options={variantOptions} />\n </span>\n </span>\n <Box as=\"span\" display=\"block\" paddingTop={1}>\n <Text\n align=\"center\"\n as=\"span\"\n size={0}\n textOverflow=\"ellipsis\"\n weight={variant === current ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>\n </button>\n ))}\n </div>\n </Stack>\n\n <TooltipButton\n icon={SparklesIcon}\n mode=\"ghost\"\n onClick={onLucky}\n text=\"I'm feeling lucky\"\n tooltip=\"Build the theme around a swatch that looks interesting\"\n width=\"fill\"\n />\n </>\n ) : (\n <Stack gap={3}>\n <Text muted size={0}>\n Take the accent, text and background colors from an image — read on device, nothing is\n uploaded.\n </Text>\n <ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n text=\"Pick an image\"\n tooltip=\"Take the colors from an image on your device\"\n width=\"fill\"\n />\n </Stack>\n )","{variant, title, options: variantOptions}","variant","title","<Stack gap={3}>\n <Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n Image palette\n </Text>\n </Box>\n {palette && (\n <ImageFileButton\n loading={busy}\n mode=\"bleed\"\n onFile={pickImage}\n padding={2}\n tooltip=\"Pick another image\"\n />\n )}\n </Flex>\n\n {palette ? (\n <>\n <div className={paletteGrid} data-with-image={imageUrl !== undefined}>\n {imageUrl && <img alt=\"\" className={imageTile} src={imageUrl} />}\n {TILES.map((key) => {\n const hex = palette[key]\n const title = IMAGE_PALETTE_TITLES[key]\n\n return (\n <Tooltip\n animate\n content={\n <Text size={1}>\n {hex ? `${title} · ${hex}` : `No ${title.toLowerCase()} color`}\n </Text>\n }\n key={key}\n placement=\"bottom\"\n portal\n >\n <span\n className={tile}\n data-empty={hex === null}\n style={{background: hex ?? undefined}}\n />\n </Tooltip>\n )\n })}\n </div>\n\n <Stack gap={2}>\n <Text muted size={0}>\n Pick the swatch to build the theme around\n </Text>\n <div\n className={variantRow}\n ref={rowRef}\n style={assignInlineVars({\n [variantWidth]: `${thumbnailWidth}px`,\n [variantScale]: String(thumbnailWidth / VARIANT_WIDTH),\n })}\n >\n {variants.map(({variant, title, options: variantOptions}) => (\n // oxlint-disable-next-line control-has-associated-label -- the title below the thumbnail is the label, deeper than the rule looks\n <button\n aria-pressed={variant === current}\n className={variantButton}\n key={variant}\n onClick={() => onVariant(variant)}\n type=\"button\"\n >\n <span className={variantFrame}>\n <span className={variantScaleBox}>\n <ThemeThumbnail options={variantOptions} />\n </span>\n </span>\n <Box as=\"span\" display=\"block\" paddingTop={1}>\n <Text\n align=\"center\"\n as=\"span\"\n size={0}\n textOverflow=\"ellipsis\"\n weight={variant === current ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>\n </button>\n ))}\n </div>\n </Stack>\n\n <TooltipButton\n icon={SparklesIcon}\n mode=\"ghost\"\n onClick={onLucky}\n text=\"I'm feeling lucky\"\n tooltip=\"Build the theme around a swatch that looks interesting\"\n width=\"fill\"\n />\n </>\n ) : (\n <Stack gap={3}>\n <Text muted size={0}>\n Take the accent, text and background colors from an image — read on device, nothing is\n uploaded.\n </Text>\n <ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n text=\"Pick an image\"\n tooltip=\"Take the colors from an image on your device\"\n width=\"fill\"\n />\n </Stack>\n )}\n </Stack>","<Card\n border\n onDragEnter={(event) => {\n event.preventDefault()\n dragDepth.current++\n setDragging(true)\n }}\n onDragLeave={() => {\n dragDepth.current = Math.max(0, dragDepth.current - 1)\n\n if (dragDepth.current === 0) setDragging(false)\n }}\n onDragOver={(event) => {\n event.preventDefault()\n }}\n onDrop={(event) => {\n event.preventDefault()\n dragDepth.current = 0\n setDragging(false)\n\n const file = event.dataTransfer.files[0]\n\n if (file) pickImage(file)\n }}\n padding={3}\n radius={3}\n tone={dragging ? 'primary' : 'default'}\n >\n <Stack gap={3}>\n <Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n Image palette\n </Text>\n </Box>\n {palette && (\n <ImageFileButton\n loading={busy}\n mode=\"bleed\"\n onFile={pickImage}\n padding={2}\n tooltip=\"Pick another image\"\n />\n )}\n </Flex>\n\n {palette ? (\n <>\n <div className={paletteGrid} data-with-image={imageUrl !== undefined}>\n {imageUrl && <img alt=\"\" className={imageTile} src={imageUrl} />}\n {TILES.map((key) => {\n const hex = palette[key]\n const title = IMAGE_PALETTE_TITLES[key]\n\n return (\n <Tooltip\n animate\n content={\n <Text size={1}>\n {hex ? `${title} · ${hex}` : `No ${title.toLowerCase()} color`}\n </Text>\n }\n key={key}\n placement=\"bottom\"\n portal\n >\n <span\n className={tile}\n data-empty={hex === null}\n style={{background: hex ?? undefined}}\n />\n </Tooltip>\n )\n })}\n </div>\n\n <Stack gap={2}>\n <Text muted size={0}>\n Pick the swatch to build the theme around\n </Text>\n <div\n className={variantRow}\n ref={rowRef}\n style={assignInlineVars({\n [variantWidth]: `${thumbnailWidth}px`,\n [variantScale]: String(thumbnailWidth / VARIANT_WIDTH),\n })}\n >\n {variants.map(({variant, title, options: variantOptions}) => (\n // oxlint-disable-next-line control-has-associated-label -- the title below the thumbnail is the label, deeper than the rule looks\n <button\n aria-pressed={variant === current}\n className={variantButton}\n key={variant}\n onClick={() => onVariant(variant)}\n type=\"button\"\n >\n <span className={variantFrame}>\n <span className={variantScaleBox}>\n <ThemeThumbnail options={variantOptions} />\n </span>\n </span>\n <Box as=\"span\" display=\"block\" paddingTop={1}>\n <Text\n align=\"center\"\n as=\"span\"\n size={0}\n textOverflow=\"ellipsis\"\n weight={variant === current ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>\n </button>\n ))}\n </div>\n </Stack>\n\n <TooltipButton\n icon={SparklesIcon}\n mode=\"ghost\"\n onClick={onLucky}\n text=\"I'm feeling lucky\"\n tooltip=\"Build the theme around a swatch that looks interesting\"\n width=\"fill\"\n />\n </>\n ) : (\n <Stack gap={3}>\n <Text muted size={0}>\n Take the accent, text and background colors from an image — read on device, nothing is\n uploaded.\n </Text>\n <ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n text=\"Pick an image\"\n tooltip=\"Take the colors from an image on your device\"\n width=\"fill\"\n />\n </Stack>\n )}\n </Stack>\n </Card>","dragging ? 'primary' : 'default'","(event) => {\n event.preventDefault()\n }","_c","_Fragment","_temp","themes.find((candidate) => candidate.slug === slug && candidate.source === 'custom')","(candidate) => candidate.slug === slug && candidate.source === 'custom'","(variant: ImagePaletteVariant | null) => {\n if (!theme.palette || variant === null) return\n\n send({\n type: 'theme.update',\n slug,\n options: applyImagePalette(theme.options, theme.palette, variant),\n })\n }","options","palette","() => send({type: 'flow.list'})","() => {\n if (!theme.palette) return\n\n applyVariant(\n pickLuckyVariant(theme.palette, {\n exclude: currentImageVariant(theme.options, theme.palette),\n }),\n )\n }","(options) => send({type: 'theme.update', slug, options})","(palette, file) =>\n send({\n type: 'theme.update',\n slug,\n options: applyImagePalette(theme.options, palette),\n palette,\n imageUrl: URL.createObjectURL(file),\n })","() => send({type: 'theme.remove', slug})","(title) => send({type: 'theme.update', slug, title})","<ThemeEditorForm\n focusTitle={focusTitle}\n imageUrl={images[slug]}\n onDone={() => send({type: 'flow.list'})}\n onLucky={() => {\n if (!theme.palette) return\n\n applyVariant(\n pickLuckyVariant(theme.palette, {\n exclude: currentImageVariant(theme.options, theme.palette),\n }),\n )\n }}\n onOptionsChange={(options) => send({type: 'theme.update', slug, options})}\n onPalette={(palette, file) =>\n send({\n type: 'theme.update',\n slug,\n options: applyImagePalette(theme.options, palette),\n palette,\n imageUrl: URL.createObjectURL(file),\n })\n }\n onRemove={() => send({type: 'theme.remove', slug})}\n onTitleChange={(title) => send({type: 'theme.update', slug, title})}\n onVariant={applyVariant}\n options={theme.options}\n palette={theme.palette}\n split={split}\n mobile={mobile}\n title={theme.title}\n />","images[slug]","title","resolveThemeOptions(options)","useMemo(() => resolveThemeOptions(options), [options])","buildPalette(options)","useMemo(() => buildPalette(options), [options])","(scheme: ThemeColorSchemeKey, changes: Partial<SchemeThemeOptions>) => {\n const values: SchemeThemeOptions = {...options[scheme], ...changes}\n\n // Clearing an option means falling back to its default, which is its absence\n for (const key of SCHEME_OPTION_KEYS) {\n if (values[key] === undefined) delete values[key]\n }\n\n const next: BuildThemeOptions = {...options}\n\n if (Object.keys(values).length === 0) {\n delete next[scheme]\n } else {\n next[scheme] = values\n }\n\n onOptionsChange(next)\n }","<ThemeThumbnail options={options} />","<Text size={1} weight=\"medium\">\n Title\n </Text>","(event) => onTitleChange(event.currentTarget.value)","(event) => event.currentTarget.select()","<Stack gap={2}>\n <Text size={1} weight=\"medium\">\n Title\n </Text>\n <TextInput\n autoFocus={focusTitle}\n fontSize={1}\n onChange={(event) => onTitleChange(event.currentTarget.value)}\n onFocus={focusTitle ? (event) => event.currentTarget.select() : undefined}\n padding={2}\n placeholder=\"Untitled theme\"\n value={title}\n />\n </Stack>","focusTitle ? (event) => event.currentTarget.select() : undefined","<ImagePaletteSection\n imageUrl={imageUrl}\n onLucky={onLucky}\n onPalette={onPalette}\n onVariant={onVariant}\n options={options}\n palette={palette}\n />","SCHEMES.map((scheme) => (\n <SchemeCard\n active={split || scheme === studioScheme}\n key={scheme}\n onChange={(changes) => patchScheme(scheme, changes)}\n options={options[scheme] ?? {}}\n palette={palettes[scheme]}\n resolved={resolved[scheme]}\n scheme={scheme}\n split={split}\n splitHint={splitHint(scheme === studioScheme, mobile)}\n />\n ))","scheme","changes","<ScrollAreaBleed>\n {SCHEMES.map((scheme) => (\n <SchemeCard\n active={split || scheme === studioScheme}\n key={scheme}\n onChange={(changes) => patchScheme(scheme, changes)}\n options={options[scheme] ?? {}}\n palette={palettes[scheme]}\n resolved={resolved[scheme]}\n scheme={scheme}\n split={split}\n splitHint={splitHint(scheme === studioScheme, mobile)}\n />\n ))}\n </ScrollAreaBleed>","<ScrollArea padding={3}>\n <Stack gap={4}>\n <ThemeThumbnail options={options} />\n\n <Stack gap={2}>\n <Text size={1} weight=\"medium\">\n Title\n </Text>\n <TextInput\n autoFocus={focusTitle}\n fontSize={1}\n onChange={(event) => onTitleChange(event.currentTarget.value)}\n onFocus={focusTitle ? (event) => event.currentTarget.select() : undefined}\n padding={2}\n placeholder=\"Untitled theme\"\n value={title}\n />\n </Stack>\n\n <ImagePaletteSection\n imageUrl={imageUrl}\n onLucky={onLucky}\n onPalette={onPalette}\n onVariant={onVariant}\n options={options}\n palette={palette}\n />\n\n <ScrollAreaBleed>\n {SCHEMES.map((scheme) => (\n <SchemeCard\n active={split || scheme === studioScheme}\n key={scheme}\n onChange={(changes) => patchScheme(scheme, changes)}\n options={options[scheme] ?? {}}\n palette={palettes[scheme]}\n resolved={resolved[scheme]}\n scheme={scheme}\n split={split}\n splitHint={splitHint(scheme === studioScheme, mobile)}\n />\n ))}\n </ScrollAreaBleed>\n </Stack>\n </ScrollArea>","<TooltipButton\n icon={TrashIcon}\n mode=\"ghost\"\n onClick={onRemove}\n text=\"Remove\"\n tone=\"critical\"\n tooltip=\"Remove the theme — it can be restored until it is deleted\"\n />","<Box flex={1} />","<Button mode=\"ghost\" onClick={onDone} padding={2} text=\"Done\" />","<Card borderTop padding={3}>\n <Flex gap={2}>\n <TooltipButton\n icon={TrashIcon}\n mode=\"ghost\"\n onClick={onRemove}\n text=\"Remove\"\n tone=\"critical\"\n tooltip=\"Remove the theme — it can be restored until it is deleted\"\n />\n <Box flex={1} />\n <Button mode=\"ghost\" onClick={onDone} padding={2} text=\"Done\" />\n </Flex>\n </Card>","<>\n <ScrollArea padding={3}>\n <Stack gap={4}>\n <ThemeThumbnail options={options} />\n\n <Stack gap={2}>\n <Text size={1} weight=\"medium\">\n Title\n </Text>\n <TextInput\n autoFocus={focusTitle}\n fontSize={1}\n onChange={(event) => onTitleChange(event.currentTarget.value)}\n onFocus={focusTitle ? (event) => event.currentTarget.select() : undefined}\n padding={2}\n placeholder=\"Untitled theme\"\n value={title}\n />\n </Stack>\n\n <ImagePaletteSection\n imageUrl={imageUrl}\n onLucky={onLucky}\n onPalette={onPalette}\n onVariant={onVariant}\n options={options}\n palette={palette}\n />\n\n <ScrollAreaBleed>\n {SCHEMES.map((scheme) => (\n <SchemeCard\n active={split || scheme === studioScheme}\n key={scheme}\n onChange={(changes) => patchScheme(scheme, changes)}\n options={options[scheme] ?? {}}\n palette={palettes[scheme]}\n resolved={resolved[scheme]}\n scheme={scheme}\n split={split}\n splitHint={splitHint(scheme === studioScheme, mobile)}\n />\n ))}\n </ScrollAreaBleed>\n </Stack>\n </ScrollArea>\n\n <Card borderTop padding={3}>\n <Flex gap={2}>\n <TooltipButton\n icon={TrashIcon}\n mode=\"ghost\"\n onClick={onRemove}\n text=\"Remove\"\n tone=\"critical\"\n tooltip=\"Remove the theme — it can be restored until it is deleted\"\n />\n <Box flex={1} />\n <Button mode=\"ghost\" onClick={onDone} padding={2} text=\"Done\" />\n </Flex>\n </Card>\n </>","event","<Box flex={1}>\n <Text size={1} weight=\"medium\">\n {name}\n </Text>\n </Box>","active && (\n <Badge fontSize={0} tone=\"primary\">\n Active\n </Badge>\n )","<Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n {name}\n </Text>\n </Box>\n {active && (\n <Badge fontSize={0} tone=\"primary\">\n Active\n </Badge>\n )}\n </Flex>","<Text muted size={0}>\n {split\n ? splitHint\n : active\n ? 'Shown in the Studio right now'\n : `Shown once the Studio appearance is ${scheme}`}\n </Text>","split\n ? splitHint\n : active\n ? 'Shown in the Studio right now'\n : `Shown once the Studio appearance is ${scheme}`","<Stack gap={2}>\n <Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n {name}\n </Text>\n </Box>\n {active && (\n <Badge fontSize={0} tone=\"primary\">\n Active\n </Badge>\n )}\n </Flex>\n <Text muted size={0}>\n {split\n ? splitHint\n : active\n ? 'Shown in the Studio right now'\n : `Shown once the Studio appearance is ${scheme}`}\n </Text>\n </Stack>","(accent) => onChange({accent})","() => onChange({accent: undefined})","<ColorRow\n auto={options.accent === undefined}\n autoLabel=\"default\"\n label={`${name} accent`}\n onChange={(accent) => onChange({accent})}\n onClear={() => onChange({accent: undefined})}\n title=\"Accent\"\n value={resolved.accent}\n />","accent","options.accent === undefined","`${name} accent`","(text) => onChange({text})","() => onChange({text: undefined})","<ColorRow\n auto={options.text === undefined}\n label={`${name} text`}\n onChange={(text) => onChange({text})}\n onClear={() => onChange({text: undefined})}\n title=\"Text\"\n value={resolved.text}\n />","text","options.text === undefined","`${name} text`","(background) => onChange({background})","() => onChange({background: undefined})","<ColorRow\n adjusted={scheme === 'dark' ? palette.black : palette.white}\n auto={options.background === undefined}\n label={`${name} background`}\n onChange={(background) => onChange({background})}\n onClear={() => onChange({background: undefined})}\n title=\"Background\"\n value={resolved.background}\n />","background","scheme === 'dark' ? palette.black : palette.white","options.background === undefined","`${name} background`","<Text size={1}>Contrast</Text>","<Stack flex={1} gap={2}>\n <Text size={1}>Contrast</Text>\n <Text muted size={0}>\n {resolved.contrast}\n {options.contrast === undefined ? ' · auto' : ''}\n </Text>\n </Stack>","contrast","options.contrast === undefined ? ' · auto' : ''","options.contrast !== undefined && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={() => onChange({contrast: undefined})}\n padding={2}\n tooltip=\"Reset to auto\"\n />\n )","<Flex align=\"center\" gap={2}>\n <Stack flex={1} gap={2}>\n <Text size={1}>Contrast</Text>\n <Text muted size={0}>\n {resolved.contrast}\n {options.contrast === undefined ? ' · auto' : ''}\n </Text>\n </Stack>\n {options.contrast !== undefined && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={() => onChange({contrast: undefined})}\n padding={2}\n tooltip=\"Reset to auto\"\n />\n )}\n </Flex>","(event) => onChange({contrast: Number(event.currentTarget.value)})","<input\n aria-label={`${name} contrast`}\n className={range}\n max={100}\n min={15}\n onChange={(event) => onChange({contrast: Number(event.currentTarget.value)})}\n step={1}\n type=\"range\"\n value={resolved.contrast}\n />","`${name} contrast`","<Text muted size={0}>\n 100 keeps text and borders neutral — lower values blend in the accent\n </Text>","<Stack gap={3}>\n <Flex align=\"center\" gap={2}>\n <Stack flex={1} gap={2}>\n <Text size={1}>Contrast</Text>\n <Text muted size={0}>\n {resolved.contrast}\n {options.contrast === undefined ? ' · auto' : ''}\n </Text>\n </Stack>\n {options.contrast !== undefined && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={() => onChange({contrast: undefined})}\n padding={2}\n tooltip=\"Reset to auto\"\n />\n )}\n </Flex>\n <input\n aria-label={`${name} contrast`}\n className={range}\n max={100}\n min={15}\n onChange={(event) => onChange({contrast: Number(event.currentTarget.value)})}\n step={1}\n type=\"range\"\n value={resolved.contrast}\n />\n <Text muted size={0}>\n 100 keeps text and borders neutral — lower values blend in the accent\n </Text>\n </Stack>","<Card borderTop padding={3}>\n <Stack gap={4}>\n <Stack gap={2}>\n <Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n {name}\n </Text>\n </Box>\n {active && (\n <Badge fontSize={0} tone=\"primary\">\n Active\n </Badge>\n )}\n </Flex>\n <Text muted size={0}>\n {split\n ? splitHint\n : active\n ? 'Shown in the Studio right now'\n : `Shown once the Studio appearance is ${scheme}`}\n </Text>\n </Stack>\n\n <ColorRow\n auto={options.accent === undefined}\n autoLabel=\"default\"\n label={`${name} accent`}\n onChange={(accent) => onChange({accent})}\n onClear={() => onChange({accent: undefined})}\n title=\"Accent\"\n value={resolved.accent}\n />\n <ColorRow\n auto={options.text === undefined}\n label={`${name} text`}\n onChange={(text) => onChange({text})}\n onClear={() => onChange({text: undefined})}\n title=\"Text\"\n value={resolved.text}\n />\n <ColorRow\n adjusted={scheme === 'dark' ? palette.black : palette.white}\n auto={options.background === undefined}\n label={`${name} background`}\n onChange={(background) => onChange({background})}\n onClear={() => onChange({background: undefined})}\n title=\"Background\"\n value={resolved.background}\n />\n\n <Stack gap={3}>\n <Flex align=\"center\" gap={2}>\n <Stack flex={1} gap={2}>\n <Text size={1}>Contrast</Text>\n <Text muted size={0}>\n {resolved.contrast}\n {options.contrast === undefined ? ' · auto' : ''}\n </Text>\n </Stack>\n {options.contrast !== undefined && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={() => onChange({contrast: undefined})}\n padding={2}\n tooltip=\"Reset to auto\"\n />\n )}\n </Flex>\n <input\n aria-label={`${name} contrast`}\n className={range}\n max={100}\n min={15}\n onChange={(event) => onChange({contrast: Number(event.currentTarget.value)})}\n step={1}\n type=\"range\"\n value={resolved.contrast}\n />\n <Text muted size={0}>\n 100 keeps text and borders neutral — lower values blend in the accent\n </Text>\n </Stack>\n </Stack>\n </Card>","autoLabel = 'auto'","(event) => onChange(event.currentTarget.value)","<input\n aria-label={label}\n className={swatch}\n onChange={(event) => onChange(event.currentTarget.value)}\n type=\"color\"\n value={value}\n />","<Text size={1}>{title}</Text>","<Text muted size={0} textOverflow=\"ellipsis\">\n {value}\n {adjusted !== undefined && adjusted !== value ? ` → ${adjusted}` : ''}\n {auto ? ` · ${autoLabel}` : ''}\n </Text>","adjusted !== undefined && adjusted !== value ? ` → ${adjusted}` : ''","auto ? ` · ${autoLabel}` : ''","<Stack className={colorRowText} flex={1} gap={2}>\n <Text size={1}>{title}</Text>\n <Text muted size={0} textOverflow=\"ellipsis\">\n {value}\n {adjusted !== undefined && adjusted !== value ? ` → ${adjusted}` : ''}\n {auto ? ` · ${autoLabel}` : ''}\n </Text>\n </Stack>","onClear && !auto && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={onClear}\n padding={2}\n tooltip={`Reset to ${autoLabel}`}\n />\n )","<Flex align=\"center\" gap={2}>\n <input\n aria-label={label}\n className={swatch}\n onChange={(event) => onChange(event.currentTarget.value)}\n type=\"color\"\n value={value}\n />\n <Stack className={colorRowText} flex={1} gap={2}>\n <Text size={1}>{title}</Text>\n <Text muted size={0} textOverflow=\"ellipsis\">\n {value}\n {adjusted !== undefined && adjusted !== value ? ` → ${adjusted}` : ''}\n {auto ? ` · ${autoLabel}` : ''}\n </Text>\n </Stack>\n {onClear && !auto && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={onClear}\n padding={2}\n tooltip={`Reset to ${autoLabel}`}\n />\n )}\n </Flex>","_c","_temp","_Fragment","async (theme: SharedTheme) => {\n const title = displayTitle(theme.title)\n\n try {\n await navigator.clipboard.writeText(encodeTheme(theme))\n toast.push({\n status: 'success',\n title: `Copied ${title} as a code`,\n description: 'Paste it in the themer of another Studio to add the theme there',\n })\n } catch {\n toast.push({status: 'error', title: `Could not copy ${title}`})\n }\n }","(text: string) => {\n const theme = decodeTheme(text)\n\n if (!theme) return false\n\n send({type: 'theme.import', title: theme.title, options: theme.options})\n toast.push({status: 'success', title: `Added ${theme.title}`})\n\n return true\n }","theme","async () => {\n let text: string\n\n try {\n text = await navigator.clipboard.readText()\n } catch {\n return 'no-clipboard'\n }\n\n return addThemeFromText(text) ? 'added' : 'no-theme'\n }","text","{copyTheme, addThemeFromText, addThemeFromClipboard}","() => {\n if (!open) return undefined\n\n const handlePaste = (event: ClipboardEvent) => {\n if (isEditable(event.target)) return\n\n const theme = decodeTheme(event.clipboardData?.getData('text/plain') ?? '')\n\n if (!theme) return\n\n event.preventDefault()\n send({type: 'theme.import', title: theme.title, options: theme.options})\n toast.push({status: 'success', title: `Added ${theme.title}`})\n }\n\n document.addEventListener('paste', handlePaste)\n\n return () => document.removeEventListener('paste', handlePaste)\n }","[open, send, toast]","_c","() => {\n if (addThemeFromText(text)) {\n onClose()\n } else {\n setInvalid(true)\n }\n }","<Button mode=\"ghost\" onClick={onClose} padding={2} text=\"Cancel\" />","text.trim()","<Button\n disabled={text.trim() === ''}\n onClick={add}\n padding={2}\n text=\"Add theme\"\n tone=\"primary\"\n />","text.trim() === ''","<Flex gap={2} justify=\"flex-end\" padding={3}>\n <Button mode=\"ghost\" onClick={onClose} padding={2} text=\"Cancel\" />\n <Button\n disabled={text.trim() === ''}\n onClick={add}\n padding={2}\n text=\"Add theme\"\n tone=\"primary\"\n />\n </Flex>","(event) => {\n event.preventDefault()\n add()\n }","<Text muted size={1}>\n Paste the code someone copied from their themer — the theme is added to your list and\n applied.\n </Text>","(event) => {\n setText(event.currentTarget.value)\n setInvalid(false)\n }","event","<TextInput\n aria-describedby={invalid ? 'themer-paste-error' : undefined}\n aria-invalid={invalid}\n aria-label=\"Theme code\"\n autoFocus\n customValidity={invalid ? 'That is not a theme code' : undefined}\n fontSize={1}\n onChange={(event) => {\n setText(event.currentTarget.value)\n setInvalid(false)\n }}\n placeholder=\"AQ…\"\n value={text}\n />","invalid ? 'themer-paste-error' : undefined","invalid ? 'That is not a theme code' : undefined","invalid && (\n <Card id=\"themer-paste-error\" padding={3} radius={2} role=\"alert\" tone=\"critical\">\n <Text size={1}>\n That is not a theme code — codes are one word, copied from a theme's menu.\n </Text>\n </Card>\n )","<Stack gap={3} padding={4}>\n <Text muted size={1}>\n Paste the code someone copied from their themer — the theme is added to your list and\n applied.\n </Text>\n <TextInput\n aria-describedby={invalid ? 'themer-paste-error' : undefined}\n aria-invalid={invalid}\n aria-label=\"Theme code\"\n autoFocus\n customValidity={invalid ? 'That is not a theme code' : undefined}\n fontSize={1}\n onChange={(event) => {\n setText(event.currentTarget.value)\n setInvalid(false)\n }}\n placeholder=\"AQ…\"\n value={text}\n />\n {invalid && (\n <Card id=\"themer-paste-error\" padding={3} radius={2} role=\"alert\" tone=\"critical\">\n <Text size={1}>\n That is not a theme code — codes are one word, copied from a theme's menu.\n </Text>\n </Card>\n )}\n </Stack>","<form\n onSubmit={(event) => {\n event.preventDefault()\n add()\n }}\n >\n <Stack gap={3} padding={4}>\n <Text muted size={1}>\n Paste the code someone copied from their themer — the theme is added to your list and\n applied.\n </Text>\n <TextInput\n aria-describedby={invalid ? 'themer-paste-error' : undefined}\n aria-invalid={invalid}\n aria-label=\"Theme code\"\n autoFocus\n customValidity={invalid ? 'That is not a theme code' : undefined}\n fontSize={1}\n onChange={(event) => {\n setText(event.currentTarget.value)\n setInvalid(false)\n }}\n placeholder=\"AQ…\"\n value={text}\n />\n {invalid && (\n <Card id=\"themer-paste-error\" padding={3} radius={2} role=\"alert\" tone=\"critical\">\n <Text size={1}>\n That is not a theme code — codes are one word, copied from a theme's menu.\n </Text>\n </Card>\n )}\n </Stack>\n </form>","<Dialog\n animate\n footer={\n <Flex gap={2} justify=\"flex-end\" padding={3}>\n <Button mode=\"ghost\" onClick={onClose} padding={2} text=\"Cancel\" />\n <Button\n disabled={text.trim() === ''}\n onClick={add}\n padding={2}\n text=\"Add theme\"\n tone=\"primary\"\n />\n </Flex>\n }\n header=\"Add a shared theme\"\n id=\"themer-paste\"\n onClickOutside={onClose}\n onClose={onClose}\n width={0}\n >\n <form\n onSubmit={(event) => {\n event.preventDefault()\n add()\n }}\n >\n <Stack gap={3} padding={4}>\n <Text muted size={1}>\n Paste the code someone copied from their themer — the theme is added to your list and\n applied.\n </Text>\n <TextInput\n aria-describedby={invalid ? 'themer-paste-error' : undefined}\n aria-invalid={invalid}\n aria-label=\"Theme code\"\n autoFocus\n customValidity={invalid ? 'That is not a theme code' : undefined}\n fontSize={1}\n onChange={(event) => {\n setText(event.currentTarget.value)\n setInvalid(false)\n }}\n placeholder=\"AQ…\"\n value={text}\n />\n {invalid && (\n <Card id=\"themer-paste-error\" padding={3} radius={2} role=\"alert\" tone=\"critical\">\n <Text size={1}>\n That is not a theme code — codes are one word, copied from a theme's menu.\n </Text>\n </Card>\n )}\n </Stack>\n </form>\n </Dialog>","_c","frame","displayTitle(theme.title)","title","(candidate) => candidate.slug === theme.slug","slug","(to: number) => {\n const order = themes.map((candidate) => candidate.slug)\n\n order.splice(to, 0, ...order.splice(index, 1))\n send({type: 'theme.reorder', order})\n }","(candidate) => candidate.slug","() => {\n // The click lands before the next frame\n requestAnimationFrame(() => {\n dragged.current = false\n })\n }","() => {\n dragged.current = true\n }","{scale: 1.04}","() => {\n if (!dragged.current) send({type: 'theme.pick', slug: theme.slug})\n }","(event) => {\n // A mouse or pen drags the card off the button; a finger scrolls the\n // list instead, and moves themes from the menu\n if (event.pointerType !== 'touch') dragControls.start(event)\n }","<span className={frame}>\n <ThemeThumbnail options={theme.options} />\n </span>","options","<Box as=\"span\" display=\"block\" paddingTop={2} paddingX={2}>\n <Text\n align=\"center\"\n as=\"span\"\n size={1}\n textOverflow=\"ellipsis\"\n weight={active ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>","active ? 'medium' : 'regular'","<button\n aria-pressed={active}\n className={pickButton}\n onClick={() => {\n if (!dragged.current) send({type: 'theme.pick', slug: theme.slug})\n }}\n onPointerDown={(event) => {\n // A mouse or pen drags the card off the button; a finger scrolls the\n // list instead, and moves themes from the menu\n if (event.pointerType !== 'touch') dragControls.start(event)\n }}\n type=\"button\"\n >\n <span className={frame}>\n <ThemeThumbnail options={theme.options} />\n </span>\n <Box as=\"span\" display=\"block\" paddingTop={2} paddingX={2}>\n <Text\n align=\"center\"\n as=\"span\"\n size={1}\n textOverflow=\"ellipsis\"\n weight={active ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>\n </button>","<Text size={1}>Show more</Text>","<Button\n aria-label={`Show more for ${title}`}\n fontSize={1}\n icon={EllipsisHorizontalIcon}\n mode=\"ghost\"\n padding={1}\n radius={2}\n />","`Show more for ${title}`","() => moveTo(index - 1)","<MenuItem\n disabled={index <= 0}\n icon={ArrowUpIcon}\n onClick={() => moveTo(index - 1)}\n text=\"Move up\"\n />","index <= 0","() => moveTo(index + 1)","<MenuItem\n disabled={index >= themes.length - 1}\n icon={ArrowDownIcon}\n onClick={() => moveTo(index + 1)}\n text=\"Move down\"\n />","index >= themes.length - 1","<MenuDivider />","theme.source === 'custom' && (\n <MenuItem\n icon={EditIcon}\n onClick={() => send({type: 'theme.edit', slug: theme.slug})}\n text=\"Edit\"\n />\n )","source","() => send({type: 'theme.duplicate', slug: theme.slug})","<MenuItem\n icon={CopyIcon}\n onClick={() => send({type: 'theme.duplicate', slug: theme.slug})}\n text={theme.source === 'custom' ? 'Duplicate' : 'Duplicate to edit'}\n />","theme.source === 'custom' ? 'Duplicate' : 'Duplicate to edit'","<MenuItem\n icon={ShareIcon}\n onClick={() => void copyTheme(theme)}\n text=\"Copy to share\"\n />","theme.source !== 'config' && (\n <>\n <MenuDivider />\n <MenuItem\n icon={TrashIcon}\n onClick={() => send({type: 'theme.remove', slug: theme.slug})}\n text=\"Remove\"\n tone=\"critical\"\n />\n </>\n )","<Menu>\n <MenuItem\n disabled={index <= 0}\n icon={ArrowUpIcon}\n onClick={() => moveTo(index - 1)}\n text=\"Move up\"\n />\n <MenuItem\n disabled={index >= themes.length - 1}\n icon={ArrowDownIcon}\n onClick={() => moveTo(index + 1)}\n text=\"Move down\"\n />\n <MenuDivider />\n {theme.source === 'custom' && (\n <MenuItem\n icon={EditIcon}\n onClick={() => send({type: 'theme.edit', slug: theme.slug})}\n text=\"Edit\"\n />\n )}\n <MenuItem\n icon={CopyIcon}\n onClick={() => send({type: 'theme.duplicate', slug: theme.slug})}\n text={theme.source === 'custom' ? 'Duplicate' : 'Duplicate to edit'}\n />\n <MenuItem\n icon={ShareIcon}\n onClick={() => void copyTheme(theme)}\n text=\"Copy to share\"\n />\n {theme.source !== 'config' && (\n <>\n <MenuDivider />\n <MenuItem\n icon={TrashIcon}\n onClick={() => send({type: 'theme.remove', slug: theme.slug})}\n text=\"Remove\"\n tone=\"critical\"\n />\n </>\n )}\n </Menu>","() => setMenuOpen(false)","() => setMenuOpen(true)","{animate: true, placement: 'bottom-end', portal: true, scheme}","<Tooltip animate content={<Text size={1}>Show more</Text>} placement=\"bottom\" portal>\n <Card className={menuCard} radius={2} scheme=\"dark\">\n <MenuButton\n button={\n <Button\n aria-label={`Show more for ${title}`}\n fontSize={1}\n icon={EllipsisHorizontalIcon}\n mode=\"ghost\"\n padding={1}\n radius={2}\n />\n }\n id={`themer-theme-${theme.slug}`}\n menu={\n <Menu>\n <MenuItem\n disabled={index <= 0}\n icon={ArrowUpIcon}\n onClick={() => moveTo(index - 1)}\n text=\"Move up\"\n />\n <MenuItem\n disabled={index >= themes.length - 1}\n icon={ArrowDownIcon}\n onClick={() => moveTo(index + 1)}\n text=\"Move down\"\n />\n <MenuDivider />\n {theme.source === 'custom' && (\n <MenuItem\n icon={EditIcon}\n onClick={() => send({type: 'theme.edit', slug: theme.slug})}\n text=\"Edit\"\n />\n )}\n <MenuItem\n icon={CopyIcon}\n onClick={() => send({type: 'theme.duplicate', slug: theme.slug})}\n text={theme.source === 'custom' ? 'Duplicate' : 'Duplicate to edit'}\n />\n <MenuItem\n icon={ShareIcon}\n onClick={() => void copyTheme(theme)}\n text=\"Copy to share\"\n />\n {theme.source !== 'config' && (\n <>\n <MenuDivider />\n <MenuItem\n icon={TrashIcon}\n onClick={() => send({type: 'theme.remove', slug: theme.slug})}\n text=\"Remove\"\n tone=\"critical\"\n />\n </>\n )}\n </Menu>\n }\n onClose={() => setMenuOpen(false)}\n onOpen={() => setMenuOpen(true)}\n popover={{animate: true, placement: 'bottom-end', portal: true, scheme}}\n />\n </Card>\n </Tooltip>","`themer-theme-${theme.slug}`","<div className={menuSlot} data-visible={menuOpen}>\n <Tooltip animate content={<Text size={1}>Show more</Text>} placement=\"bottom\" portal>\n <Card className={menuCard} radius={2} scheme=\"dark\">\n <MenuButton\n button={\n <Button\n aria-label={`Show more for ${title}`}\n fontSize={1}\n icon={EllipsisHorizontalIcon}\n mode=\"ghost\"\n padding={1}\n radius={2}\n />\n }\n id={`themer-theme-${theme.slug}`}\n menu={\n <Menu>\n <MenuItem\n disabled={index <= 0}\n icon={ArrowUpIcon}\n onClick={() => moveTo(index - 1)}\n text=\"Move up\"\n />\n <MenuItem\n disabled={index >= themes.length - 1}\n icon={ArrowDownIcon}\n onClick={() => moveTo(index + 1)}\n text=\"Move down\"\n />\n <MenuDivider />\n {theme.source === 'custom' && (\n <MenuItem\n icon={EditIcon}\n onClick={() => send({type: 'theme.edit', slug: theme.slug})}\n text=\"Edit\"\n />\n )}\n <MenuItem\n icon={CopyIcon}\n onClick={() => send({type: 'theme.duplicate', slug: theme.slug})}\n text={theme.source === 'custom' ? 'Duplicate' : 'Duplicate to edit'}\n />\n <MenuItem\n icon={ShareIcon}\n onClick={() => void copyTheme(theme)}\n text=\"Copy to share\"\n />\n {theme.source !== 'config' && (\n <>\n <MenuDivider />\n <MenuItem\n icon={TrashIcon}\n onClick={() => send({type: 'theme.remove', slug: theme.slug})}\n text=\"Remove\"\n tone=\"critical\"\n />\n </>\n )}\n </Menu>\n }\n onClose={() => setMenuOpen(false)}\n onOpen={() => setMenuOpen(true)}\n popover={{animate: true, placement: 'bottom-end', portal: true, scheme}}\n />\n </Card>\n </Tooltip>\n </div>","<Reorder.Item\n as=\"div\"\n className={root}\n dragControls={dragControls}\n dragListener={false}\n onDragEnd={() => {\n // The click lands before the next frame\n requestAnimationFrame(() => {\n dragged.current = false\n })\n }}\n onDragStart={() => {\n dragged.current = true\n }}\n ref={active ? scrollRefIntoViewIfNeeded : undefined}\n value={theme.slug}\n whileDrag={{scale: 1.04}}\n >\n <button\n aria-pressed={active}\n className={pickButton}\n onClick={() => {\n if (!dragged.current) send({type: 'theme.pick', slug: theme.slug})\n }}\n onPointerDown={(event) => {\n // A mouse or pen drags the card off the button; a finger scrolls the\n // list instead, and moves themes from the menu\n if (event.pointerType !== 'touch') dragControls.start(event)\n }}\n type=\"button\"\n >\n <span className={frame}>\n <ThemeThumbnail options={theme.options} />\n </span>\n <Box as=\"span\" display=\"block\" paddingTop={2} paddingX={2}>\n <Text\n align=\"center\"\n as=\"span\"\n size={1}\n textOverflow=\"ellipsis\"\n weight={active ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>\n </button>\n\n {/* The tooltip wraps a card rather than the button: the menu button\n clones its button to wire it up, which a tooltip in between would\n swallow. The card keeps the button dark, as it sits on the dark\n half of the thumbnail, while the menu opens in the sidebar's scheme */}\n <div className={menuSlot} data-visible={menuOpen}>\n <Tooltip animate content={<Text size={1}>Show more</Text>} placement=\"bottom\" portal>\n <Card className={menuCard} radius={2} scheme=\"dark\">\n <MenuButton\n button={\n <Button\n aria-label={`Show more for ${title}`}\n fontSize={1}\n icon={EllipsisHorizontalIcon}\n mode=\"ghost\"\n padding={1}\n radius={2}\n />\n }\n id={`themer-theme-${theme.slug}`}\n menu={\n <Menu>\n <MenuItem\n disabled={index <= 0}\n icon={ArrowUpIcon}\n onClick={() => moveTo(index - 1)}\n text=\"Move up\"\n />\n <MenuItem\n disabled={index >= themes.length - 1}\n icon={ArrowDownIcon}\n onClick={() => moveTo(index + 1)}\n text=\"Move down\"\n />\n <MenuDivider />\n {theme.source === 'custom' && (\n <MenuItem\n icon={EditIcon}\n onClick={() => send({type: 'theme.edit', slug: theme.slug})}\n text=\"Edit\"\n />\n )}\n <MenuItem\n icon={CopyIcon}\n onClick={() => send({type: 'theme.duplicate', slug: theme.slug})}\n text={theme.source === 'custom' ? 'Duplicate' : 'Duplicate to edit'}\n />\n <MenuItem\n icon={ShareIcon}\n onClick={() => void copyTheme(theme)}\n text=\"Copy to share\"\n />\n {theme.source !== 'config' && (\n <>\n <MenuDivider />\n <MenuItem\n icon={TrashIcon}\n onClick={() => send({type: 'theme.remove', slug: theme.slug})}\n text=\"Remove\"\n tone=\"critical\"\n />\n </>\n )}\n </Menu>\n }\n onClose={() => setMenuOpen(false)}\n onOpen={() => setMenuOpen(true)}\n popover={{animate: true, placement: 'bottom-end', portal: true, scheme}}\n />\n </Card>\n </Tooltip>\n </div>\n </Reorder.Item>","active ? scrollRefIntoViewIfNeeded : undefined","Reorder.Item","candidate","_c","_temp","frame","_Fragment","(palette, file) =>\n send({\n type: 'theme.add',\n title: titleFromFileName(file.name),\n options: optionsFromImagePalette(palette),\n palette,\n imageUrl: URL.createObjectURL(file),\n })","(order: string[]) => send({type: 'theme.reorder', order})","themes.map((theme) => theme.slug)","(theme) => theme.slug","themes.map((theme) => (\n <ThemeCard active={theme.slug === active.slug} key={theme.slug} theme={theme} />\n ))","active.slug","(theme) => (\n <ThemeCard active={theme.slug === active.slug} key={theme.slug} theme={theme} />\n )","theme","<MotionConfig reducedMotion=\"user\">\n <Reorder.Group\n as=\"div\"\n className={cardGrid}\n onReorder={(order: string[]) => send({type: 'theme.reorder', order})}\n values={themes.map((theme) => theme.slug)}\n >\n {themes.map((theme) => (\n <ThemeCard active={theme.slug === active.slug} key={theme.slug} theme={theme} />\n ))}\n </Reorder.Group>\n </MotionConfig>","Reorder.Group","removed.length > 0 && (\n <Button\n gap={2}\n icon={RestoreIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.removed'})}\n padding={2}\n text={`Show removed (${removed.length})`}\n width=\"fill\"\n />\n )","length","<ScrollArea padding={3}>\n <Stack gap={4}>\n {/* The group tells a column from a grid by measuring the cards, and\n lets go of the layout animations for users who prefer reduced motion */}\n <MotionConfig reducedMotion=\"user\">\n <Reorder.Group\n as=\"div\"\n className={cardGrid}\n onReorder={(order: string[]) => send({type: 'theme.reorder', order})}\n values={themes.map((theme) => theme.slug)}\n >\n {themes.map((theme) => (\n <ThemeCard active={theme.slug === active.slug} key={theme.slug} theme={theme} />\n ))}\n </Reorder.Group>\n </MotionConfig>\n {/* Trails the list rather than sitting in the footer, so the footer\n does not shift when the first theme gets removed */}\n {removed.length > 0 && (\n <Button\n gap={2}\n icon={RestoreIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.removed'})}\n padding={2}\n text={`Show removed (${removed.length})`}\n width=\"fill\"\n />\n )}\n </Stack>\n </ScrollArea>","<Box flex={1}>\n <TooltipButton\n icon={AddIcon}\n mode=\"ghost\"\n onClick={() => send({type: 'theme.add'})}\n text=\"Add theme\"\n tooltip=\"Add a theme based on the applied one\"\n width=\"fill\"\n />\n </Box>","<ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n tooltip=\"Add a theme from the colors of an image\"\n />","<TooltipButton\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={async () => {\n // Straight from the clipboard where the browser allows; by hand otherwise\n if ((await addThemeFromClipboard()) !== 'added') setPasting(true)\n }}\n tooltip=\"Add a theme from a code someone shared\"\n />","<Card borderTop padding={3}>\n <Flex gap={2}>\n <Box flex={1}>\n <TooltipButton\n icon={AddIcon}\n mode=\"ghost\"\n onClick={() => send({type: 'theme.add'})}\n text=\"Add theme\"\n tooltip=\"Add a theme based on the applied one\"\n width=\"fill\"\n />\n </Box>\n <ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n tooltip=\"Add a theme from the colors of an image\"\n />\n <TooltipButton\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={async () => {\n // Straight from the clipboard where the browser allows; by hand otherwise\n if ((await addThemeFromClipboard()) !== 'added') setPasting(true)\n }}\n tooltip=\"Add a theme from a code someone shared\"\n />\n </Flex>\n </Card>","pasting && <PasteThemeDialog onClose={() => setPasting(false)} />","<>\n <ScrollArea padding={3}>\n <Stack gap={4}>\n {/* The group tells a column from a grid by measuring the cards, and\n lets go of the layout animations for users who prefer reduced motion */}\n <MotionConfig reducedMotion=\"user\">\n <Reorder.Group\n as=\"div\"\n className={cardGrid}\n onReorder={(order: string[]) => send({type: 'theme.reorder', order})}\n values={themes.map((theme) => theme.slug)}\n >\n {themes.map((theme) => (\n <ThemeCard active={theme.slug === active.slug} key={theme.slug} theme={theme} />\n ))}\n </Reorder.Group>\n </MotionConfig>\n {/* Trails the list rather than sitting in the footer, so the footer\n does not shift when the first theme gets removed */}\n {removed.length > 0 && (\n <Button\n gap={2}\n icon={RestoreIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.removed'})}\n padding={2}\n text={`Show removed (${removed.length})`}\n width=\"fill\"\n />\n )}\n </Stack>\n </ScrollArea>\n\n <Card borderTop padding={3}>\n <Flex gap={2}>\n <Box flex={1}>\n <TooltipButton\n icon={AddIcon}\n mode=\"ghost\"\n onClick={() => send({type: 'theme.add'})}\n text=\"Add theme\"\n tooltip=\"Add a theme based on the applied one\"\n width=\"fill\"\n />\n </Box>\n <ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n tooltip=\"Add a theme from the colors of an image\"\n />\n <TooltipButton\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={async () => {\n // Straight from the clipboard where the browser allows; by hand otherwise\n if ((await addThemeFromClipboard()) !== 'added') setPasting(true)\n }}\n tooltip=\"Add a theme from a code someone shared\"\n />\n </Flex>\n </Card>\n\n {pasting && <PasteThemeDialog onClose={() => setPasting(false)} />}\n </>","_c","_temp","_Fragment","createThemeSnippet(theme.options)","options","async () => {\n try {\n await navigator.clipboard.writeText(snippet)\n toast.push({status: 'success', title: 'Copied the theme to the clipboard'})\n } catch {\n toast.push({status: 'error', title: 'Could not copy the theme'})\n }\n }","<Flex gap={2} justify=\"flex-end\" padding={3}>\n <Button\n gap={2}\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={() => void handleCopy()}\n padding={2}\n text=\"Copy\"\n />\n </Flex>","<code>sanity.config.ts</code>","<code>theme</code>","<code>defineConfig</code>","displayTitle(theme.title)","title","<Text muted size={1}>\n Export this from <code>sanity.config.ts</code> and pass it as the <code>theme</code> of{' '}\n <code>defineConfig</code> to make “{displayTitle(theme.title)}” the Studio theme without\n this tool.\n </Text>","<Card border overflow=\"auto\" padding={3} radius={2} tone=\"transparent\">\n <Code language=\"ts\" size={1}>\n {snippet}\n </Code>\n </Card>","<Box padding={4}>\n <Stack gap={4}>\n <Text muted size={1}>\n Export this from <code>sanity.config.ts</code> and pass it as the <code>theme</code> of{' '}\n <code>defineConfig</code> to make “{displayTitle(theme.title)}” the Studio theme without\n this tool.\n </Text>\n <Card border overflow=\"auto\" padding={3} radius={2} tone=\"transparent\">\n <Code language=\"ts\" size={1}>\n {snippet}\n </Code>\n </Card>\n </Stack>\n </Box>","<Dialog\n animate\n footer={\n <Flex gap={2} justify=\"flex-end\" padding={3}>\n <Button\n gap={2}\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={() => void handleCopy()}\n padding={2}\n text=\"Copy\"\n />\n </Flex>\n }\n header=\"Add to your config\"\n id=\"themer-snippet\"\n onClickOutside={onClose}\n onClose={onClose}\n width={1}\n >\n <Box padding={4}>\n <Stack gap={4}>\n <Text muted size={1}>\n Export this from <code>sanity.config.ts</code> and pass it as the <code>theme</code> of{' '}\n <code>defineConfig</code> to make “{displayTitle(theme.title)}” the Studio theme without\n this tool.\n </Text>\n <Card border overflow=\"auto\" padding={3} radius={2} tone=\"transparent\">\n <Code language=\"ts\" size={1}>\n {snippet}\n </Code>\n </Card>\n </Stack>\n </Box>\n </Dialog>","_c","navbarHeight === null ? undefined : {height: navbarHeight}","!inList && (\n <TooltipButton\n icon={ArrowLeftIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.list'})}\n padding={2}\n tooltip=\"Back to the themes\"\n />\n )","<Box className={title} flex={1}>\n <Text size={1} textOverflow=\"ellipsis\" weight=\"semibold\">\n {VIEW_TITLES[view.name]}\n </Text>\n </Box>","VIEW_TITLES[view.name]","view.name !== 'removed' && (\n <>\n <TooltipButton\n aria-pressed={split}\n icon={SplitVerticalIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'preview.toggle'})}\n padding={2}\n selected={split}\n tooltip=\"Show light and dark side by side\"\n />\n <TooltipButton\n icon={CodeBlockIcon}\n mode=\"bleed\"\n onClick={() => setSnippetOpen(true)}\n padding={2}\n tooltip=\"Show the code for the applied theme\"\n />\n </>\n )","name","<TooltipButton\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.close'})}\n padding={2}\n tooltip=\"Close themer\"\n />","<Flex>\n {view.name !== 'removed' && (\n <>\n <TooltipButton\n aria-pressed={split}\n icon={SplitVerticalIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'preview.toggle'})}\n padding={2}\n selected={split}\n tooltip=\"Show light and dark side by side\"\n />\n <TooltipButton\n icon={CodeBlockIcon}\n mode=\"bleed\"\n onClick={() => setSnippetOpen(true)}\n padding={2}\n tooltip=\"Show the code for the applied theme\"\n />\n </>\n )}\n <TooltipButton\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.close'})}\n padding={2}\n tooltip=\"Close themer\"\n />\n </Flex>","<Flex align=\"center\" gap={1} height=\"fill\">\n {!inList && (\n <TooltipButton\n icon={ArrowLeftIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.list'})}\n padding={2}\n tooltip=\"Back to the themes\"\n />\n )}\n <Box className={title} flex={1}>\n <Text size={1} textOverflow=\"ellipsis\" weight=\"semibold\">\n {VIEW_TITLES[view.name]}\n </Text>\n </Box>\n {/* Packed without gaps, so the title still fits next to them at the\n narrowest sidebar width */}\n <Flex>\n {view.name !== 'removed' && (\n <>\n <TooltipButton\n aria-pressed={split}\n icon={SplitVerticalIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'preview.toggle'})}\n padding={2}\n selected={split}\n tooltip=\"Show light and dark side by side\"\n />\n <TooltipButton\n icon={CodeBlockIcon}\n mode=\"bleed\"\n onClick={() => setSnippetOpen(true)}\n padding={2}\n tooltip=\"Show the code for the applied theme\"\n />\n </>\n )}\n <TooltipButton\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.close'})}\n padding={2}\n tooltip=\"Close themer\"\n />\n </Flex>\n </Flex>","<Card\n borderBottom\n padding={3}\n sizing=\"border\"\n style={navbarHeight === null ? undefined : {height: navbarHeight}}\n >\n <Flex align=\"center\" gap={1} height=\"fill\">\n {!inList && (\n <TooltipButton\n icon={ArrowLeftIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.list'})}\n padding={2}\n tooltip=\"Back to the themes\"\n />\n )}\n <Box className={title} flex={1}>\n <Text size={1} textOverflow=\"ellipsis\" weight=\"semibold\">\n {VIEW_TITLES[view.name]}\n </Text>\n </Box>\n {/* Packed without gaps, so the title still fits next to them at the\n narrowest sidebar width */}\n <Flex>\n {view.name !== 'removed' && (\n <>\n <TooltipButton\n aria-pressed={split}\n icon={SplitVerticalIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'preview.toggle'})}\n padding={2}\n selected={split}\n tooltip=\"Show light and dark side by side\"\n />\n <TooltipButton\n icon={CodeBlockIcon}\n mode=\"bleed\"\n onClick={() => setSnippetOpen(true)}\n padding={2}\n tooltip=\"Show the code for the applied theme\"\n />\n </>\n )}\n <TooltipButton\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.close'})}\n padding={2}\n tooltip=\"Close themer\"\n />\n </Flex>\n </Flex>\n </Card>","view.name === 'list' && <ThemeList />","view.name === 'edit' && <ThemeEditor focusTitle={view.focusTitle} slug={view.slug} />","focusTitle","slug","view.name === 'removed' && <RemovedThemes />","<Flex direction=\"column\" height=\"fill\">\n {/* The header sits next to the Studio navbar and takes its height, so\n the two bottom borders line up — whatever height the Studio, its\n breakpoint or a custom navbar gives it */}\n <Card\n borderBottom\n padding={3}\n sizing=\"border\"\n style={navbarHeight === null ? undefined : {height: navbarHeight}}\n >\n <Flex align=\"center\" gap={1} height=\"fill\">\n {!inList && (\n <TooltipButton\n icon={ArrowLeftIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.list'})}\n padding={2}\n tooltip=\"Back to the themes\"\n />\n )}\n <Box className={title} flex={1}>\n <Text size={1} textOverflow=\"ellipsis\" weight=\"semibold\">\n {VIEW_TITLES[view.name]}\n </Text>\n </Box>\n {/* Packed without gaps, so the title still fits next to them at the\n narrowest sidebar width */}\n <Flex>\n {view.name !== 'removed' && (\n <>\n <TooltipButton\n aria-pressed={split}\n icon={SplitVerticalIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'preview.toggle'})}\n padding={2}\n selected={split}\n tooltip=\"Show light and dark side by side\"\n />\n <TooltipButton\n icon={CodeBlockIcon}\n mode=\"bleed\"\n onClick={() => setSnippetOpen(true)}\n padding={2}\n tooltip=\"Show the code for the applied theme\"\n />\n </>\n )}\n <TooltipButton\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.close'})}\n padding={2}\n tooltip=\"Close themer\"\n />\n </Flex>\n </Flex>\n </Card>\n\n {view.name === 'list' && <ThemeList />}\n {view.name === 'edit' && <ThemeEditor focusTitle={view.focusTitle} slug={view.slug} />}\n {view.name === 'removed' && <RemovedThemes />}\n </Flex>","snippetOpen && <ThemeSnippetDialog onClose={() => setSnippetOpen(false)} theme={active} />","<Card height=\"fill\">\n <Flex direction=\"column\" height=\"fill\">\n {/* The header sits next to the Studio navbar and takes its height, so\n the two bottom borders line up — whatever height the Studio, its\n breakpoint or a custom navbar gives it */}\n <Card\n borderBottom\n padding={3}\n sizing=\"border\"\n style={navbarHeight === null ? undefined : {height: navbarHeight}}\n >\n <Flex align=\"center\" gap={1} height=\"fill\">\n {!inList && (\n <TooltipButton\n icon={ArrowLeftIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.list'})}\n padding={2}\n tooltip=\"Back to the themes\"\n />\n )}\n <Box className={title} flex={1}>\n <Text size={1} textOverflow=\"ellipsis\" weight=\"semibold\">\n {VIEW_TITLES[view.name]}\n </Text>\n </Box>\n {/* Packed without gaps, so the title still fits next to them at the\n narrowest sidebar width */}\n <Flex>\n {view.name !== 'removed' && (\n <>\n <TooltipButton\n aria-pressed={split}\n icon={SplitVerticalIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'preview.toggle'})}\n padding={2}\n selected={split}\n tooltip=\"Show light and dark side by side\"\n />\n <TooltipButton\n icon={CodeBlockIcon}\n mode=\"bleed\"\n onClick={() => setSnippetOpen(true)}\n padding={2}\n tooltip=\"Show the code for the applied theme\"\n />\n </>\n )}\n <TooltipButton\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.close'})}\n padding={2}\n tooltip=\"Close themer\"\n />\n </Flex>\n </Flex>\n </Card>\n\n {view.name === 'list' && <ThemeList />}\n {view.name === 'edit' && <ThemeEditor focusTitle={view.focusTitle} slug={view.slug} />}\n {view.name === 'removed' && <RemovedThemes />}\n </Flex>\n\n {snippetOpen && <ThemeSnippetDialog onClose={() => setSnippetOpen(false)} theme={active} />}\n </Card>","_c","_Fragment","() => writeStoredWidth(width)","[width]","(event: React.PointerEvent<HTMLDivElement>) => {\n if (event.button !== 0) return\n\n event.preventDefault()\n event.currentTarget.setPointerCapture(event.pointerId)\n drag.current = {pointerId: event.pointerId, startX: event.clientX, startWidth: width}\n setDragging(true)\n }","useCallback(\n (event: React.PointerEvent<HTMLDivElement>) => {\n if (event.button !== 0) return\n\n event.preventDefault()\n event.currentTarget.setPointerCapture(event.pointerId)\n drag.current = {pointerId: event.pointerId, startX: event.clientX, startWidth: width}\n setDragging(true)\n },\n [width],\n )","(event: React.PointerEvent<HTMLDivElement>) => {\n if (!drag.current || drag.current.pointerId !== event.pointerId) return\n\n // The sidebar sits on the right, so dragging left makes it wider\n setWidth(clampWidth(drag.current.startWidth - (event.clientX - drag.current.startX)))\n }","event","useCallback((event: React.PointerEvent<HTMLDivElement>) => {\n if (!drag.current || drag.current.pointerId !== event.pointerId) return\n\n // The sidebar sits on the right, so dragging left makes it wider\n setWidth(clampWidth(drag.current.startWidth - (event.clientX - drag.current.startX)))\n }, [])","(event: React.PointerEvent<HTMLDivElement>) => {\n if (!drag.current || drag.current.pointerId !== event.pointerId) return\n\n drag.current = null\n setDragging(false)\n }","useCallback((event: React.PointerEvent<HTMLDivElement>) => {\n if (!drag.current || drag.current.pointerId !== event.pointerId) return\n\n drag.current = null\n setDragging(false)\n }, [])","(event: React.KeyboardEvent<HTMLDivElement>) => {\n const steps: Record<string, number> = {\n ArrowLeft: KEYBOARD_STEP,\n ArrowRight: -KEYBOARD_STEP,\n Home: MAXIMUM_WIDTH,\n End: -MAXIMUM_WIDTH,\n }\n const step = steps[event.key]\n\n if (step === undefined) return\n\n event.preventDefault()\n setWidth((current) => clampWidth(current + step))\n }","useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {\n const steps: Record<string, number> = {\n ArrowLeft: KEYBOARD_STEP,\n ArrowRight: -KEYBOARD_STEP,\n Home: MAXIMUM_WIDTH,\n End: -MAXIMUM_WIDTH,\n }\n const step = steps[event.key]\n\n if (step === undefined) return\n\n event.preventDefault()\n setWidth((current) => clampWidth(current + step))\n }, [])","overlay ? undefined : {width}","!overlay && (\n <div\n aria-label=\"Resize the themer\"\n className={resizeHandle}\n aria-orientation=\"vertical\"\n aria-valuemax={MAXIMUM_WIDTH}\n aria-valuemin={MINIMUM_WIDTH}\n aria-valuenow={width}\n data-dragging={dragging}\n onDoubleClick={() => setWidth(MINIMUM_WIDTH)}\n onKeyDown={handleKeyDown}\n onPointerCancel={handlePointerUp}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n // oxlint-disable-next-line prefer-tag-over-role -- a window splitter is a focusable, draggable separator, which an hr is not\n role=\"separator\"\n tabIndex={0}\n />\n )","<Box className={content} height=\"fill\" overflow=\"hidden\">\n <ThemerSidebar />\n </Box>","<Card borderLeft={!overlay} className={frame} height=\"fill\">\n {!overlay && (\n <div\n aria-label=\"Resize the themer\"\n className={resizeHandle}\n aria-orientation=\"vertical\"\n aria-valuemax={MAXIMUM_WIDTH}\n aria-valuemin={MINIMUM_WIDTH}\n aria-valuenow={width}\n data-dragging={dragging}\n onDoubleClick={() => setWidth(MINIMUM_WIDTH)}\n onKeyDown={handleKeyDown}\n onPointerCancel={handlePointerUp}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n // oxlint-disable-next-line prefer-tag-over-role -- a window splitter is a focusable, draggable separator, which an hr is not\n role=\"separator\"\n tabIndex={0}\n />\n )}\n <Box className={content} height=\"fill\" overflow=\"hidden\">\n <ThemerSidebar />\n </Box>\n </Card>","!overlay","<Layer\n className={sidebar}\n data-overlay={overlay}\n style={overlay ? undefined : {width}}\n zOffset={100}\n >\n <Card borderLeft={!overlay} className={frame} height=\"fill\">\n {!overlay && (\n <div\n aria-label=\"Resize the themer\"\n className={resizeHandle}\n aria-orientation=\"vertical\"\n aria-valuemax={MAXIMUM_WIDTH}\n aria-valuemin={MINIMUM_WIDTH}\n aria-valuenow={width}\n data-dragging={dragging}\n onDoubleClick={() => setWidth(MINIMUM_WIDTH)}\n onKeyDown={handleKeyDown}\n onPointerCancel={handlePointerUp}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n // oxlint-disable-next-line prefer-tag-over-role -- a window splitter is a focusable, draggable separator, which an hr is not\n role=\"separator\"\n tabIndex={0}\n />\n )}\n <Box className={content} height=\"fill\" overflow=\"hidden\">\n <ThemerSidebar />\n </Box>\n </Card>\n </Layer>","_c","() => {\n const root = ref.current\n\n if (!root) return undefined\n\n let navbar: Element | null = null\n const resizeObserver = new ResizeObserver(() => {\n if (navbar) setHeight(navbar.getBoundingClientRect().height)\n })\n\n // The navbar can render after the layout (the Studio suspends while it\n // loads) and remount (switching workspaces), so it is looked up again\n // whenever the Studio's DOM changes — cheap, as it sits near the top\n const observeNavbar = () => {\n const next = root.querySelector(NAVBAR_SELECTOR)\n\n if (next === navbar) return\n\n if (navbar) resizeObserver.unobserve(navbar)\n\n navbar = next\n\n if (navbar) {\n resizeObserver.observe(navbar)\n setHeight(navbar.getBoundingClientRect().height)\n } else {\n setHeight(null)\n }\n }\n\n const mutationObserver = new MutationObserver(observeNavbar)\n\n mutationObserver.observe(root, {childList: true, subtree: true})\n observeNavbar()\n\n return () => {\n mutationObserver.disconnect()\n resizeObserver.disconnect()\n }\n }","[ref]","ref.current","_c","() => {\n if (Object.is(deferred, value)) return\n\n startTransition(() => {\n addTransitionType(type)\n setTransitions((count) => count + 1)\n })\n }","[deferred, type, value]","(count) => count + 1","_c","_temp","() => ({baseOptions, stored: readStoredState()})","{input}","snapshot.hasTag('panel')","snapshot.hasTag('split')","snapshot.hasTag('moving')","snapshot.hasTag('switching')","resolveThemes(stored, baseOptions)","useMemo(\n () => resolveThemes(stored, baseOptions),\n [stored, baseOptions],\n )","activeOptions === null ? null : buildTheme(activeOptions)","useMemo(\n () => (activeOptions === null ? null : buildTheme(activeOptions)),\n [activeOptions],\n )","() => {\n const background = shownTheme?.v2?.color[scheme].default.bg\n\n if (background === undefined) return undefined\n\n const {style} = document.body\n const previous = style.backgroundColor\n\n style.backgroundColor = background\n\n return () => {\n style.backgroundColor = previous\n }\n }","color","[scheme, shownTheme]","() => () => {\n for (const url of Object.values(actorRef.getSnapshot().context.images)) {\n URL.revokeObjectURL(url)\n }\n }","[actorRef]","{\n baseOptions,\n themes,\n removed,\n active,\n images,\n view,\n open,\n split,\n mobile,\n navbarHeight,\n send,\n }","useMemo<ThemerContextValue>(\n () => ({\n baseOptions,\n themes,\n removed,\n active,\n images,\n view,\n open,\n split,\n mobile,\n navbarHeight,\n send,\n }),\n [active, baseOptions, images, mobile, navbarHeight, open, removed, send, split, themes, view],\n )","() => send({type: 'layout.transitioned'})","reduceMotion\n ? {\n copyEnter: 'none',\n copyExit: 'none',\n panelEnter: 'none',\n panelExit: 'none',\n update: 'none',\n studioUpdate: 'none',\n }\n : {\n copyEnter: mobile ? splitTransitionClasses.dropIn : splitTransitionClasses.slideIn,\n copyExit: mobile ? splitTransitionClasses.dropOut : splitTransitionClasses.slideOut,\n panelEnter: panelTransitionClasses.slideIn,\n panelExit: panelTransitionClasses.slideOut,\n // The copy and the panel cross-fade to another theme, in place\n update: switching ? splitTransitionClasses.crossfade : 'none',\n // The Studio too — unless it is giving way or taking room, which cross-fades as it goes\n studioUpdate: moving\n ? splitTransitionClasses.resize\n : switching\n ? splitTransitionClasses.crossfade\n : 'none',\n }","shownOpen && (\n <Activity mode={shownSplit ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"opposite\"\n enter={classes.copyEnter}\n exit={classes.copyExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <StudioPreview\n borderBottom={mobile}\n borderRight={!mobile}\n scheme={oppositeScheme}\n theme={shownTheme}\n >\n {studio}\n </StudioPreview>\n </ViewTransition>\n </Activity>\n )","copyEnter","copyExit","update","<StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>","<ViewTransition key=\"primary\" onUpdate={transitioned} update={classes.studioUpdate}>\n <StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>\n </ViewTransition>","studioUpdate","<ResizableSidebar overlay={mobile} />","<ThemeProvider theme={shownTheme ?? undefined}>\n <ResizableSidebar overlay={mobile} />\n </ThemeProvider>","shownTheme ?? undefined","<ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n <ResizableSidebar overlay={mobile} />\n </ThemeProvider>\n </ViewTransition>","panelEnter","panelExit","<Activity mode={shownOpen ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n <ResizableSidebar overlay={mobile} />\n </ThemeProvider>\n </ViewTransition>\n </Activity>","shownOpen ? 'visible' : 'hidden'","<Flex className={layout} direction={mobile ? 'column' : 'row'} height=\"fill\" sizing=\"border\">\n {/* The opposite scheme comes first — on the far side of the sidebar,\n or on top on small screens — so the Studio the user was looking at\n stays where it is, mounted, in its own scheme. A whole second\n Studio is too costly to keep around (its styled-components alone\n insert CSS as they render), so it only exists while the sidebar is\n open — hidden until the split shows, warmed up for the transition */}\n {shownOpen && (\n <Activity mode={shownSplit ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"opposite\"\n enter={classes.copyEnter}\n exit={classes.copyExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <StudioPreview\n borderBottom={mobile}\n borderRight={!mobile}\n scheme={oppositeScheme}\n theme={shownTheme}\n >\n {studio}\n </StudioPreview>\n </ViewTransition>\n </Activity>\n )}\n {/* The Studio gives way and takes room while the machine says the\n layout is moving, and cross-fades while it says a theme is being\n switched to — the Studio updates in transitions of its own all the\n time, and none of those may animate it. Once a transition is under\n way the machine hears of it, and the Studio is its own again before\n the next commit */}\n <ViewTransition key=\"primary\" onUpdate={transitioned} update={classes.studioUpdate}>\n <StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>\n </ViewTransition>\n\n {/* The sidebar is small and cheap: it stays mounted, hidden while\n closed, keeping its state and ready to show */}\n <Activity mode={shownOpen ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n <ResizableSidebar overlay={mobile} />\n </ThemeProvider>\n </ViewTransition>\n </Activity>\n </Flex>","mobile ? 'column' : 'row'","<ThemerContext.Provider value={context}>\n <Flex className={layout} direction={mobile ? 'column' : 'row'} height=\"fill\" sizing=\"border\">\n {/* The opposite scheme comes first — on the far side of the sidebar,\n or on top on small screens — so the Studio the user was looking at\n stays where it is, mounted, in its own scheme. A whole second\n Studio is too costly to keep around (its styled-components alone\n insert CSS as they render), so it only exists while the sidebar is\n open — hidden until the split shows, warmed up for the transition */}\n {shownOpen && (\n <Activity mode={shownSplit ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"opposite\"\n enter={classes.copyEnter}\n exit={classes.copyExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <StudioPreview\n borderBottom={mobile}\n borderRight={!mobile}\n scheme={oppositeScheme}\n theme={shownTheme}\n >\n {studio}\n </StudioPreview>\n </ViewTransition>\n </Activity>\n )}\n {/* The Studio gives way and takes room while the machine says the\n layout is moving, and cross-fades while it says a theme is being\n switched to — the Studio updates in transitions of its own all the\n time, and none of those may animate it. Once a transition is under\n way the machine hears of it, and the Studio is its own again before\n the next commit */}\n <ViewTransition key=\"primary\" onUpdate={transitioned} update={classes.studioUpdate}>\n <StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>\n </ViewTransition>\n\n {/* The sidebar is small and cheap: it stays mounted, hidden while\n closed, keeping its state and ready to show */}\n <Activity mode={shownOpen ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n <ResizableSidebar overlay={mobile} />\n </ThemeProvider>\n </ViewTransition>\n </Activity>\n </Flex>\n </ThemerContext.Provider>","ThemerContext.Provider","<Card\n borderBottom={borderBottom}\n borderRight={borderRight}\n className={scheme ? studioScheme[scheme] : undefined}\n flex={1}\n height=\"fill\"\n overflow=\"hidden\"\n ref={ref}\n >\n {children}\n </Card>","scheme ? studioScheme[scheme] : undefined","<ThemeProvider scheme={scheme} theme={theme ?? undefined}>\n <Card\n borderBottom={borderBottom}\n borderRight={borderRight}\n className={scheme ? studioScheme[scheme] : undefined}\n flex={1}\n height=\"fill\"\n overflow=\"hidden\"\n ref={ref}\n >\n {children}\n </Card>\n </ThemeProvider>","theme ?? undefined","_c","<Text size={1}>Themer</Text>","() => send({type: 'sidebar.toggle'})","<Tooltip animate content={<Text size={1}>Themer</Text>} portal>\n <Button\n aria-label=\"Themer\"\n icon={ColorWheelIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.toggle'})}\n // The Studio's own navbar buttons go through a wrapper that pins them\n // to this padding, where `@sanity/ui` defaults to a roomier 3\n padding={2}\n selected={open}\n />\n </Tooltip>","props.__internal_actions ?? []","__internal_actions","props.renderDefault({\n ...props,\n __internal_actions: [\n ...(props.__internal_actions ?? []),\n {\n location: 'topbar',\n name: 'themer-topbar',\n render: () => <ThemerNavbarButton />,\n },\n {\n icon: ColorWheelIcon,\n location: 'sidebar',\n name: 'themer-sidebar',\n onAction: () => send({type: 'sidebar.toggle'}),\n selected: open,\n title: 'Themer',\n },\n ],\n })","() => <ThemerNavbarButton />","_c","<ThemerLayout {...props} baseOptions={baseOptions} />","_c"],"sources":["../src/tool/context.ts","../src/tool/imagePalette.ts","../src/tool/options.ts","../src/tool/themes.ts","../src/tool/storage.ts","../src/tool/machine.ts","../src/tool/ScrollArea.css.ts","../src/tool/ScrollArea.tsx","../src/tool/ThemePreview.css.ts","../src/tool/ThemePreview.tsx","../src/tool/ThemeThumbnail.tsx","../src/tool/RemovedThemes.css.ts","../src/tool/RemovedThemes.tsx","../src/tool/TooltipButton.tsx","../src/tool/ImageFileButton.tsx","../src/tool/imageFile.ts","../src/tool/useImagePalette.ts","../src/tool/ImagePaletteSection.css.ts","../src/tool/ImagePaletteSection.tsx","../src/tool/ThemeEditor.css.ts","../src/tool/ThemeEditor.tsx","../src/tool/share.ts","../src/tool/useThemeCodes.ts","../src/tool/PasteThemeDialog.tsx","../src/tool/ThemeCard.css.ts","../src/tool/ThemeCard.tsx","../src/tool/ThemeList.css.ts","../src/tool/ThemeList.tsx","../src/tool/snippet.ts","../src/tool/ThemeSnippetDialog.tsx","../src/tool/ThemerSidebar.css.ts","../src/tool/ThemerSidebar.tsx","../src/tool/ResizableSidebar.css.ts","../src/tool/ResizableSidebar.tsx","../src/tool/usePrefersReducedMotion.ts","../src/tool/useStudioNavbarHeight.ts","../src/tool/useTypedDeferredValue.ts","../src/tool/ThemerLayout.css.ts","../src/tool/ThemerLayout.tsx","../src/tool/ThemerNavbar.tsx","../src/tool/plugin.tsx"],"sourcesContent":["import {createContext, useContext} from 'react'\n\nimport {BuildThemeOptions} from '../theme/options'\nimport {ThemerEvent} from './machine'\nimport {ThemerTheme} from './themes'\n\n/**\n * The flow the sidebar is in: picking a theme from the list, editing one of\n * the user's own themes, or restoring removed ones.\n *\n * @internal\n */\nexport type ThemerView =\n | {name: 'list'}\n | {\n name: 'edit'\n slug: string\n /** Whether the title input should take focus, for themes that were just created */\n focusTitle: boolean\n }\n | {name: 'removed'}\n\n/** @internal */\nexport interface ThemerContextValue {\n /** The theme options the Studio's configured theme was generated from */\n baseOptions: BuildThemeOptions\n /** The themes to pick from, in list order */\n themes: ThemerTheme[]\n /** The removed themes, which can be restored */\n removed: ThemerTheme[]\n /** The applied theme */\n active: ThemerTheme\n /** Object URLs of the images themes took their palette from this session, by slug */\n images: Record<string, string>\n view: ThemerView\n /** Whether the themer sidebar is open */\n open: boolean\n /** Whether the Studio shows twice side by side, in light and dark */\n split: boolean\n /** Whether the Studio is on a small screen, where the sidebar covers it and the split preview stacks */\n mobile: boolean\n /** The height of the Studio navbar, which the sidebar's header matches — `null` until it has rendered */\n navbarHeight: number | null\n /** Sends an event to the themer machine */\n send: (event: ThemerEvent) => void\n}\n\n/** @internal */\nexport const ThemerContext = createContext<ThemerContextValue | null>(null)\n\n/** @internal */\nexport function useThemer(): ThemerContextValue {\n const context = useContext(ThemerContext)\n\n if (!context) {\n throw new Error('useThemer must be used within the `themerTool` plugin')\n }\n\n return context\n}\n","import {type ThemeColorSchemeKey} from '@sanity/ui/theme'\n\nimport {hexToHsl, hslToHex, relativeLuminance} from '../theme/hsl'\nimport {BuildThemeOptions, DEFAULT_BACKGROUND, SCHEMES, SchemeThemeOptions} from '../theme/options'\n\n/**\n * The swatches of an image palette — the same set the Sanity image pipeline\n * derives for uploaded assets (`metadata.palette`), extracted on device here.\n *\n * @internal\n */\nexport type ImagePaletteKey =\n | 'dominant'\n | 'vibrant'\n | 'lightVibrant'\n | 'darkVibrant'\n | 'muted'\n | 'lightMuted'\n | 'darkMuted'\n\n/**\n * An image palette: a hex color per swatch, or `null` when the image has no\n * color that qualifies for it.\n *\n * @internal\n */\nexport type ImagePalette = Record<ImagePaletteKey, string | null>\n\n/** @internal */\nexport const IMAGE_PALETTE_KEYS: ImagePaletteKey[] = [\n 'vibrant',\n 'lightVibrant',\n 'darkVibrant',\n 'muted',\n 'lightMuted',\n 'darkMuted',\n 'dominant',\n]\n\n/**\n * The swatches a theme can be built around — every swatch but the dominant\n * one, which only repeats one of them.\n *\n * @internal\n */\nexport type ImagePaletteVariant = Exclude<ImagePaletteKey, 'dominant'>\n\n/** The variants in the order the tool offers them @internal */\nexport const IMAGE_PALETTE_VARIANTS: ImagePaletteVariant[] = [\n 'muted',\n 'vibrant',\n 'lightMuted',\n 'lightVibrant',\n 'darkMuted',\n 'darkVibrant',\n]\n\n/** @internal */\nexport const IMAGE_PALETTE_TITLES: Record<ImagePaletteKey, string> = {\n dominant: 'Dominant',\n vibrant: 'Vibrant',\n lightVibrant: 'Light vibrant',\n darkVibrant: 'Dark vibrant',\n muted: 'Muted',\n lightMuted: 'Light muted',\n darkMuted: 'Dark muted',\n}\n\n/** Pixels the way a canvas hands them out: rows of RGBA bytes @internal */\nexport interface PixelData {\n data: ArrayLike<number>\n width: number\n height: number\n}\n\ninterface Bucket {\n count: number\n r: number\n g: number\n b: number\n}\n\ninterface Candidate {\n hex: string\n population: number\n saturation: number\n lightness: number\n}\n\n/** What a swatch looks for: a lightness and saturation to aim at, within bounds */\ninterface Target {\n lightness: [target: number, min: number, max: number]\n saturation: [target: number, min: number, max: number]\n}\n\n/**\n * The swatch targets of the Vibrant algorithm (the one behind Sanity's image\n * palettes), in the order they get to claim colors.\n */\nconst TARGETS: Array<[key: Exclude<ImagePaletteKey, 'dominant'>, target: Target]> = [\n ['vibrant', {lightness: [0.5, 0.3, 0.7], saturation: [1, 0.35, 1]}],\n ['lightVibrant', {lightness: [0.74, 0.55, 1], saturation: [1, 0.35, 1]}],\n ['darkVibrant', {lightness: [0.26, 0, 0.45], saturation: [1, 0.35, 1]}],\n ['muted', {lightness: [0.5, 0.3, 0.7], saturation: [0.3, 0, 0.4]}],\n ['lightMuted', {lightness: [0.74, 0.55, 1], saturation: [0.3, 0, 0.4]}],\n ['darkMuted', {lightness: [0.26, 0, 0.45], saturation: [0.3, 0, 0.4]}],\n]\n\nconst SATURATION_WEIGHT = 3\nconst LIGHTNESS_WEIGHT = 6.5\nconst POPULATION_WEIGHT = 0.5\n\n/** Pixels this transparent are not part of the picture */\nconst MINIMUM_ALPHA = 125\n\n/** Near-black and near-white pixels are backdrop rather than color */\nconst MINIMUM_LIGHTNESS = 0.05\nconst MAXIMUM_LIGHTNESS = 0.95\n\n/** How many of the 8 bits per channel survive the quantization */\nconst QUANTIZE_BITS = 5\n\n/**\n * Extracts the color palette of an image from its pixels, the way the Vibrant\n * algorithm does: the colors are quantized into a coarse histogram, and each\n * swatch claims the color that comes closest to its target lightness and\n * saturation — weighted by how much of the image it covers — that no other\n * swatch has claimed yet. The dominant swatch is simply the most common\n * color. Runs anywhere pixels come from, so it can run on device.\n *\n * @internal\n */\nexport function extractImagePalette(pixels: PixelData): ImagePalette {\n const candidates = quantize(pixels)\n const palette: ImagePalette = {\n dominant: null,\n vibrant: null,\n lightVibrant: null,\n darkVibrant: null,\n muted: null,\n lightMuted: null,\n darkMuted: null,\n }\n\n if (candidates.length === 0) return palette\n\n const maximumPopulation = Math.max(...candidates.map((candidate) => candidate.population))\n const claimed = new Set<string>()\n\n for (const [key, target] of TARGETS) {\n let best: Candidate | null = null\n let bestScore = -Infinity\n\n for (const candidate of candidates) {\n if (claimed.has(candidate.hex) || !matchesTarget(candidate, target)) continue\n\n const score = scoreCandidate(candidate, target, maximumPopulation)\n\n if (score > bestScore) {\n best = candidate\n bestScore = score\n }\n }\n\n if (best) {\n palette[key] = best.hex\n claimed.add(best.hex)\n }\n }\n\n palette.dominant = candidates.reduce((most, candidate) =>\n candidate.population > most.population ? candidate : most,\n ).hex\n\n return palette\n}\n\n/**\n * Turns the pixels into a histogram of quantized colors, each averaged over\n * the pixels it stands for, leaving out transparent pixels and the near-black\n * and near-white ones that are backdrop rather than color.\n */\nfunction quantize(pixels: PixelData): Candidate[] {\n const {data} = pixels\n const buckets = new Map<number, Bucket>()\n const shift = 8 - QUANTIZE_BITS\n const length = Math.min(data.length, pixels.width * pixels.height * 4)\n\n for (let offset = 0; offset + 3 < length; offset += 4) {\n const r = data[offset]\n const g = data[offset + 1]\n const b = data[offset + 2]\n const a = data[offset + 3]\n\n if (a < MINIMUM_ALPHA) continue\n\n const lightness = (Math.max(r, g, b) + Math.min(r, g, b)) / 510\n\n if (lightness < MINIMUM_LIGHTNESS || lightness > MAXIMUM_LIGHTNESS) continue\n\n const key =\n ((r >> shift) << (QUANTIZE_BITS * 2)) | ((g >> shift) << QUANTIZE_BITS) | (b >> shift)\n const bucket = buckets.get(key)\n\n if (bucket) {\n bucket.count++\n bucket.r += r\n bucket.g += g\n bucket.b += b\n } else {\n buckets.set(key, {count: 1, r, g, b})\n }\n }\n\n const candidates: Candidate[] = []\n\n for (const bucket of buckets.values()) {\n const r = Math.round(bucket.r / bucket.count)\n const g = Math.round(bucket.g / bucket.count)\n const b = Math.round(bucket.b / bucket.count)\n const {saturation, lightness} = saturationAndLightness(r, g, b)\n\n candidates.push({hex: rgbToHex(r, g, b), population: bucket.count, saturation, lightness})\n }\n\n return candidates\n}\n\nfunction matchesTarget(candidate: Candidate, target: Target): boolean {\n return (\n candidate.lightness >= target.lightness[1] &&\n candidate.lightness <= target.lightness[2] &&\n candidate.saturation >= target.saturation[1] &&\n candidate.saturation <= target.saturation[2]\n )\n}\n\nfunction scoreCandidate(candidate: Candidate, target: Target, maximumPopulation: number): number {\n return (\n SATURATION_WEIGHT * (1 - Math.abs(candidate.saturation - target.saturation[0])) +\n LIGHTNESS_WEIGHT * (1 - Math.abs(candidate.lightness - target.lightness[0])) +\n POPULATION_WEIGHT * (candidate.population / maximumPopulation)\n )\n}\n\nfunction saturationAndLightness(r: number, g: number, b: number) {\n const max = Math.max(r, g, b) / 255\n const min = Math.min(r, g, b) / 255\n const lightness = (max + min) / 2\n const delta = max - min\n const saturation =\n delta === 0 ? 0 : lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min)\n\n return {saturation, lightness}\n}\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n return `#${[r, g, b].map((channel) => channel.toString(16).padStart(2, '0')).join('')}`\n}\n\n/** Reads `#rgb` and `#rrggbb` alike — stored palettes may hold either */\nfunction hexToRgb(hex: string): [number, number, number] {\n const expanded =\n hex.length === 4 ? `${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex.slice(1)\n\n return [\n parseInt(expanded.slice(0, 2), 16),\n parseInt(expanded.slice(2, 4), 16),\n parseInt(expanded.slice(4, 6), 16),\n ]\n}\n\n/** Mixes `amount` of `color` into `base` */\nfunction tint(base: string, color: string, amount: number): string {\n const [r1, g1, b1] = hexToRgb(base)\n const [r2, g2, b2] = hexToRgb(color)\n\n return rgbToHex(\n Math.round(r1 + (r2 - r1) * amount),\n Math.round(g1 + (g2 - g1) * amount),\n Math.round(b1 + (b2 - b1) * amount),\n )\n}\n\n/** How much of the palette tints each scheme's stock background */\nconst BACKGROUND_TINT = {light: 0.08, dark: 0.3}\n\n/**\n * The WCAG AA contrast for normal text — what a button label needs against\n * the button. Sanity UI labels solid buttons in white on the accent's 500\n * tint in the light scheme and in black on its 400 tint in the dark scheme\n * (lighter than the accent, so an accent that passes against black passes\n * there too).\n */\nconst MINIMUM_ACCENT_CONTRAST = 4.5\nconst BUTTON_LABEL: Record<ThemeColorSchemeKey, string> = {light: '#ffffff', dark: '#000000'}\nconst ACCENT_LIGHTNESS_STEP = 0.01\n\nfunction contrastRatio(a: string, b: string): number {\n const [lighter, darker] = [relativeLuminance(a), relativeLuminance(b)].sort((x, y) => y - x)\n\n return (lighter + 0.05) / (darker + 0.05)\n}\n\n/**\n * Makes an accent readable as a button in the given scheme: a light scheme\n * accent is darkened until white text on it reaches AA contrast, a dark\n * scheme accent lightened until black text does — as far as the accent's\n * hue and saturation allow. Accents that already pass come back untouched.\n *\n * @internal\n */\nexport function readableAccent(hex: string, scheme: ThemeColorSchemeKey): string {\n const label = BUTTON_LABEL[scheme]\n const {h, s} = hexToHsl(hex)\n let {l} = hexToHsl(hex)\n let current = hex\n\n while (contrastRatio(current, label) < MINIMUM_ACCENT_CONTRAST) {\n const next = scheme === 'light' ? l - ACCENT_LIGHTNESS_STEP : l + ACCENT_LIGHTNESS_STEP\n\n if (next < 0 || next > 1) break\n\n l = next\n current = hslToHex({h, s, l})\n }\n\n return current\n}\n\n/** The accent a variant gives a scheme, made readable on a button */\nfunction variantAccent(\n palette: ImagePalette,\n variant: ImagePaletteVariant | null,\n scheme: ThemeColorSchemeKey,\n): string | null {\n const swatch =\n (variant ? palette[variant] : null) ??\n palette.vibrant ??\n (scheme === 'light' ? palette.darkVibrant : palette.lightVibrant) ??\n palette.dominant\n\n return swatch === null ? null : readableAccent(swatch, scheme)\n}\n\n/**\n * Derives theme options from an image palette: the accent of both schemes is\n * the swatch of the chosen variant — or, without one, the vibrant swatch,\n * each scheme falling back to the vibrant swatch of its own end of the scale —\n * adjusted until it is readable as a button. The muted swatch becomes the text color, and the light and dark muted\n * swatches tint the stock backgrounds of their scheme — enough to carry the\n * image's tone without giving up a usable canvas. Whatever the palette lacks\n * stays at the theme defaults.\n *\n * @internal\n */\nexport function optionsFromImagePalette(\n palette: ImagePalette,\n variant?: ImagePaletteVariant,\n): BuildThemeOptions {\n const options: BuildThemeOptions = {}\n\n const light = schemeFromPalette(\n variantAccent(palette, variant ?? null, 'light'),\n palette.muted ?? palette.darkMuted,\n palette.lightMuted ?? palette.muted ?? palette.lightVibrant,\n 'light',\n )\n const dark = schemeFromPalette(\n variantAccent(palette, variant ?? null, 'dark'),\n palette.muted ?? palette.lightMuted,\n palette.darkMuted ?? palette.muted ?? palette.darkVibrant,\n 'dark',\n )\n\n if (light) options.light = light\n if (dark) options.dark = dark\n\n return options\n}\n\nfunction schemeFromPalette(\n accent: string | null,\n text: string | null,\n backgroundTint: string | null,\n scheme: 'light' | 'dark',\n): SchemeThemeOptions | null {\n const options: SchemeThemeOptions = {}\n\n if (accent) options.accent = accent\n if (text) options.text = text\n if (backgroundTint) {\n options.background = tint(DEFAULT_BACKGROUND[scheme], backgroundTint, BACKGROUND_TINT[scheme])\n }\n\n return Object.keys(options).length > 0 ? options : null\n}\n\n/**\n * Applies an image palette to existing theme options: the colors the palette\n * provides replace the current ones scheme by scheme, and whatever it does\n * not touch — like the contrast — stays as it is.\n *\n * @internal\n */\nexport function applyImagePalette(\n options: BuildThemeOptions,\n palette: ImagePalette,\n variant?: ImagePaletteVariant,\n): BuildThemeOptions {\n const derived = optionsFromImagePalette(palette, variant)\n const next: BuildThemeOptions = {...options}\n\n for (const scheme of SCHEMES) {\n if (derived[scheme]) next[scheme] = {...options[scheme], ...derived[scheme]}\n }\n\n return next\n}\n\n/**\n * The variant a theme is built around: the one whose swatch is the theme's\n * accent, or `null` when the accent comes from somewhere else.\n *\n * @internal\n */\nexport function currentImageVariant(\n options: BuildThemeOptions,\n palette: ImagePalette,\n): ImagePaletteVariant | null {\n const accent = options.light?.accent\n\n if (!accent) return null\n\n return (\n IMAGE_PALETTE_VARIANTS.find(\n (variant) => palette[variant] !== null && variantAccent(palette, variant, 'light') === accent,\n ) ?? null\n )\n}\n\n/**\n * How much a swatch has going for it as an accent: saturation first, then\n * closeness to mid lightness — the range where an accent reads best.\n */\nfunction interest(hex: string): number {\n const {s, l} = hexToHsl(hex)\n\n return 0.1 + 0.55 * s + 0.35 * (1 - Math.abs(l - 0.5) * 2)\n}\n\n/**\n * Picks a variant for the \"I'm feeling lucky\" button: a random one, weighted\n * by how interesting its swatch is as an accent, and never the one the theme\n * already uses while there are others to pick from.\n *\n * @internal\n */\nexport function pickLuckyVariant(\n palette: ImagePalette,\n options: {exclude?: ImagePaletteVariant | null; random?: () => number} = {},\n): ImagePaletteVariant | null {\n const {exclude = null, random = Math.random} = options\n let candidates = IMAGE_PALETTE_VARIANTS.filter((variant) => palette[variant] !== null)\n\n if (candidates.length > 1) {\n candidates = candidates.filter((variant) => variant !== exclude)\n }\n\n if (candidates.length === 0) return null\n\n const weights = candidates.map((variant) => interest(palette[variant]!))\n const total = weights.reduce((sum, weight) => sum + weight, 0)\n let pick = random() * total\n\n for (const [index, variant] of candidates.entries()) {\n pick -= weights[index]\n\n if (pick < 0) return variant\n }\n\n return candidates.at(-1) ?? null\n}\n\n/** A theme title for the image a palette came from, e.g. `sunset-beach.jpg` → `sunset beach` @internal */\nexport function titleFromFileName(name: string): string {\n return (\n name\n .replace(/\\.[^.]+$/, '')\n .replace(/[-_]+/g, ' ')\n .trim() || 'Image theme'\n )\n}\n","import {\n BuildThemeOptions,\n DEFAULT_ACCENT,\n DEFAULT_BACKGROUND,\n DEFAULT_CONTRAST,\n deriveTextColor,\n ResolvedSchemeOptions,\n resolveThemeOptions,\n SCHEMES,\n SchemeThemeOptions,\n} from '../theme/options'\n\n/**\n * Reduces theme options to the minimal object that recreates them through\n * `buildTheme`: in each scheme the accent is dropped when it is the stock\n * one, the text color when it matches the one derived from the accent, and\n * the background and contrast when they match the defaults — and a scheme\n * with nothing left is dropped altogether. Options that boil down to the\n * stock Studio theme reduce to `null` — they need nothing from this package.\n *\n * @internal\n */\nexport function minimizeOptions(options: BuildThemeOptions): BuildThemeOptions | null {\n const resolved = resolveThemeOptions(options)\n const minimized: BuildThemeOptions = {}\n\n for (const scheme of SCHEMES) {\n const values = resolved[scheme]\n const kept: SchemeThemeOptions = {}\n\n if (values.accent !== DEFAULT_ACCENT) kept.accent = values.accent\n if (values.text !== deriveTextColor(values.accent)) kept.text = values.text\n if (values.background !== DEFAULT_BACKGROUND[scheme]) kept.background = values.background\n if (values.contrast !== DEFAULT_CONTRAST) kept.contrast = values.contrast\n\n if (Object.keys(kept).length > 0) minimized[scheme] = kept\n }\n\n return Object.keys(minimized).length === 0 ? null : minimized\n}\n\n/**\n * Whether two sets of theme options generate the same theme, comparing their\n * resolved forms so that derived and explicit values (and hex casing) don't\n * matter.\n *\n * @internal\n */\nexport function sameOptions(a: BuildThemeOptions, b: BuildThemeOptions): boolean {\n const left = resolveThemeOptions(a)\n const right = resolveThemeOptions(b)\n\n return SCHEMES.every((scheme) => sameScheme(left[scheme], right[scheme]))\n}\n\nfunction sameScheme(a: ResolvedSchemeOptions, b: ResolvedSchemeOptions): boolean {\n return (\n a.accent === b.accent &&\n a.text === b.text &&\n a.background === b.background &&\n a.contrast === b.contrast\n )\n}\n","import {BuildThemeOptions} from '../theme/options'\nimport {presets} from '../theme/presets'\nimport {ImagePalette} from './imagePalette'\nimport {sameOptions} from './options'\n\n/**\n * Where a theme in the themer list comes from: the theme the Studio config\n * was generated from, one of the presets defined in code, or one the user\n * added in the tool. Only custom themes are editable — the others are\n * duplicated into a custom theme first.\n */\ntype ThemerThemeSource = 'config' | 'preset' | 'custom'\n\n/** A theme the themer tool lists @internal */\nexport interface ThemerTheme {\n slug: string\n title: string\n options: BuildThemeOptions\n source: ThemerThemeSource\n /** The palette of the image the theme's colors were taken from, for custom themes */\n palette?: ImagePalette\n}\n\n/** A theme the user added in the tool, as persisted @internal */\nexport interface CustomTheme {\n slug: string\n title: string\n options: BuildThemeOptions\n /** The palette of the image the theme's colors were taken from */\n palette?: ImagePalette\n}\n\n/** The persisted themer state @internal */\nexport interface ThemerState {\n /**\n * The slug of the applied theme — `null` applies nothing on top of the\n * Studio's configured theme\n */\n active: string | null\n /** The themes the user added */\n custom: CustomTheme[]\n /** The slugs of the themes the user removed from the list */\n removed: string[]\n /**\n * The slugs of the themes in the order the user arranged them — themes not\n * in it (new presets, themes added since) follow in their default order\n */\n order: string[]\n}\n\n/** The slug of the theme the Studio config was generated from @internal */\nexport const CONFIG_SLUG = 'config'\n\n/** The title of the theme the Studio config was generated from */\nconst CONFIG_TITLE = 'Studio config'\n\n/** The title new themes start out with @internal */\nexport const UNTITLED_THEME = 'Untitled theme'\n\n/** @internal */\nexport const initialThemerState: ThemerState = {active: null, custom: [], removed: [], order: []}\n\n/** The themes the tool works with, derived from the persisted state @internal */\nexport interface ResolvedThemes {\n /** The themes to pick from, in list order */\n themes: ThemerTheme[]\n /** The removed themes, which can be restored */\n removed: ThemerTheme[]\n /** The applied theme */\n active: ThemerTheme\n}\n\n/**\n * Resolves the list of themes: the configured theme first, then the presets\n * (minus the ones that would only repeat the configured theme), then the\n * user's own themes — rearranged into the order the user dragged them into,\n * with themes that order does not know about kept in that default order after\n * the ones it does. Removed themes are set aside so they can be restored, and\n * the active theme falls back to the configured one when its slug no longer\n * resolves.\n *\n * @internal\n */\nexport function resolveThemes(state: ThemerState, baseOptions: BuildThemeOptions): ResolvedThemes {\n const config: ThemerTheme = {\n slug: CONFIG_SLUG,\n title: CONFIG_TITLE,\n options: baseOptions,\n source: 'config',\n }\n\n const all: ThemerTheme[] = [config]\n\n for (const preset of presets) {\n if (!sameOptions(preset.options, baseOptions)) {\n all.push({slug: preset.slug, title: preset.title, options: preset.options, source: 'preset'})\n }\n }\n\n for (const theme of state.custom) {\n all.push({\n slug: theme.slug,\n title: theme.title,\n options: theme.options,\n source: 'custom',\n ...(theme.palette ? {palette: theme.palette} : {}),\n })\n }\n\n const position = new Map(state.order.map((slug, index) => [slug, index]))\n const rank = (theme: ThemerTheme) => position.get(theme.slug) ?? state.order.length\n\n // A stable sort: the themes the order does not know about keep their default order\n all.sort((a, b) => rank(a) - rank(b))\n\n const removedSlugs = new Set(state.removed)\n const themes = all.filter((theme) => !removedSlugs.has(theme.slug))\n const removed = all.filter((theme) => removedSlugs.has(theme.slug))\n const active = themes.find((theme) => theme.slug === state.active) ?? config\n\n return {themes, removed, active}\n}\n\n/**\n * Creates a custom theme with a slug that cannot collide with the presets or\n * the configured theme.\n *\n * @internal\n */\nexport function createCustomTheme(\n title: string,\n options: BuildThemeOptions,\n palette?: ImagePalette,\n): CustomTheme {\n return {slug: `custom-${randomId()}`, title, options, ...(palette ? {palette} : {})}\n}\n\n/** The title of a duplicated theme @internal */\nexport function duplicateTitle(title: string): string {\n return `${displayTitle(title)} copy`\n}\n\n/** The title to show for a theme, falling back when the user cleared it @internal */\nexport function displayTitle(title: string): string {\n return title.trim() || UNTITLED_THEME\n}\n\nfunction randomId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n\n return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`\n}\n","import {isColor} from '../lib/mix'\nimport {\n BuildThemeOptions,\n MAXIMUM_CONTRAST,\n MINIMUM_CONTRAST,\n SCHEMES,\n SchemeThemeOptions,\n} from '../theme/options'\nimport {presets} from '../theme/presets'\nimport {IMAGE_PALETTE_KEYS, ImagePalette} from './imagePalette'\nimport {\n CONFIG_SLUG,\n createCustomTheme,\n CustomTheme,\n displayTitle,\n initialThemerState,\n ThemerState,\n} from './themes'\n\nconst STORAGE_KEY = 'sanityStudio:themer:state'\n\n/**\n * Where earlier versions of the tool kept their single draft theme — it is\n * migrated into a custom theme, so a draft survives the upgrade.\n */\nconst LEGACY_STORAGE_KEY = 'sanityStudio:themer:options'\n\n/** The title a draft from before themes had titles is imported under */\nconst LEGACY_DRAFT_TITLE = 'Draft theme'\n\nfunction sanitizeColor(value: unknown): string | null {\n return typeof value === 'string' && isColor(value) ? value.toLowerCase() : null\n}\n\nfunction sanitizeContrast(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value)\n ? Math.min(MAXIMUM_CONTRAST, Math.max(MINIMUM_CONTRAST, value))\n : null\n}\n\nfunction sanitizeSchemeOptions(value: unknown): SchemeThemeOptions {\n const options: SchemeThemeOptions = {}\n\n if (!value || typeof value !== 'object') return options\n\n const accent = sanitizeColor(Reflect.get(value, 'accent'))\n const text = sanitizeColor(Reflect.get(value, 'text'))\n const background = sanitizeColor(Reflect.get(value, 'background'))\n const contrast = sanitizeContrast(Reflect.get(value, 'contrast'))\n\n if (accent) options.accent = accent\n if (text) options.text = text\n if (background) options.background = background\n if (contrast !== null) options.contrast = contrast\n\n return options\n}\n\n/**\n * Options from before they were grouped by scheme: one accent, text color and\n * contrast for both schemes, and a background per scheme. Both schemes get\n * the shared colors, so the theme keeps looking the same.\n */\nfunction sanitizeFlatOptions(value: object): BuildThemeOptions | null {\n const accent = sanitizeColor(Reflect.get(value, 'accent'))\n\n if (!accent) return null\n\n const shared: SchemeThemeOptions = {accent}\n const text = sanitizeColor(Reflect.get(value, 'text'))\n const contrast = sanitizeContrast(Reflect.get(value, 'contrast'))\n\n if (text) shared.text = text\n if (contrast !== null) shared.contrast = contrast\n\n const backgrounds: unknown = Reflect.get(value, 'background')\n const options: BuildThemeOptions = {}\n\n for (const scheme of SCHEMES) {\n const background =\n backgrounds && typeof backgrounds === 'object'\n ? sanitizeColor(Reflect.get(backgrounds, scheme))\n : null\n\n options[scheme] = background ? {...shared, background} : {...shared}\n }\n\n return options\n}\n\nfunction sanitizeOptions(value: unknown): BuildThemeOptions | null {\n if (!value || typeof value !== 'object') return null\n\n // The flat shape always had an accent, the scheme shape never has one at the top\n if ('accent' in value) {\n return sanitizeFlatOptions(value)\n }\n\n const options: BuildThemeOptions = {}\n\n for (const scheme of SCHEMES) {\n const schemeOptions = sanitizeSchemeOptions(Reflect.get(value, scheme))\n\n if (Object.keys(schemeOptions).length > 0) options[scheme] = schemeOptions\n }\n\n return options\n}\n\nfunction isReservedSlug(slug: string): boolean {\n return slug === CONFIG_SLUG || presets.some((preset) => preset.slug === slug)\n}\n\nfunction sanitizeCustomTheme(value: unknown): CustomTheme | null {\n if (!value || typeof value !== 'object') return null\n\n const slug: unknown = Reflect.get(value, 'slug')\n\n if (typeof slug !== 'string' || !slug || isReservedSlug(slug)) return null\n\n const options = sanitizeOptions(Reflect.get(value, 'options'))\n\n if (!options) return null\n\n const title: unknown = Reflect.get(value, 'title')\n const palette = sanitizePalette(Reflect.get(value, 'palette'))\n\n return {\n slug,\n title: displayTitle(typeof title === 'string' ? title : ''),\n options,\n ...(palette ? {palette} : {}),\n }\n}\n\nfunction sanitizePalette(value: unknown): ImagePalette | null {\n if (!value || typeof value !== 'object') return null\n\n const palette: Partial<ImagePalette> = {}\n let swatches = 0\n\n for (const key of IMAGE_PALETTE_KEYS) {\n const swatch = sanitizeColor(Reflect.get(value, key))\n\n palette[key] = swatch\n if (swatch) swatches++\n }\n\n // oxlint-disable-next-line no-unsafe-type-assertion -- the loop assigns every key\n return swatches > 0 ? (palette as ImagePalette) : null\n}\n\nfunction sanitizeState(value: unknown): ThemerState {\n if (!value || typeof value !== 'object') return initialThemerState\n\n const custom: CustomTheme[] = []\n const rawCustom: unknown = Reflect.get(value, 'custom')\n\n if (Array.isArray(rawCustom)) {\n for (const item of rawCustom) {\n const theme = sanitizeCustomTheme(item)\n\n if (theme && !custom.some((existing) => existing.slug === theme.slug)) {\n custom.push(theme)\n }\n }\n }\n\n const removed: string[] = []\n const rawRemoved: unknown = Reflect.get(value, 'removed')\n\n if (Array.isArray(rawRemoved)) {\n for (const slug of rawRemoved) {\n const known =\n typeof slug === 'string' &&\n slug !== CONFIG_SLUG &&\n (presets.some((preset) => preset.slug === slug) ||\n custom.some((theme) => theme.slug === slug))\n\n if (known && !removed.includes(slug)) removed.push(slug)\n }\n }\n\n const order: string[] = []\n const rawOrder: unknown = Reflect.get(value, 'order')\n\n if (Array.isArray(rawOrder)) {\n for (const slug of rawOrder) {\n const known =\n typeof slug === 'string' &&\n (slug === CONFIG_SLUG ||\n presets.some((preset) => preset.slug === slug) ||\n custom.some((theme) => theme.slug === slug))\n\n if (known && !order.includes(slug)) order.push(slug)\n }\n }\n\n const rawActive: unknown = Reflect.get(value, 'active')\n\n return {active: typeof rawActive === 'string' ? rawActive : null, custom, removed, order}\n}\n\nfunction readLegacyState(): ThemerState | null {\n const raw = localStorage.getItem(LEGACY_STORAGE_KEY)\n\n if (!raw) return null\n\n const options = sanitizeOptions(JSON.parse(raw))\n\n if (!options) return null\n\n const theme = createCustomTheme(LEGACY_DRAFT_TITLE, options)\n\n return {active: theme.slug, custom: [theme], removed: [], order: []}\n}\n\n/**\n * Restores the themer state from localStorage, so the user's themes survive\n * studio reloads. Falls back to the initial state when nothing usable is\n * stored.\n *\n * @internal\n */\nexport function readStoredState(): ThemerState {\n try {\n if (typeof localStorage === 'undefined') return initialThemerState\n\n const raw = localStorage.getItem(STORAGE_KEY)\n\n if (raw) return sanitizeState(JSON.parse(raw))\n\n return readLegacyState() ?? initialThemerState\n } catch {\n return initialThemerState\n }\n}\n\n/** @internal */\nexport function writeStoredState(state: ThemerState): void {\n try {\n if (typeof localStorage === 'undefined') return\n\n localStorage.setItem(STORAGE_KEY, JSON.stringify(state))\n localStorage.removeItem(LEGACY_STORAGE_KEY)\n } catch {\n // Storage can be unavailable (e.g. private browsing) — themes just won't persist\n }\n}\n","import {assign, not, or, setup, SnapshotFrom, stateIn} from 'xstate'\n\nimport {BuildThemeOptions} from '../theme/options'\nimport {ImagePalette} from './imagePalette'\nimport {writeStoredState} from './storage'\nimport {\n CONFIG_SLUG,\n createCustomTheme,\n duplicateTitle,\n resolveThemes,\n ThemerState,\n UNTITLED_THEME,\n} from './themes'\n\n/**\n * How long the layout's motions — the panel's and the split preview's, and\n * the cross-fade to another theme — are taken to last when the layout never\n * says: it reports every view transition it starts, so this only ends a\n * motion that has none (reduced motion, a browser without view transitions),\n * where the `moving` and `switching` tags have nothing to affect. Generous,\n * so that it never runs out before a slow deferred commit starts the real one.\n *\n * @internal\n */\nexport const MOTION_DURATION = 2000\n\n/** What the themer machine starts from @internal */\nexport interface ThemerInput {\n /** The theme options the Studio's configured theme was generated from */\n baseOptions: BuildThemeOptions\n /** The persisted state of an earlier session */\n stored: ThemerState\n}\n\n/** @internal */\nexport interface ThemerMachineContext extends ThemerState {\n baseOptions: BuildThemeOptions\n /** The theme open in the editor, while the `flow` is `edit` */\n editing: {\n slug: string\n /** Whether the title input should take focus, for themes that were just created */\n focusTitle: boolean\n } | null\n /**\n * Object URLs of the images that themes took their palette from, by theme\n * slug — kept for the session only, so the image can be shown right after\n * it was picked\n */\n images: Record<string, string>\n}\n\n/** @internal */\nexport type ThemerEvent =\n | {type: 'sidebar.toggle'}\n | {type: 'sidebar.close'}\n /**\n * A view transition of the layout is under way — the panel or the split copy\n * arriving or leaving, the Studio giving way, taking room or cross-fading to\n * another theme — and the layout is no longer `moving` or `switching` for\n * the machine\n */\n | {type: 'layout.transitioned'}\n /**\n * Switches between showing the Studio once, in its own appearance, and\n * twice side by side, in light and dark\n */\n | {type: 'preview.toggle'}\n /** Applies a theme to the whole Studio */\n | {type: 'theme.pick'; slug: string}\n /**\n * Adds a theme and opens it in the editor — based on the applied theme, or\n * on the given title, options and image palette (e.g. from an image)\n */\n | {\n type: 'theme.add'\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n }\n /** Adds a copy of a theme and opens it in the editor */\n | {type: 'theme.duplicate'; slug: string}\n /** Adds a theme someone shared as a code, and applies it — staying in the list */\n | {type: 'theme.import'; title: string; options: BuildThemeOptions}\n /** Opens one of the user's own themes in the editor */\n | {type: 'theme.edit'; slug: string}\n | {\n type: 'theme.update'\n slug: string\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n }\n /** Takes a theme out of the list — it can be restored until it is deleted */\n | {type: 'theme.remove'; slug: string}\n /** Puts a removed theme back in the list */\n | {type: 'theme.restore'; slug: string}\n /** Deletes one of the user's own themes for good */\n | {type: 'theme.delete'; slug: string}\n /** Rearranges the list: the slugs of the listed themes, in their new order */\n | {type: 'theme.reorder'; order: string[]}\n /** Back to picking a theme */\n | {type: 'flow.list'}\n /** On to restoring removed themes */\n | {type: 'flow.removed'}\n\nfunction themesOf(context: ThemerMachineContext) {\n return resolveThemes(context, context.baseOptions)\n}\n\nfunction revokeObjectUrl(url: string) {\n if (typeof URL !== 'undefined' && typeof URL.revokeObjectURL === 'function') {\n URL.revokeObjectURL(url)\n }\n}\n\n/**\n * The state of the themer tool: three parallel regions, whether the\n * `sidebar` is open, which `flow` it is in — picking a theme from the `list`,\n * `edit`ing one of the user's own themes, or restoring `removed` ones — and\n * whether the `preview` shows the Studio once (`single`) or in light and dark\n * side by side (`split`). The context carries the persisted state (the\n * applied theme, the user's themes and what was removed) alongside what the\n * flows need.\n *\n * The sidebar and the preview pass through `opening`/`closing` and\n * `splitting`/`unsplitting` on their way, states tagged for the UI: `panel`\n * and `split` say what shows (the panel from `opening` on, the split copy from\n * `splitting` on), `moving` that the Studio is about to give way or take room\n * — which is when, and only when, the layout lets a view transition animate\n * it. A fourth region, `theme`, is `switching` (its tag) right after an event\n * that applies another theme — picking one, adding, duplicating, importing or\n * editing one, removing or deleting the applied one — when the layout\n * cross-fades to it; edits to the applied theme's colors are not switches,\n * they follow the pointer. These states last until the layout reports its\n * transition under way — every boundary reports, the panel's and the copy's\n * entering and leaving as much as the Studio's morphing — or for\n * `MOTION_DURATION` when nothing animates.\n *\n * Theme operations are handled in every flow, and the flows leave on their\n * own when they lose their subject: the editor when its theme is removed or\n * deleted, and the removed view when its last theme is restored or deleted.\n *\n * @internal\n */\nexport const themerMachine = setup({\n // XState reads `types` for their types only — the values are never used\n types: {\n // oxlint-disable-next-line no-unsafe-type-assertion -- type-level placeholder\n context: {} as ThemerMachineContext,\n // oxlint-disable-next-line no-unsafe-type-assertion -- type-level placeholder\n events: {} as ThemerEvent,\n // oxlint-disable-next-line no-unsafe-type-assertion -- type-level placeholder\n input: {} as ThemerInput,\n },\n guards: {\n isCustomTheme: ({context}, params: {slug: string}) =>\n themesOf(context).themes.some(\n (theme) => theme.slug === params.slug && theme.source === 'custom',\n ),\n isEditingListedTheme: ({context}) =>\n context.editing !== null &&\n themesOf(context).themes.some(\n (theme) => theme.slug === context.editing?.slug && theme.source === 'custom',\n ),\n hasRemovedThemes: ({context}) => themesOf(context).removed.length > 0,\n sidebarShown: or([stateIn({sidebar: 'opening'}), stateIn({sidebar: 'open'})]),\n // Only another theme than the applied one changes anything to cross-fade to\n isAnotherTheme: ({context}, params: {slug: string}) =>\n params.slug !== (context.active ?? CONFIG_SLUG),\n // Removing or deleting the applied theme is what changes the applied theme\n isAppliedTheme: ({context}, params: {slug: string}) => params.slug === context.active,\n // A theme added from given options (an image's) looks different from the applied one\n hasOptions: (_, params: {options: BuildThemeOptions | undefined}) =>\n params.options !== undefined,\n },\n actions: {\n // The state that survives sessions is written as the machine starts — that\n // completes the migration of what an earlier version stored — and on\n // every change to it\n persist: ({context}) => {\n const {active, custom, removed, order} = context\n\n writeStoredState({active, custom, removed, order})\n },\n // The image of a replaced palette or a deleted theme is released from memory\n revokeImage: (_, params: {url: string | undefined}) => {\n if (params.url) revokeObjectUrl(params.url)\n },\n pick: assign((_, params: {slug: string}) => ({\n active: params.slug === CONFIG_SLUG ? null : params.slug,\n })),\n add: assign(\n (\n {context},\n params: {\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n },\n ) => {\n const theme = createCustomTheme(\n params.title ?? UNTITLED_THEME,\n params.options ?? themesOf(context).active.options,\n params.palette,\n )\n\n return {\n active: theme.slug,\n custom: [...context.custom, theme],\n // A theme named after its image needs no renaming right away\n editing: {slug: theme.slug, focusTitle: params.title === undefined},\n images: params.imageUrl\n ? {...context.images, [theme.slug]: params.imageUrl}\n : context.images,\n }\n },\n ),\n import: assign(({context}, params: {title: string; options: BuildThemeOptions}) => {\n const theme = createCustomTheme(params.title, params.options)\n\n return {active: theme.slug, custom: [...context.custom, theme]}\n }),\n duplicate: assign(({context}, params: {slug: string}) => {\n const {themes, removed} = themesOf(context)\n const source = [...themes, ...removed].find((theme) => theme.slug === params.slug)\n\n if (!source) return {}\n\n const theme = createCustomTheme(duplicateTitle(source.title), source.options, source.palette)\n\n return {\n active: theme.slug,\n custom: [...context.custom, theme],\n editing: {slug: theme.slug, focusTitle: true},\n }\n }),\n startEditing: assign((_, params: {slug: string}) => ({\n active: params.slug,\n editing: {slug: params.slug, focusTitle: false},\n })),\n stopEditing: assign({editing: null}),\n update: assign(\n (\n {context},\n params: {\n slug: string\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n },\n ) => ({\n custom: context.custom.map((theme) => {\n if (theme.slug !== params.slug) return theme\n\n // Keep the options identity when only the title changes, so the\n // applied theme is not rebuilt on every keystroke\n return {\n ...theme,\n title: params.title ?? theme.title,\n options: params.options ?? theme.options,\n ...(params.palette ? {palette: params.palette} : {}),\n }\n }),\n images:\n params.imageUrl && context.custom.some((theme) => theme.slug === params.slug)\n ? {...context.images, [params.slug]: params.imageUrl}\n : context.images,\n }),\n ),\n remove: assign(({context}, params: {slug: string}) => {\n if (params.slug === CONFIG_SLUG || context.removed.includes(params.slug)) return {}\n\n return {\n active: context.active === params.slug ? null : context.active,\n removed: [...context.removed, params.slug],\n }\n }),\n restore: assign(({context}, params: {slug: string}) => ({\n removed: context.removed.filter((slug) => slug !== params.slug),\n })),\n reorder: assign(({context}, params: {order: string[]}) => {\n const listed = new Set(themesOf(context).themes.map((theme) => theme.slug))\n const order = params.order.filter((slug) => listed.has(slug))\n\n // The removed themes keep their place in line for when they are restored\n return {\n order: [...order, ...context.order.filter((slug) => !order.includes(slug))],\n }\n }),\n delete: assign(({context}, params: {slug: string}) => {\n if (!context.custom.some((theme) => theme.slug === params.slug)) return {}\n\n const {[params.slug]: _deleted, ...images} = context.images\n\n return {\n active: context.active === params.slug ? null : context.active,\n custom: context.custom.filter((theme) => theme.slug !== params.slug),\n removed: context.removed.filter((slug) => slug !== params.slug),\n images,\n }\n }),\n },\n}).createMachine({\n id: 'themer',\n context: ({input}) => ({\n ...input.stored,\n baseOptions: input.baseOptions,\n editing: null,\n images: {},\n }),\n entry: 'persist',\n type: 'parallel',\n states: {\n sidebar: {\n initial: 'closed',\n states: {\n closed: {\n on: {'sidebar.toggle': 'opening'},\n },\n opening: {\n tags: ['panel', 'moving'],\n after: {[MOTION_DURATION]: 'open'},\n on: {\n 'layout.transitioned': 'open',\n 'sidebar.toggle': 'closing',\n 'sidebar.close': 'closing',\n },\n },\n open: {\n tags: ['panel'],\n on: {'sidebar.toggle': 'closing', 'sidebar.close': 'closing'},\n },\n closing: {\n tags: ['moving'],\n after: {[MOTION_DURATION]: 'closed'},\n on: {'layout.transitioned': 'closed', 'sidebar.toggle': 'opening'},\n },\n },\n },\n // The split preview belongs to a sidebar session: closing the sidebar —\n // from its header or from the navbar — ends the split with it\n preview: {\n initial: 'single',\n states: {\n single: {\n on: {'preview.toggle': 'splitting'},\n },\n splitting: {\n tags: ['split', 'moving'],\n after: {[MOTION_DURATION]: 'split'},\n on: {\n 'layout.transitioned': 'split',\n 'preview.toggle': 'unsplitting',\n 'sidebar.close': 'unsplitting',\n 'sidebar.toggle': {guard: 'sidebarShown', target: 'unsplitting'},\n },\n },\n split: {\n tags: ['split'],\n on: {\n 'preview.toggle': 'unsplitting',\n 'sidebar.close': 'unsplitting',\n 'sidebar.toggle': {guard: 'sidebarShown', target: 'unsplitting'},\n },\n },\n unsplitting: {\n tags: ['moving'],\n after: {[MOTION_DURATION]: 'single'},\n on: {'layout.transitioned': 'single', 'preview.toggle': 'splitting'},\n },\n },\n },\n theme: {\n initial: 'applied',\n states: {\n // Only events that change what is applied count: adding a copy of the\n // applied theme, editing or duplicating it, picking it again, or\n // removing another theme leaves the Studio looking the same — with\n // nothing to cross-fade, nothing would report the switch over, and the\n // first edit that followed would cross-fade instead of following the\n // pointer\n applied: {\n on: {\n 'theme.pick': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.edit': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.duplicate': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.remove': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.delete': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.add': {\n guard: {type: 'hasOptions', params: ({event}) => ({options: event.options})},\n target: 'switching',\n },\n 'theme.import': 'switching',\n },\n },\n switching: {\n tags: ['switching'],\n after: {[MOTION_DURATION]: 'applied'},\n on: {\n 'layout.transitioned': 'applied',\n // Another switch starts the clock over\n 'theme.pick': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.edit': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.duplicate': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.remove': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.delete': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.add': {\n guard: {type: 'hasOptions', params: ({event}) => ({options: event.options})},\n target: 'switching',\n reenter: true,\n },\n 'theme.import': {target: 'switching', reenter: true},\n },\n },\n },\n },\n flow: {\n initial: 'list',\n on: {\n 'theme.pick': {\n actions: [{type: 'pick', params: ({event}) => ({slug: event.slug})}, 'persist'],\n },\n 'theme.add': {\n target: '.edit',\n actions: [\n {\n type: 'add',\n params: ({event}) => ({\n title: event.title,\n options: event.options,\n palette: event.palette,\n imageUrl: event.imageUrl,\n }),\n },\n 'persist',\n ],\n },\n 'theme.duplicate': {\n target: '.edit',\n actions: [{type: 'duplicate', params: ({event}) => ({slug: event.slug})}, 'persist'],\n },\n 'theme.edit': {\n target: '.edit',\n guard: {type: 'isCustomTheme', params: ({event}) => ({slug: event.slug})},\n actions: [{type: 'startEditing', params: ({event}) => ({slug: event.slug})}, 'persist'],\n },\n 'theme.update': {\n actions: [\n // A new image replaces the one the theme had\n {\n type: 'revokeImage',\n params: ({context, event}) => ({\n url: event.imageUrl ? context.images[event.slug] : undefined,\n }),\n },\n {\n type: 'update',\n params: ({event}) => ({\n slug: event.slug,\n title: event.title,\n options: event.options,\n palette: event.palette,\n imageUrl: event.imageUrl,\n }),\n },\n 'persist',\n ],\n },\n 'theme.remove': {\n actions: [{type: 'remove', params: ({event}) => ({slug: event.slug})}, 'persist'],\n },\n 'theme.restore': {\n actions: [{type: 'restore', params: ({event}) => ({slug: event.slug})}, 'persist'],\n },\n 'theme.delete': {\n actions: [\n {\n type: 'revokeImage',\n params: ({context, event}) => ({url: context.images[event.slug]}),\n },\n {type: 'delete', params: ({event}) => ({slug: event.slug})},\n 'persist',\n ],\n },\n 'theme.reorder': {\n actions: [{type: 'reorder', params: ({event}) => ({order: event.order})}, 'persist'],\n },\n 'theme.import': {\n actions: [\n {\n type: 'import',\n params: ({event}) => ({title: event.title, options: event.options}),\n },\n 'persist',\n ],\n },\n 'flow.list': '.list',\n 'flow.removed': {\n target: '.removed',\n guard: 'hasRemovedThemes',\n },\n },\n states: {\n list: {},\n edit: {\n exit: 'stopEditing',\n // The theme being edited can go away — removed from the editor, or\n // duplicated from a theme that does not exist — and then there is\n // nothing left to edit\n always: {guard: not('isEditingListedTheme'), target: 'list'},\n },\n removed: {\n always: {guard: not('hasRemovedThemes'), target: 'list'},\n },\n },\n },\n },\n})\n\n/** @internal */\nexport type ThemerSnapshot = SnapshotFrom<typeof themerMachine>\n\n/** The part of the machine context that is persisted between sessions @internal */\nexport function selectStoredState(snapshot: ThemerSnapshot): ThemerState {\n const {active, custom, removed, order} = snapshot.context\n\n return {active, custom, removed, order}\n}\n","import {style} from '@vanilla-extract/css'\n\n/** Always reserves the scrollbar's space, so the content never shifts */\nexport const root = style({\n scrollbarGutter: 'stable',\n})\n","import {Box, useTheme_v2} from '@sanity/ui'\nimport {createContext, useContext, useEffect, useMemo, useRef, useState} from 'react'\n\nimport {root} from './ScrollArea.css'\n\n/** How far the content of a scroll area sits from its edges, in pixels */\ninterface ScrollAreaInset {\n left: number\n right: number\n bottom: number\n}\n\nconst ScrollAreaInsetContext = createContext<ScrollAreaInset>({left: 0, right: 0, bottom: 0})\n\n/**\n * The gutter the last scroll area measured. It is the same for every scroll\n * area (the reserved gutter does not depend on the content), so the next one\n * starts out with the right padding instead of shifting after its first\n * measurement — which would also throw off anything scrolling into view as\n * the content mounts.\n */\nlet lastGutter = 0\n\n/**\n * The scrolling body of a sidebar flow. It always reserves the scrollbar's\n * space, so the content does not shift when the list grows or shrinks past\n * the point where it needs to scroll — and it takes that space out of its\n * right padding, so the content stays centered rather than hugging the left.\n * With overlay scrollbars there is no gutter and the padding stays even.\n *\n * @internal\n */\nexport function ScrollArea(props: {children: React.ReactNode; padding: number}) {\n const {children, padding} = props\n const {space} = useTheme_v2()\n const ref = useRef<HTMLDivElement | null>(null)\n const [gutter, setGutter] = useState(() => lastGutter)\n\n useEffect(() => {\n const element = ref.current\n\n if (!element) return undefined\n\n // The reserved gutter is the part of the box the content cannot use\n const measure = () => {\n lastGutter = element.offsetWidth - element.clientWidth\n setGutter(lastGutter)\n }\n\n measure()\n\n const observer = new ResizeObserver(measure)\n\n observer.observe(element)\n\n return () => observer.disconnect()\n }, [])\n\n const inset = useMemo<ScrollAreaInset>(\n () => ({\n left: space[padding],\n right: Math.max(0, space[padding] - gutter),\n bottom: space[padding],\n }),\n [gutter, padding, space],\n )\n\n return (\n <Box\n className={root}\n flex={1}\n overflow=\"auto\"\n padding={padding}\n ref={ref}\n style={{paddingRight: inset.right}}\n >\n <ScrollAreaInsetContext.Provider value={inset}>{children}</ScrollAreaInsetContext.Provider>\n </Box>\n )\n}\n\n/**\n * Content that runs edge to edge in a scroll area — up to the scrollbar —\n * instead of sitting inside its padding. It goes last: it runs down to the\n * bottom edge too.\n *\n * @internal\n */\nexport function ScrollAreaBleed(props: {children: React.ReactNode}) {\n const {left, right, bottom} = useContext(ScrollAreaInsetContext)\n\n return (\n <div style={{marginLeft: -left, marginRight: -right, marginBottom: -bottom}}>\n {props.children}\n </div>\n )\n}\n","import {createVar, style, styleVariants} from '@vanilla-extract/css'\n\n/**\n * The dark scheme covers the right side of the preview behind a diagonal cut,\n * like the \"Auto\" appearance preview in macOS System Settings shows both\n * schemes at once.\n */\nconst DARK_CLIP_PATH = 'polygon(58% 0, 100% 0, 100% 100%, 42% 100%)'\n\n/** The thickness of a line of text — bold enough to survive a half-size preview */\nconst LINE = 4\n\n/**\n * The colors a mockup paints with — only the ones the theme options change —\n * assigned per color scheme from the theme it previews\n */\nexport const colors = {\n bg: createVar(),\n fg: createVar(),\n mutedFg: createVar(),\n border: createVar(),\n accent: createVar(),\n avatar: createVar(),\n selectedBg: createVar(),\n selectedFg: createVar(),\n inputBg: createVar(),\n inputBorder: createVar(),\n}\n\nexport const root = style({\n position: 'relative',\n aspectRatio: '16 / 9',\n overflow: 'hidden',\n})\n\nconst studioBase = style({\n position: 'absolute',\n inset: 0,\n display: 'flex',\n flexDirection: 'column',\n background: colors.bg,\n})\n\nexport const studio = styleVariants({\n light: [studioBase],\n dark: [studioBase, {clipPath: DARK_CLIP_PATH}],\n})\n\nexport const navbar = style({\n flex: 'none',\n display: 'flex',\n alignItems: 'center',\n gap: 5,\n height: '18%',\n padding: '0 5%',\n borderBottom: `1px solid ${colors.border}`,\n})\n\nexport const logo = style({\n flex: 'none',\n width: 8,\n height: 8,\n borderRadius: 2,\n background: colors.accent,\n})\n\nexport const spacer = style({\n flex: 1,\n})\n\nexport const avatar = style({\n flex: 'none',\n width: 8,\n height: 8,\n borderRadius: '50%',\n background: colors.avatar,\n})\n\nexport const body = style({\n flex: 1,\n display: 'flex',\n minHeight: 0,\n})\n\nexport const list = style({\n flex: 'none',\n display: 'flex',\n flexDirection: 'column',\n width: '36%',\n borderRight: `1px solid ${colors.border}`,\n})\n\nexport const listRow = style({\n flex: 'none',\n display: 'flex',\n alignItems: 'center',\n height: '18%',\n padding: '0 9%',\n selectors: {\n \"&[data-selected='true']\": {\n background: colors.selectedBg,\n },\n },\n})\n\nexport const pane = style({\n flex: 1,\n display: 'flex',\n flexDirection: 'column',\n minWidth: 0,\n})\n\nexport const form = style({\n flex: 1,\n display: 'flex',\n flexDirection: 'column',\n gap: 7,\n padding: '9% 8% 0',\n minHeight: 0,\n overflow: 'hidden',\n})\n\nexport const field = style({\n flex: 'none',\n display: 'flex',\n flexDirection: 'column',\n gap: 3,\n})\n\nexport const input = style({\n height: 12,\n borderRadius: 3,\n background: colors.inputBg,\n boxShadow: `inset 0 0 0 1px ${colors.inputBorder}`,\n})\n\nexport const footer = style({\n flex: 'none',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'flex-end',\n height: '20%',\n padding: '0 8%',\n borderTop: `1px solid ${colors.border}`,\n})\n\nexport const button = style({\n width: '32%',\n height: '50%',\n borderRadius: 3,\n background: colors.accent,\n})\n\nconst lineBase = style({\n flex: 'none',\n height: LINE,\n borderRadius: LINE / 2,\n})\n\n/** A line of text, in one of the text colors — its width is set inline */\nexport const line = styleVariants({\n fg: [lineBase, {background: colors.fg}],\n muted: [lineBase, {background: colors.mutedFg}],\n selected: [lineBase, {background: colors.selectedFg}],\n})\n","import {ThemeProvider, useTheme_v2} from '@sanity/ui'\nimport {type ThemeColorSchemeKey} from '@sanity/ui/theme'\nimport {assignInlineVars} from '@vanilla-extract/dynamic'\nimport {useMemo} from 'react'\n\nimport {buildTheme} from '../theme/buildTheme'\nimport {BuildThemeOptions} from '../theme/options'\n\nimport {\n avatar,\n body,\n button,\n colors,\n field,\n footer,\n form,\n input,\n line,\n list,\n listRow,\n logo,\n navbar,\n pane,\n root,\n spacer,\n studio,\n} from './ThemePreview.css'\n\n/** The document rows in the list pane — the widths of their title lines */\nconst LIST_ROWS = ['62%', '46%', '70%', '52%']\n\n/** The fields in the document form — the widths of their labels */\nconst FIELDS = ['32%', '24%']\n\n/**\n * A tiny, lo-fi mockup of a Studio in the given theme — the navbar over the\n * structure tool with a list of documents on the left and a document form on\n * the right — in both color schemes at once: the light scheme on the left and\n * the dark scheme on the right, split diagonally. It only paints colors the\n * theme options change (backgrounds, text, borders, the accent) so that a\n * glance shows what the theme does to the Studio — with few and bold enough\n * elements to still read when scaled down to a thumbnail.\n *\n * @internal\n */\nexport function ThemePreview(props: {options: BuildThemeOptions}) {\n const {options} = props\n const theme = useMemo(() => buildTheme(options), [options])\n\n return (\n <div aria-hidden className={root}>\n <ThemeProvider scheme=\"light\" theme={theme} tone=\"default\">\n <MockStudio scheme=\"light\" />\n </ThemeProvider>\n <ThemeProvider scheme=\"dark\" theme={theme} tone=\"default\">\n <MockStudio scheme=\"dark\" />\n </ThemeProvider>\n </div>\n )\n}\n\nfunction MockStudio(props: {scheme: ThemeColorSchemeKey}) {\n const {scheme} = props\n const {color} = useTheme_v2()\n const selected = color.selectable.default.selected\n const inputColor = color.input.default.enabled\n\n return (\n <div\n className={studio[scheme]}\n style={assignInlineVars({\n [colors.bg]: color.bg,\n [colors.fg]: color.fg,\n [colors.mutedFg]: color.muted.fg,\n [colors.border]: color.border,\n [colors.accent]: color.button.default.primary.enabled.bg,\n [colors.avatar]: color.avatar.blue.bg,\n [colors.selectedBg]: selected.bg,\n [colors.selectedFg]: selected.fg,\n [colors.inputBg]: inputColor.bg,\n [colors.inputBorder]: inputColor.border,\n })}\n >\n <div className={navbar}>\n <div className={logo} />\n <div className={line.fg} style={{width: '22%'}} />\n <div className={spacer} />\n <div className={avatar} />\n </div>\n\n <div className={body}>\n <div className={list}>\n {LIST_ROWS.map((width, index) => {\n const isSelected = index === 1\n\n return (\n <div className={listRow} data-selected={isSelected} key={width}>\n <div className={isSelected ? line.selected : line.muted} style={{width}} />\n </div>\n )\n })}\n </div>\n\n <div className={pane}>\n <div className={form}>\n <div className={line.fg} style={{width: '45%'}} />\n {FIELDS.map((width) => (\n <div className={field} key={width}>\n <div className={line.muted} style={{width}} />\n <div className={input} />\n </div>\n ))}\n </div>\n <div className={footer}>\n <div className={button} />\n </div>\n </div>\n </div>\n </div>\n )\n}\n","import {Card} from '@sanity/ui'\n\nimport {BuildThemeOptions} from '../theme/options'\nimport {ThemePreview} from './ThemePreview'\n\n/**\n * The floating preview card of a theme: the lo-fi studio mockup on a raised,\n * rounded card, like the appearance previews in macOS System Settings.\n *\n * @internal\n */\nexport function ThemeThumbnail(props: {options: BuildThemeOptions}) {\n return (\n <Card overflow=\"hidden\" radius={3} shadow={1}>\n <ThemePreview options={props.options} />\n </Card>\n )\n}\n","import {style} from '@vanilla-extract/css'\n\n/** A removed theme's thumbnail, dimmed to read as removed */\nexport const removedThumbnail = style({\n opacity: 0.6,\n})\n","import {Box, Button, Flex, Stack, Text} from '@sanity/ui'\n\nimport {useThemer} from './context'\nimport {ScrollArea} from './ScrollArea'\nimport {displayTitle} from './themes'\nimport {ThemeThumbnail} from './ThemeThumbnail'\n\nimport {removedThumbnail} from './RemovedThemes.css'\n\n/**\n * The flow for restoring removed themes: the presets and custom themes the\n * user took out of the list, each with a button to put it back — and, for\n * custom themes, one to delete it for good. The machine leaves the flow once\n * the last one is gone.\n *\n * @internal\n */\nexport function RemovedThemes() {\n const {removed, send} = useThemer()\n\n return (\n <ScrollArea padding={3}>\n <Stack gap={5}>\n {removed.map((theme) => (\n <Stack gap={3} key={theme.slug}>\n <Box className={removedThumbnail}>\n <ThemeThumbnail options={theme.options} />\n </Box>\n <Text align=\"center\" size={1} textOverflow=\"ellipsis\">\n {displayTitle(theme.title)}\n </Text>\n <Flex gap={2} justify=\"center\">\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.restore', slug: theme.slug})}\n padding={2}\n text=\"Restore\"\n />\n {theme.source === 'custom' && (\n <Button\n mode=\"ghost\"\n onClick={() => send({type: 'theme.delete', slug: theme.slug})}\n padding={2}\n text=\"Delete\"\n tone=\"critical\"\n />\n )}\n </Flex>\n </Stack>\n ))}\n </Stack>\n </ScrollArea>\n )\n}\n","import {Button, ButtonProps, Text} from '@sanity/ui'\nimport {Tooltip} from '@sanity/ui/tooltip'\n\n/**\n * A button with an animated tooltip instead of a `title` attribute, sized like\n * the Studio's own buttons (`padding` and `gap` of 2). Icon-only buttons take\n * the tooltip as their accessible name.\n *\n * @internal\n */\nexport function TooltipButton(props: ButtonProps & {tooltip: string}) {\n const {tooltip, ...buttonProps} = props\n\n return (\n <Tooltip animate content={<Text size={1}>{tooltip}</Text>} placement=\"bottom\" portal>\n <Button\n aria-label={buttonProps.text === undefined ? tooltip : undefined}\n gap={2}\n padding={2}\n {...buttonProps}\n />\n </Tooltip>\n )\n}\n","import {ImageIcon} from '@sanity/icons/Image'\nimport {ButtonProps} from '@sanity/ui'\nimport {useRef} from 'react'\n\nimport {TooltipButton} from './TooltipButton'\n\n/**\n * A button that opens the file picker for an image, and hands the chosen file\n * over — the picker is a hidden file input, so nothing is uploaded anywhere.\n *\n * @internal\n */\nexport function ImageFileButton(\n props: {onFile: (file: File) => void; tooltip: string} & Pick<\n ButtonProps,\n 'disabled' | 'loading' | 'mode' | 'padding' | 'text' | 'width'\n >,\n) {\n const {onFile, ...buttonProps} = props\n const inputRef = useRef<HTMLInputElement | null>(null)\n\n return (\n <>\n <TooltipButton {...buttonProps} icon={ImageIcon} onClick={() => inputRef.current?.click()} />\n <input\n accept=\"image/*\"\n hidden\n onChange={(event) => {\n const file = event.currentTarget.files?.[0]\n\n // Picking the same file again should work, so the input forgets it\n event.currentTarget.value = ''\n\n if (file) onFile(file)\n }}\n ref={inputRef}\n type=\"file\"\n />\n </>\n )\n}\n","import {PixelData} from './imagePalette'\n\n/**\n * The longest side the image is scaled down to before its colors are read —\n * a few thousand pixels are plenty for a palette, and keep it instant\n */\nconst SAMPLE_SIZE = 96\n\n/**\n * Reads the pixels of an image file on device: the image is decoded, drawn\n * onto a small canvas and read back, so nothing leaves the browser.\n *\n * @internal\n */\nexport async function readImagePixels(file: Blob): Promise<PixelData> {\n const image = await decodeImage(file)\n\n try {\n const scale = Math.min(1, SAMPLE_SIZE / Math.max(image.width, image.height))\n const width = Math.max(1, Math.round(image.width * scale))\n const height = Math.max(1, Math.round(image.height * scale))\n const canvas = document.createElement('canvas')\n\n canvas.width = width\n canvas.height = height\n\n const context = canvas.getContext('2d', {willReadFrequently: true})\n\n if (!context) throw new Error('Could not draw the image')\n\n context.drawImage(image, 0, 0, width, height)\n\n return {data: context.getImageData(0, 0, width, height).data, width, height}\n } finally {\n // Where `createImageBitmap` is missing, `ImageBitmap` may be too\n if (typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap) image.close()\n }\n}\n\nasync function decodeImage(file: Blob): Promise<ImageBitmap | HTMLImageElement> {\n if (typeof createImageBitmap === 'function') {\n try {\n return await createImageBitmap(file)\n } catch {\n // Some browsers cannot decode every format this way (e.g. SVG) — an\n // image element handles whatever the browser can display\n }\n }\n\n const url = URL.createObjectURL(file)\n\n try {\n return await new Promise<HTMLImageElement>((resolve, reject) => {\n const image = new Image()\n\n image.addEventListener('load', () => resolve(image), {once: true})\n image.addEventListener('error', () => reject(new Error('Could not decode the image')), {\n once: true,\n })\n image.src = url\n })\n } finally {\n URL.revokeObjectURL(url)\n }\n}\n","import {useToast} from '@sanity/ui/toast'\nimport {useCallback, useEffect, useRef, useState} from 'react'\n\nimport {readImagePixels} from './imageFile'\nimport {extractImagePalette, ImagePalette} from './imagePalette'\n\n/**\n * Extracts the palette of image files on device and hands it to `onPalette`,\n * along with the file it came from. Failures end up as a toast.\n *\n * @internal\n */\nexport function useImagePalette(onPalette: (palette: ImagePalette, file: File) => void): {\n busy: boolean\n pickImage: (file: File) => void\n} {\n const toast = useToast()\n const [busy, setBusy] = useState(false)\n const onPaletteRef = useRef(onPalette)\n const mountedRef = useRef(true)\n // Picks are numbered so that a slow earlier image cannot overwrite a later\n // pick once its extraction finishes\n const latestPickRef = useRef(0)\n\n useEffect(() => {\n onPaletteRef.current = onPalette\n }, [onPalette])\n\n useEffect(() => {\n mountedRef.current = true\n\n return () => {\n mountedRef.current = false\n }\n }, [])\n\n const pickImage = useCallback(\n (file: File) => {\n const pick = ++latestPickRef.current\n\n setBusy(true)\n\n // Whether this pick still matters: it is the latest one and the\n // component that asked for it is still around\n const isCurrent = () => mountedRef.current && latestPickRef.current === pick\n\n const extract = async () => {\n const palette = extractImagePalette(await readImagePixels(file))\n\n if (!isCurrent()) return\n\n if (Object.values(palette).every((swatch) => swatch === null)) {\n toast.push({status: 'warning', title: 'Found no colors in that image'})\n } else {\n onPaletteRef.current(palette, file)\n }\n }\n\n extract()\n .catch(() => {\n if (isCurrent()) toast.push({status: 'error', title: 'Could not read that image'})\n })\n .finally(() => {\n if (isCurrent()) setBusy(false)\n })\n },\n [toast],\n )\n\n return {busy, pickImage}\n}\n","import {createVar, style} from '@vanilla-extract/css'\n\nconst TILE_SIZE = 24\nconst TILE_GAP = 4\n\n/** The image spans both rows of tiles */\nconst IMAGE_SIZE = TILE_SIZE * 2 + TILE_GAP\n\n/** The variant previews are the theme thumbnails, laid out at this width and scaled down */\nexport const VARIANT_WIDTH = 144\n\nexport const VARIANT_GAP = 8\nexport const ROW_PADDING = 12\n\n/** The frame around a variant's thumbnail, on either side */\nexport const VARIANT_FRAME = 4\n\n/** The width of one variant's thumbnail, set on the row once it is measured */\nexport const variantWidth = createVar()\n\n/** How much a variant's thumbnail is scaled down from `VARIANT_WIDTH` */\nexport const variantScale = createVar()\n\nexport const paletteGrid = style({\n display: 'grid',\n gridTemplateColumns: `repeat(3, ${TILE_SIZE}px)`,\n gridAutoRows: TILE_SIZE,\n gap: TILE_GAP,\n selectors: {\n \"&[data-with-image='true']\": {\n gridTemplateColumns: `${IMAGE_SIZE}px repeat(3, ${TILE_SIZE}px)`,\n },\n },\n})\n\nexport const imageTile = style({\n display: 'block',\n gridRow: 'span 2',\n width: IMAGE_SIZE,\n height: IMAGE_SIZE,\n borderRadius: 3,\n objectFit: 'cover',\n boxShadow: 'inset 0 0 0 1px var(--card-border-color)',\n})\n\nexport const tile = style({\n display: 'block',\n borderRadius: 3,\n boxShadow: 'inset 0 0 0 1px var(--card-border-color)',\n selectors: {\n \"&[data-empty='true']\": {\n background:\n 'repeating-linear-gradient(-45deg, transparent 0 3px, var(--card-border-color) 3px 4px)',\n },\n },\n})\n\n/**\n * The variants scroll sideways and snap into place, bleeding into the card's\n * padding so the row runs from edge to edge\n */\nexport const variantRow = style({\n display: 'flex',\n gap: VARIANT_GAP,\n margin: `0 -${ROW_PADDING}px`,\n padding: `0 ${ROW_PADDING}px`,\n overflowX: 'auto',\n scrollSnapType: 'x mandatory',\n scrollPaddingLeft: ROW_PADDING,\n scrollbarWidth: 'thin',\n})\n\nexport const variantButton = style({\n 'appearance': 'none',\n 'flex': 'none',\n 'display': 'block',\n 'boxSizing': 'border-box',\n 'width': `calc(${variantWidth} + ${VARIANT_FRAME * 2}px)`,\n 'margin': 0,\n 'padding': 0,\n 'border': 0,\n 'background': 'none',\n 'color': 'inherit',\n 'font': 'inherit',\n 'textAlign': 'center',\n 'cursor': 'pointer',\n 'scrollSnapAlign': 'start',\n ':focus': {\n outline: 'none',\n },\n})\n\n/**\n * Clips the scaled-down thumbnail to its visual size, with room for the ring\n * — drawn inside the frame, like the theme cards', so the scrolling row\n * never clips it\n */\nexport const variantFrame = style({\n display: 'block',\n boxSizing: 'border-box',\n width: `calc(${variantWidth} + ${VARIANT_FRAME * 2}px)`,\n height: `calc(${variantWidth} * 9 / 16 + ${VARIANT_FRAME * 2}px)`,\n padding: VARIANT_FRAME,\n borderRadius: 7,\n overflow: 'hidden',\n transition: 'box-shadow 100ms',\n selectors: {\n [`${variantButton}:hover &`]: {\n boxShadow: 'inset 0 0 0 2px var(--card-border-color)',\n },\n [`${variantButton}[aria-pressed='true'] &`]: {\n boxShadow: 'inset 0 0 0 2px var(--card-focus-ring-color)',\n },\n [`${variantButton}:focus-visible &`]: {\n outline: '2px solid var(--card-focus-ring-color)',\n outlineOffset: -2,\n },\n },\n})\n\nexport const variantScaleBox = style({\n display: 'block',\n width: VARIANT_WIDTH,\n transform: `scale(${variantScale})`,\n transformOrigin: 'top left',\n})\n","import {SparklesIcon} from '@sanity/icons/Sparkles'\nimport {Box, Card, Flex, Stack, Text} from '@sanity/ui'\nimport {Tooltip} from '@sanity/ui/tooltip'\nimport {assignInlineVars} from '@vanilla-extract/dynamic'\nimport {useEffect, useMemo, useRef, useState} from 'react'\n\nimport {BuildThemeOptions} from '../theme/options'\nimport {ImageFileButton} from './ImageFileButton'\nimport {\n applyImagePalette,\n currentImageVariant,\n IMAGE_PALETTE_TITLES,\n IMAGE_PALETTE_VARIANTS,\n ImagePalette,\n ImagePaletteKey,\n ImagePaletteVariant,\n} from './imagePalette'\nimport {ThemeThumbnail} from './ThemeThumbnail'\nimport {TooltipButton} from './TooltipButton'\nimport {useImagePalette} from './useImagePalette'\n\nimport {\n imageTile,\n paletteGrid,\n ROW_PADDING,\n tile,\n VARIANT_FRAME,\n VARIANT_GAP,\n VARIANT_WIDTH,\n variantButton,\n variantFrame,\n variantRow,\n variantScale,\n variantScaleBox,\n variantWidth,\n} from './ImagePaletteSection.css'\n\n/** The swatches on display: the vibrant family over the muted one */\nconst TILES: ImagePaletteKey[] = [\n 'vibrant',\n 'lightVibrant',\n 'darkVibrant',\n 'muted',\n 'lightMuted',\n 'darkMuted',\n]\n\n/**\n * How many variants show at once: two and a half, so that the cut-off third\n * one gives away that the row scrolls\n */\nconst VISIBLE_VARIANTS = 2.5\n\n/** The width of one variant, before the row has been measured */\nconst FALLBACK_VARIANT_WIDTH = 58\n\n/**\n * Takes the colors of a theme from an image, on device: the image is read\n * into a canvas and its palette extracted right there — nothing is uploaded.\n * Shows the image (while the session lasts) next to its palette, offers a\n * theme variant built around each swatch as a small preview, and a lucky\n * button that picks an interesting one.\n *\n * @internal\n */\nexport function ImagePaletteSection(props: {\n imageUrl?: string\n onPalette: (palette: ImagePalette, file: File) => void\n onVariant: (variant: ImagePaletteVariant) => void\n onLucky: () => void\n options: BuildThemeOptions\n palette?: ImagePalette\n}) {\n const {imageUrl, onPalette, onVariant, onLucky, options, palette} = props\n const {busy, pickImage} = useImagePalette(onPalette)\n const [dragging, setDragging] = useState(false)\n // `dragleave` also fires when the pointer crosses into a child, so the\n // highlight tracks how deep the drag is rather than the last event\n const dragDepth = useRef(0)\n const current = palette ? currentImageVariant(options, palette) : null\n const rowRef = useRef<HTMLDivElement | null>(null)\n const [thumbnailWidth, setThumbnailWidth] = useState(FALLBACK_VARIANT_WIDTH)\n\n // Size the variants to the row, so two and a half of them are in view\n useEffect(() => {\n const row = rowRef.current\n\n if (!row) return undefined\n\n const measure = () => {\n const visible =\n row.clientWidth - ROW_PADDING - (Math.ceil(VISIBLE_VARIANTS) - 1) * VARIANT_GAP\n\n // Each variant is its thumbnail plus the frame on either side\n setThumbnailWidth(Math.max(24, Math.floor(visible / VISIBLE_VARIANTS) - VARIANT_FRAME * 2))\n }\n\n measure()\n\n const observer = new ResizeObserver(measure)\n\n observer.observe(row)\n\n return () => observer.disconnect()\n }, [palette])\n\n const variants = useMemo(() => {\n if (!palette) return []\n\n return IMAGE_PALETTE_VARIANTS.flatMap((variant) =>\n palette[variant] === null\n ? []\n : [\n {\n variant,\n title: IMAGE_PALETTE_TITLES[variant],\n options: applyImagePalette(options, palette, variant),\n },\n ],\n )\n }, [options, palette])\n\n return (\n <Card\n border\n onDragEnter={(event) => {\n event.preventDefault()\n dragDepth.current++\n setDragging(true)\n }}\n onDragLeave={() => {\n dragDepth.current = Math.max(0, dragDepth.current - 1)\n\n if (dragDepth.current === 0) setDragging(false)\n }}\n onDragOver={(event) => {\n event.preventDefault()\n }}\n onDrop={(event) => {\n event.preventDefault()\n dragDepth.current = 0\n setDragging(false)\n\n const file = event.dataTransfer.files[0]\n\n if (file) pickImage(file)\n }}\n padding={3}\n radius={3}\n tone={dragging ? 'primary' : 'default'}\n >\n <Stack gap={3}>\n <Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n Image palette\n </Text>\n </Box>\n {palette && (\n <ImageFileButton\n loading={busy}\n mode=\"bleed\"\n onFile={pickImage}\n padding={2}\n tooltip=\"Pick another image\"\n />\n )}\n </Flex>\n\n {palette ? (\n <>\n <div className={paletteGrid} data-with-image={imageUrl !== undefined}>\n {imageUrl && <img alt=\"\" className={imageTile} src={imageUrl} />}\n {TILES.map((key) => {\n const hex = palette[key]\n const title = IMAGE_PALETTE_TITLES[key]\n\n return (\n <Tooltip\n animate\n content={\n <Text size={1}>\n {hex ? `${title} · ${hex}` : `No ${title.toLowerCase()} color`}\n </Text>\n }\n key={key}\n placement=\"bottom\"\n portal\n >\n <span\n className={tile}\n data-empty={hex === null}\n style={{background: hex ?? undefined}}\n />\n </Tooltip>\n )\n })}\n </div>\n\n <Stack gap={2}>\n <Text muted size={0}>\n Pick the swatch to build the theme around\n </Text>\n <div\n className={variantRow}\n ref={rowRef}\n style={assignInlineVars({\n [variantWidth]: `${thumbnailWidth}px`,\n [variantScale]: String(thumbnailWidth / VARIANT_WIDTH),\n })}\n >\n {variants.map(({variant, title, options: variantOptions}) => (\n // oxlint-disable-next-line control-has-associated-label -- the title below the thumbnail is the label, deeper than the rule looks\n <button\n aria-pressed={variant === current}\n className={variantButton}\n key={variant}\n onClick={() => onVariant(variant)}\n type=\"button\"\n >\n <span className={variantFrame}>\n <span className={variantScaleBox}>\n <ThemeThumbnail options={variantOptions} />\n </span>\n </span>\n <Box as=\"span\" display=\"block\" paddingTop={1}>\n <Text\n align=\"center\"\n as=\"span\"\n size={0}\n textOverflow=\"ellipsis\"\n weight={variant === current ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>\n </button>\n ))}\n </div>\n </Stack>\n\n <TooltipButton\n icon={SparklesIcon}\n mode=\"ghost\"\n onClick={onLucky}\n text=\"I'm feeling lucky\"\n tooltip=\"Build the theme around a swatch that looks interesting\"\n width=\"fill\"\n />\n </>\n ) : (\n <Stack gap={3}>\n <Text muted size={0}>\n Take the accent, text and background colors from an image — read on device, nothing is\n uploaded.\n </Text>\n <ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n text=\"Pick an image\"\n tooltip=\"Take the colors from an image on your device\"\n width=\"fill\"\n />\n </Stack>\n )}\n </Stack>\n </Card>\n )\n}\n","import {style} from '@vanilla-extract/css'\n\n/** A native color input, trimmed down to its swatch */\nexport const swatch = style({\n boxSizing: 'border-box',\n flex: 'none',\n width: 33,\n height: 33,\n padding: 0,\n border: '1px solid var(--card-border-color)',\n borderRadius: 4,\n background: 'none',\n cursor: 'pointer',\n selectors: {\n '&::-webkit-color-swatch-wrapper': {\n padding: 0,\n },\n '&::-webkit-color-swatch': {\n border: 'none',\n borderRadius: 3,\n },\n '&::-moz-color-swatch': {\n border: 'none',\n borderRadius: 3,\n },\n },\n})\n\n/** A native range input, themed through `accent-color` */\nexport const range = style({\n display: 'block',\n width: '100%',\n margin: 0,\n accentColor: 'var(--card-focus-ring-color)',\n})\n\n/** Lets the color row's text shrink and truncate instead of widening the row */\nexport const colorRowText = style({\n minWidth: 0,\n})\n","import {ResetIcon} from '@sanity/icons/Reset'\nimport {TrashIcon} from '@sanity/icons/Trash'\nimport {Badge, Box, Button, Card, Flex, Stack, Text, TextInput} from '@sanity/ui'\nimport {type ThemeColorSchemeKey} from '@sanity/ui/theme'\nimport {useMemo} from 'react'\nimport {useColorSchemeValue} from 'sanity'\n\nimport {buildPalette, GeneratedColorPalette} from '../theme/buildPalette'\nimport {\n BuildThemeOptions,\n ResolvedSchemeOptions,\n resolveThemeOptions,\n SCHEMES,\n SchemeThemeOptions,\n} from '../theme/options'\nimport {useThemer} from './context'\nimport {\n applyImagePalette,\n currentImageVariant,\n ImagePalette,\n ImagePaletteVariant,\n pickLuckyVariant,\n} from './imagePalette'\nimport {ImagePaletteSection} from './ImagePaletteSection'\nimport {ScrollArea, ScrollAreaBleed} from './ScrollArea'\nimport {ThemeThumbnail} from './ThemeThumbnail'\nimport {TooltipButton} from './TooltipButton'\n\nimport {colorRowText, range, swatch} from './ThemeEditor.css'\n\nconst SCHEME_TITLES: Record<ThemeColorSchemeKey, string> = {\n dark: 'Dark mode',\n light: 'Light mode',\n}\n\n/**\n * Where the split preview shows a scheme: the Studio's own scheme stays next\n * to the sidebar (or at the bottom, stacked on small screens), the other one\n * takes the far side\n */\nfunction splitHint(own: boolean, mobile: boolean): string {\n if (mobile) return own ? 'Shown at the bottom' : 'Shown at the top'\n\n return own ? 'Shown on the right, next to the themer' : 'Shown on the left'\n}\n\nconst SCHEME_OPTION_KEYS: Array<keyof SchemeThemeOptions> = [\n 'accent',\n 'text',\n 'background',\n 'contrast',\n]\n\n/**\n * `<input type=\"color\">` paints the color into a shadow-DOM swatch that brings\n * its own border and padding, which then sits inside ours as a second border.\n * Stripping that chrome leaves the themed border as the only one.\n */\n/**\n * The flow for editing one of the user's own themes: its title, a live\n * preview, and a card per color scheme with the accent/text/background\n * pickers and the contrast slider of that scheme. Every change applies to the\n * whole Studio right away — in the scheme the Studio is showing, or in both\n * side by side in the split preview.\n *\n * @internal\n */\nexport function ThemeEditor(props: {focusTitle: boolean; slug: string}) {\n const {focusTitle, slug} = props\n const {themes, images, split, mobile, send} = useThemer()\n const theme = themes.find((candidate) => candidate.slug === slug && candidate.source === 'custom')\n\n // The machine only enters the edit flow for a listed custom theme, and\n // leaves it as soon as the theme is removed or deleted\n if (!theme) return null\n\n const applyVariant = (variant: ImagePaletteVariant | null) => {\n if (!theme.palette || variant === null) return\n\n send({\n type: 'theme.update',\n slug,\n options: applyImagePalette(theme.options, theme.palette, variant),\n })\n }\n\n return (\n <ThemeEditorForm\n focusTitle={focusTitle}\n imageUrl={images[slug]}\n onDone={() => send({type: 'flow.list'})}\n onLucky={() => {\n if (!theme.palette) return\n\n applyVariant(\n pickLuckyVariant(theme.palette, {\n exclude: currentImageVariant(theme.options, theme.palette),\n }),\n )\n }}\n onOptionsChange={(options) => send({type: 'theme.update', slug, options})}\n onPalette={(palette, file) =>\n send({\n type: 'theme.update',\n slug,\n options: applyImagePalette(theme.options, palette),\n palette,\n imageUrl: URL.createObjectURL(file),\n })\n }\n onRemove={() => send({type: 'theme.remove', slug})}\n onTitleChange={(title) => send({type: 'theme.update', slug, title})}\n onVariant={applyVariant}\n options={theme.options}\n palette={theme.palette}\n split={split}\n mobile={mobile}\n title={theme.title}\n />\n )\n}\n\n// Split from `ThemeEditor` so that the derived values below can be hooks\n// while the editor bails out early when the theme is gone\nfunction ThemeEditorForm(props: {\n focusTitle: boolean\n imageUrl?: string\n onDone: () => void\n onLucky: () => void\n onOptionsChange: (options: BuildThemeOptions) => void\n onPalette: (palette: ImagePalette, file: File) => void\n onRemove: () => void\n onTitleChange: (title: string) => void\n onVariant: (variant: ImagePaletteVariant) => void\n options: BuildThemeOptions\n palette?: ImagePalette\n /** Whether the Studio shows in light and dark side by side */\n split: boolean\n /** Whether the split preview stacks, on a small screen */\n mobile: boolean\n title: string\n}) {\n const {\n focusTitle,\n imageUrl,\n onDone,\n onLucky,\n onOptionsChange,\n onPalette,\n onRemove,\n onTitleChange,\n onVariant,\n options,\n palette,\n split,\n mobile,\n title,\n } = props\n // The scheme the Studio is showing, with the appearance setting resolved\n const studioScheme = useColorSchemeValue()\n const resolved = useMemo(() => resolveThemeOptions(options), [options])\n const palettes = useMemo(() => buildPalette(options), [options])\n\n const patchScheme = (scheme: ThemeColorSchemeKey, changes: Partial<SchemeThemeOptions>) => {\n const values: SchemeThemeOptions = {...options[scheme], ...changes}\n\n // Clearing an option means falling back to its default, which is its absence\n for (const key of SCHEME_OPTION_KEYS) {\n if (values[key] === undefined) delete values[key]\n }\n\n const next: BuildThemeOptions = {...options}\n\n if (Object.keys(values).length === 0) {\n delete next[scheme]\n } else {\n next[scheme] = values\n }\n\n onOptionsChange(next)\n }\n\n return (\n <>\n <ScrollArea padding={3}>\n <Stack gap={4}>\n <ThemeThumbnail options={options} />\n\n <Stack gap={2}>\n <Text size={1} weight=\"medium\">\n Title\n </Text>\n <TextInput\n autoFocus={focusTitle}\n fontSize={1}\n onChange={(event) => onTitleChange(event.currentTarget.value)}\n onFocus={focusTitle ? (event) => event.currentTarget.select() : undefined}\n padding={2}\n placeholder=\"Untitled theme\"\n value={title}\n />\n </Stack>\n\n <ImagePaletteSection\n imageUrl={imageUrl}\n onLucky={onLucky}\n onPalette={onPalette}\n onVariant={onVariant}\n options={options}\n palette={palette}\n />\n\n <ScrollAreaBleed>\n {SCHEMES.map((scheme) => (\n <SchemeCard\n active={split || scheme === studioScheme}\n key={scheme}\n onChange={(changes) => patchScheme(scheme, changes)}\n options={options[scheme] ?? {}}\n palette={palettes[scheme]}\n resolved={resolved[scheme]}\n scheme={scheme}\n split={split}\n splitHint={splitHint(scheme === studioScheme, mobile)}\n />\n ))}\n </ScrollAreaBleed>\n </Stack>\n </ScrollArea>\n\n <Card borderTop padding={3}>\n <Flex gap={2}>\n <TooltipButton\n icon={TrashIcon}\n mode=\"ghost\"\n onClick={onRemove}\n text=\"Remove\"\n tone=\"critical\"\n tooltip=\"Remove the theme — it can be restored until it is deleted\"\n />\n <Box flex={1} />\n <Button mode=\"ghost\" onClick={onDone} padding={2} text=\"Done\" />\n </Flex>\n </Card>\n </>\n )\n}\n\n/**\n * The colors of one scheme. The cards span the sidebar edge to edge, one\n * after the other with a border between them, in the sidebar's own scheme —\n * the swatches show the colors they edit. The scheme the Studio is showing is\n * marked as active: its changes show up in the Studio right away, the other\n * scheme's only once the appearance setting switches to it — or both are\n * active, in the split preview.\n */\nfunction SchemeCard(props: {\n active: boolean\n onChange: (changes: Partial<SchemeThemeOptions>) => void\n options: SchemeThemeOptions\n palette: GeneratedColorPalette\n resolved: ResolvedSchemeOptions\n scheme: ThemeColorSchemeKey\n split: boolean\n /** Where the split preview shows this scheme */\n splitHint: string\n}) {\n const {active, onChange, options, palette, resolved, scheme, split, splitHint} = props\n const name = SCHEME_TITLES[scheme]\n\n return (\n <Card borderTop padding={3}>\n <Stack gap={4}>\n <Stack gap={2}>\n <Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <Text size={1} weight=\"medium\">\n {name}\n </Text>\n </Box>\n {active && (\n <Badge fontSize={0} tone=\"primary\">\n Active\n </Badge>\n )}\n </Flex>\n <Text muted size={0}>\n {split\n ? splitHint\n : active\n ? 'Shown in the Studio right now'\n : `Shown once the Studio appearance is ${scheme}`}\n </Text>\n </Stack>\n\n <ColorRow\n auto={options.accent === undefined}\n autoLabel=\"default\"\n label={`${name} accent`}\n onChange={(accent) => onChange({accent})}\n onClear={() => onChange({accent: undefined})}\n title=\"Accent\"\n value={resolved.accent}\n />\n <ColorRow\n auto={options.text === undefined}\n label={`${name} text`}\n onChange={(text) => onChange({text})}\n onClear={() => onChange({text: undefined})}\n title=\"Text\"\n value={resolved.text}\n />\n <ColorRow\n adjusted={scheme === 'dark' ? palette.black : palette.white}\n auto={options.background === undefined}\n label={`${name} background`}\n onChange={(background) => onChange({background})}\n onClear={() => onChange({background: undefined})}\n title=\"Background\"\n value={resolved.background}\n />\n\n <Stack gap={3}>\n <Flex align=\"center\" gap={2}>\n <Stack flex={1} gap={2}>\n <Text size={1}>Contrast</Text>\n <Text muted size={0}>\n {resolved.contrast}\n {options.contrast === undefined ? ' · auto' : ''}\n </Text>\n </Stack>\n {options.contrast !== undefined && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={() => onChange({contrast: undefined})}\n padding={2}\n tooltip=\"Reset to auto\"\n />\n )}\n </Flex>\n <input\n aria-label={`${name} contrast`}\n className={range}\n max={100}\n min={15}\n onChange={(event) => onChange({contrast: Number(event.currentTarget.value)})}\n step={1}\n type=\"range\"\n value={resolved.contrast}\n />\n <Text muted size={0}>\n 100 keeps text and borders neutral — lower values blend in the accent\n </Text>\n </Stack>\n </Stack>\n </Card>\n )\n}\n\n/** One color of a scheme: a swatch with its value and an optional reset button */\nfunction ColorRow(props: {\n /** The color the generator actually applied, when it may differ from the input */\n adjusted?: string\n /** Whether the value is derived or a default rather than explicitly set */\n auto?: boolean\n /** What the value is when it is not set: derived (`auto`) or the stock one (`default`) */\n autoLabel?: 'auto' | 'default'\n /** The accessible name of the picker */\n label: string\n onChange: (value: string) => void\n onClear?: () => void\n title: string\n value: string\n}) {\n const {adjusted, auto, autoLabel = 'auto', label, onChange, onClear, title, value} = props\n\n return (\n <Flex align=\"center\" gap={2}>\n <input\n aria-label={label}\n className={swatch}\n onChange={(event) => onChange(event.currentTarget.value)}\n type=\"color\"\n value={value}\n />\n <Stack className={colorRowText} flex={1} gap={2}>\n <Text size={1}>{title}</Text>\n <Text muted size={0} textOverflow=\"ellipsis\">\n {value}\n {adjusted !== undefined && adjusted !== value ? ` → ${adjusted}` : ''}\n {auto ? ` · ${autoLabel}` : ''}\n </Text>\n </Stack>\n {onClear && !auto && (\n <TooltipButton\n icon={ResetIcon}\n mode=\"bleed\"\n onClick={onClear}\n padding={2}\n tooltip={`Reset to ${autoLabel}`}\n />\n )}\n </Flex>\n )\n}\n","import {isColor} from '../lib/mix'\nimport {\n BuildThemeOptions,\n MAXIMUM_CONTRAST,\n MINIMUM_CONTRAST,\n SCHEMES,\n SchemeThemeOptions,\n} from '../theme/options'\nimport {displayTitle} from './themes'\n\n/** What a theme code carries: a theme's title and its options @internal */\nexport interface SharedTheme {\n title: string\n options: BuildThemeOptions\n}\n\n/**\n * The version of the code format, the first byte of every code — bumped if\n * the layout below ever changes, so older codes can still be told apart\n */\nconst VERSION = 1\n\n/** The fields a scheme's options can set, in the order their bytes go */\nconst FIELDS = ['accent', 'text', 'background', 'contrast'] as const\n\n/**\n * Turns a theme into a short code to share in a message: `title`, and the\n * colors and contrasts of both schemes, packed into bytes — one flag byte says\n * which of the eight fields are set, each color is its three RGB bytes and a\n * contrast one byte, and the title takes up the rest — then base64url'd, so\n * it is one word without padding, safe in URLs and chat. A theme setting\n * everything comes to about 30 bytes plus its title.\n *\n * @internal\n */\nexport function encodeTheme(theme: SharedTheme): string {\n const bytes: number[] = [VERSION, 0]\n let flags = 0\n\n SCHEMES.forEach((scheme, schemeIndex) => {\n const options: SchemeThemeOptions = theme.options[scheme] ?? {}\n\n FIELDS.forEach((field, fieldIndex) => {\n const value = options[field]\n\n if (value === undefined) return\n\n if (field === 'contrast') {\n if (typeof value !== 'number' || !Number.isFinite(value)) return\n\n bytes.push(Math.round(Math.min(MAXIMUM_CONTRAST, Math.max(MINIMUM_CONTRAST, value))))\n } else {\n if (typeof value !== 'string' || !isColor(value)) return\n\n bytes.push(...rgbBytes(value))\n }\n\n flags |= 1 << (schemeIndex * FIELDS.length + fieldIndex)\n })\n })\n\n bytes[1] = flags\n bytes.push(...new TextEncoder().encode(theme.title.trim()))\n\n return toBase64Url(Uint8Array.from(bytes))\n}\n\n/**\n * Reads a theme back out of a code — as pasted, so surrounding whitespace and\n * words around the code are fine: the first word that is a code counts. `null`\n * when nothing in the text is one.\n *\n * @internal\n */\nexport function decodeTheme(text: string): SharedTheme | null {\n for (const word of text.trim().split(/\\s+/)) {\n const theme = decodeCode(word)\n\n if (theme) return theme\n }\n\n return null\n}\n\nfunction decodeCode(code: string): SharedTheme | null {\n const bytes = fromBase64Url(code)\n\n if (!bytes || bytes.length < 2 || bytes[0] !== VERSION) return null\n\n const flags = bytes[1]\n const options: BuildThemeOptions = {}\n let offset = 2\n\n for (const [schemeIndex, scheme] of SCHEMES.entries()) {\n const schemeOptions: SchemeThemeOptions = {}\n\n for (const [fieldIndex, field] of FIELDS.entries()) {\n if (!(flags & (1 << (schemeIndex * FIELDS.length + fieldIndex)))) continue\n\n if (field === 'contrast') {\n if (offset + 1 > bytes.length) return null\n\n schemeOptions.contrast = Math.min(\n MAXIMUM_CONTRAST,\n Math.max(MINIMUM_CONTRAST, bytes[offset]),\n )\n offset += 1\n } else {\n if (offset + 3 > bytes.length) return null\n\n schemeOptions[field] = hexColor(bytes.subarray(offset, offset + 3))\n offset += 3\n }\n }\n\n if (Object.keys(schemeOptions).length > 0) options[scheme] = schemeOptions\n }\n\n let title: string\n\n try {\n title = new TextDecoder('utf-8', {fatal: true}).decode(bytes.subarray(offset))\n } catch {\n return null\n }\n\n return {title: displayTitle(title), options}\n}\n\n/** The three RGB bytes of a `#rgb` or `#rrggbb` color */\nfunction rgbBytes(color: string): number[] {\n const hex = color.length === 4 ? color.slice(1).replaceAll(/./g, '$&$&') : color.slice(1)\n\n return [0, 2, 4].map((start) => parseInt(hex.slice(start, start + 2), 16))\n}\n\nfunction hexColor(rgb: Uint8Array): string {\n return `#${[...rgb].map((byte) => byte.toString(16).padStart(2, '0')).join('')}`\n}\n\nfunction toBase64Url(bytes: Uint8Array): string {\n let binary = ''\n\n for (const byte of bytes) binary += String.fromCharCode(byte)\n\n return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '')\n}\n\nfunction fromBase64Url(code: string): Uint8Array | null {\n if (!/^[A-Za-z0-9_-]+$/.test(code)) return null\n\n const base64 = code.replaceAll('-', '+').replaceAll('_', '/')\n\n try {\n return Uint8Array.from(atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, '=')), (char) =>\n char.charCodeAt(0),\n )\n } catch {\n return null\n }\n}\n","import {useToast} from '@sanity/ui/toast'\nimport {useEffect} from 'react'\n\nimport {useThemer} from './context'\nimport {decodeTheme, encodeTheme, SharedTheme} from './share'\nimport {displayTitle} from './themes'\n\n/**\n * Copying a theme as a code to share, and adding a theme from a code someone\n * shared — with toasts telling how it went.\n *\n * @internal\n */\nexport function useThemeCodes(): {\n copyTheme: (theme: SharedTheme) => Promise<void>\n /** Adds the theme in the text, if there is one — the result says whether there was */\n addThemeFromText: (text: string) => boolean\n /** Adds the theme on the clipboard, if there is one and the browser hands the clipboard over */\n addThemeFromClipboard: () => Promise<'added' | 'no-theme' | 'no-clipboard'>\n} {\n const {send} = useThemer()\n const toast = useToast()\n\n const copyTheme = async (theme: SharedTheme) => {\n const title = displayTitle(theme.title)\n\n try {\n await navigator.clipboard.writeText(encodeTheme(theme))\n toast.push({\n status: 'success',\n title: `Copied ${title} as a code`,\n description: 'Paste it in the themer of another Studio to add the theme there',\n })\n } catch {\n toast.push({status: 'error', title: `Could not copy ${title}`})\n }\n }\n\n const addThemeFromText = (text: string) => {\n const theme = decodeTheme(text)\n\n if (!theme) return false\n\n send({type: 'theme.import', title: theme.title, options: theme.options})\n toast.push({status: 'success', title: `Added ${theme.title}`})\n\n return true\n }\n\n const addThemeFromClipboard = async () => {\n let text: string\n\n try {\n text = await navigator.clipboard.readText()\n } catch {\n return 'no-clipboard'\n }\n\n return addThemeFromText(text) ? 'added' : 'no-theme'\n }\n\n return {copyTheme, addThemeFromText, addThemeFromClipboard}\n}\n\n/**\n * Adds a theme pasted anywhere in the Studio while the themer's list is\n * showing — pastes into inputs and other editable things are theirs.\n *\n * @internal\n */\nexport function usePasteThemeCodes(): void {\n const {open, send} = useThemer()\n const toast = useToast()\n\n useEffect(() => {\n if (!open) return undefined\n\n const handlePaste = (event: ClipboardEvent) => {\n if (isEditable(event.target)) return\n\n const theme = decodeTheme(event.clipboardData?.getData('text/plain') ?? '')\n\n if (!theme) return\n\n event.preventDefault()\n send({type: 'theme.import', title: theme.title, options: theme.options})\n toast.push({status: 'success', title: `Added ${theme.title}`})\n }\n\n document.addEventListener('paste', handlePaste)\n\n return () => document.removeEventListener('paste', handlePaste)\n }, [open, send, toast])\n}\n\nfunction isEditable(target: EventTarget | null): boolean {\n return (\n target instanceof HTMLElement &&\n (target.isContentEditable || target.matches('input, textarea, select'))\n )\n}\n","import {Button, Card, Dialog, Flex, Stack, Text, TextInput} from '@sanity/ui'\nimport {useState} from 'react'\n\nimport {useThemeCodes} from './useThemeCodes'\n\n/**\n * Where a theme code someone shared gets pasted by hand — for browsers that\n * do not hand the clipboard over, and for codes that are not on it.\n *\n * @internal\n */\nexport function PasteThemeDialog(props: {onClose: () => void}) {\n const {onClose} = props\n const {addThemeFromText} = useThemeCodes()\n const [text, setText] = useState('')\n const [invalid, setInvalid] = useState(false)\n\n const add = () => {\n if (addThemeFromText(text)) {\n onClose()\n } else {\n setInvalid(true)\n }\n }\n\n return (\n <Dialog\n animate\n footer={\n <Flex gap={2} justify=\"flex-end\" padding={3}>\n <Button mode=\"ghost\" onClick={onClose} padding={2} text=\"Cancel\" />\n <Button\n disabled={text.trim() === ''}\n onClick={add}\n padding={2}\n text=\"Add theme\"\n tone=\"primary\"\n />\n </Flex>\n }\n header=\"Add a shared theme\"\n id=\"themer-paste\"\n onClickOutside={onClose}\n onClose={onClose}\n width={0}\n >\n <form\n onSubmit={(event) => {\n event.preventDefault()\n add()\n }}\n >\n <Stack gap={3} padding={4}>\n <Text muted size={1}>\n Paste the code someone copied from their themer — the theme is added to your list and\n applied.\n </Text>\n <TextInput\n aria-describedby={invalid ? 'themer-paste-error' : undefined}\n aria-invalid={invalid}\n aria-label=\"Theme code\"\n autoFocus\n customValidity={invalid ? 'That is not a theme code' : undefined}\n fontSize={1}\n onChange={(event) => {\n setText(event.currentTarget.value)\n setInvalid(false)\n }}\n placeholder=\"AQ…\"\n value={text}\n />\n {invalid && (\n <Card id=\"themer-paste-error\" padding={3} radius={2} role=\"alert\" tone=\"critical\">\n <Text size={1}>\n That is not a theme code — codes are one word, copied from a theme's menu.\n </Text>\n </Card>\n )}\n </Stack>\n </form>\n </Dialog>\n )\n}\n","import {style} from '@vanilla-extract/css'\n\nexport const root = style({\n position: 'relative',\n})\n\n/** A bare button, so that the thumbnail and the title can carry the styling */\nexport const pickButton = style({\n 'appearance': 'none',\n 'display': 'block',\n 'boxSizing': 'border-box',\n 'width': '100%',\n 'margin': 0,\n 'padding': 0,\n 'border': 0,\n 'background': 'none',\n 'color': 'inherit',\n 'font': 'inherit',\n 'textAlign': 'center',\n 'cursor': 'pointer',\n ':focus': {\n outline: 'none',\n },\n})\n\n/**\n * Wraps the thumbnail with room for the ring that marks the applied theme —\n * hovering shows a faint ring, the applied theme the focus ring color, and\n * keyboard focus an outline. The rings are drawn inside the frame's padding,\n * so nothing sticks out to be clipped by the scrolling list.\n */\nexport const frame = style({\n display: 'block',\n padding: 4,\n borderRadius: 10,\n transition: 'box-shadow 100ms',\n selectors: {\n [`${pickButton}:hover &`]: {\n boxShadow: 'inset 0 0 0 2px var(--card-border-color)',\n },\n [`${pickButton}[aria-pressed='true'] &`]: {\n boxShadow: 'inset 0 0 0 2px var(--card-focus-ring-color)',\n },\n [`${pickButton}:focus-visible &`]: {\n outline: '2px solid var(--card-focus-ring-color)',\n outlineOffset: -2,\n },\n },\n})\n\n/**\n * The actions menu sits on the thumbnail's top right corner, like the actions\n * of an image input, and only shows on hover, on keyboard focus, and while it\n * is open — its popover is portaled, so an open menu does not count as focus\n * within\n */\nexport const menuSlot = style({\n position: 'absolute',\n top: 10,\n right: 10,\n opacity: 0,\n transition: 'opacity 100ms',\n selectors: {\n [`${root}:hover &, ${root}:focus-within &, &[data-visible='true']`]: {\n opacity: 1,\n },\n },\n})\n\n/** The dark card behind the menu button sizes to the button, not the slot */\nexport const menuCard = style({\n display: 'inline-block',\n})\n","import {ArrowDownIcon} from '@sanity/icons/ArrowDown'\nimport {ArrowUpIcon} from '@sanity/icons/ArrowUp'\nimport {CopyIcon} from '@sanity/icons/Copy'\nimport {EditIcon} from '@sanity/icons/Edit'\nimport {EllipsisHorizontalIcon} from '@sanity/icons/EllipsisHorizontal'\nimport {ShareIcon} from '@sanity/icons/Share'\nimport {TrashIcon} from '@sanity/icons/Trash'\nimport {Box, Button, Card, Text, useRootTheme} from '@sanity/ui'\nimport {Menu, MenuButton, MenuDivider, MenuItem} from '@sanity/ui/menu'\nimport {Tooltip} from '@sanity/ui/tooltip'\nimport {Reorder, useDragControls} from 'motion/react'\nimport {useRef, useState} from 'react'\nimport scrollIntoView from 'scroll-into-view-if-needed'\n\nimport {useThemer} from './context'\nimport {displayTitle, ThemerTheme} from './themes'\nimport {ThemeThumbnail} from './ThemeThumbnail'\nimport {useThemeCodes} from './useThemeCodes'\n\nimport {frame, menuCard, menuSlot, pickButton, root} from './ThemeCard.css'\n\n/**\n * Scrolls the applied theme's card into view when it is not already, as a\n * ref: the list scrolls to it as it mounts — coming back from the editor or\n * the removed themes — and when another theme gets applied\n */\nfunction scrollRefIntoViewIfNeeded(node: HTMLElement | null) {\n if (node) scrollIntoView(node, {block: 'nearest', scrollMode: 'if-needed'})\n}\n\n/**\n * One theme in the list: the floating preview with the title below it, like\n * an appearance option in macOS System Settings. Clicking it applies the\n * theme to the whole Studio, dragging it moves the theme to another place in\n * the list; its menu holds the move, edit, duplicate and remove flows that\n * apply to the theme.\n *\n * @internal\n */\nexport function ThemeCard(props: {active: boolean; theme: ThemerTheme}) {\n const {active, theme} = props\n const {themes, send} = useThemer()\n const {copyTheme} = useThemeCodes()\n const {scheme} = useRootTheme()\n const [menuOpen, setMenuOpen] = useState(false)\n const dragControls = useDragControls()\n // Letting go of a drag also clicks the card, and that click must not pick\n const dragged = useRef(false)\n const title = displayTitle(theme.title)\n const index = themes.findIndex((candidate) => candidate.slug === theme.slug)\n\n const moveTo = (to: number) => {\n const order = themes.map((candidate) => candidate.slug)\n\n order.splice(to, 0, ...order.splice(index, 1))\n send({type: 'theme.reorder', order})\n }\n\n return (\n <Reorder.Item\n as=\"div\"\n className={root}\n dragControls={dragControls}\n dragListener={false}\n onDragEnd={() => {\n // The click lands before the next frame\n requestAnimationFrame(() => {\n dragged.current = false\n })\n }}\n onDragStart={() => {\n dragged.current = true\n }}\n ref={active ? scrollRefIntoViewIfNeeded : undefined}\n value={theme.slug}\n whileDrag={{scale: 1.04}}\n >\n <button\n aria-pressed={active}\n className={pickButton}\n onClick={() => {\n if (!dragged.current) send({type: 'theme.pick', slug: theme.slug})\n }}\n onPointerDown={(event) => {\n // A mouse or pen drags the card off the button; a finger scrolls the\n // list instead, and moves themes from the menu\n if (event.pointerType !== 'touch') dragControls.start(event)\n }}\n type=\"button\"\n >\n <span className={frame}>\n <ThemeThumbnail options={theme.options} />\n </span>\n <Box as=\"span\" display=\"block\" paddingTop={2} paddingX={2}>\n <Text\n align=\"center\"\n as=\"span\"\n size={1}\n textOverflow=\"ellipsis\"\n weight={active ? 'medium' : 'regular'}\n >\n {title}\n </Text>\n </Box>\n </button>\n\n {/* The tooltip wraps a card rather than the button: the menu button\n clones its button to wire it up, which a tooltip in between would\n swallow. The card keeps the button dark, as it sits on the dark\n half of the thumbnail, while the menu opens in the sidebar's scheme */}\n <div className={menuSlot} data-visible={menuOpen}>\n <Tooltip animate content={<Text size={1}>Show more</Text>} placement=\"bottom\" portal>\n <Card className={menuCard} radius={2} scheme=\"dark\">\n <MenuButton\n button={\n <Button\n aria-label={`Show more for ${title}`}\n fontSize={1}\n icon={EllipsisHorizontalIcon}\n mode=\"ghost\"\n padding={1}\n radius={2}\n />\n }\n id={`themer-theme-${theme.slug}`}\n menu={\n <Menu>\n <MenuItem\n disabled={index <= 0}\n icon={ArrowUpIcon}\n onClick={() => moveTo(index - 1)}\n text=\"Move up\"\n />\n <MenuItem\n disabled={index >= themes.length - 1}\n icon={ArrowDownIcon}\n onClick={() => moveTo(index + 1)}\n text=\"Move down\"\n />\n <MenuDivider />\n {theme.source === 'custom' && (\n <MenuItem\n icon={EditIcon}\n onClick={() => send({type: 'theme.edit', slug: theme.slug})}\n text=\"Edit\"\n />\n )}\n <MenuItem\n icon={CopyIcon}\n onClick={() => send({type: 'theme.duplicate', slug: theme.slug})}\n text={theme.source === 'custom' ? 'Duplicate' : 'Duplicate to edit'}\n />\n <MenuItem\n icon={ShareIcon}\n onClick={() => void copyTheme(theme)}\n text=\"Copy to share\"\n />\n {theme.source !== 'config' && (\n <>\n <MenuDivider />\n <MenuItem\n icon={TrashIcon}\n onClick={() => send({type: 'theme.remove', slug: theme.slug})}\n text=\"Remove\"\n tone=\"critical\"\n />\n </>\n )}\n </Menu>\n }\n onClose={() => setMenuOpen(false)}\n onOpen={() => setMenuOpen(true)}\n popover={{animate: true, placement: 'bottom-end', portal: true, scheme}}\n />\n </Card>\n </Tooltip>\n </div>\n </Reorder.Item>\n )\n}\n","import {style} from '@vanilla-extract/css'\n\nimport {MAXIMUM_WIDTH, MINIMUM_WIDTH} from './sidebarWidth'\n\nimport {sidebarContent} from './ResizableSidebar.css'\n\nconst CARD_GAP = 16\n\n/**\n * The theme cards flow into two columns once the sidebar has been dragged a\n * third of the way from its narrowest to its widest. The breakpoints are\n * container queries on the sidebar's content (`ResizableSidebar`), which is\n * as wide as the sidebar itself.\n */\nconst TWO_COLUMNS_FROM = MINIMUM_WIDTH + (MAXIMUM_WIDTH - MINIMUM_WIDTH) * 0.3\n\n/** Three columns on the wider small screens the sidebar covers, where two would be huge */\nconst THREE_COLUMNS_FROM = 480\n\n/**\n * The columns share the width equally whatever the titles: a plain `1fr` may\n * not shrink below a card's min-content width, and a card's title does not\n * wrap, so one long title would widen its column past the sidebar\n */\nconst COLUMN = 'minmax(0, 1fr)'\n\nexport const cardGrid = style({\n 'display': 'grid',\n 'gridTemplateColumns': COLUMN,\n 'gap': CARD_GAP,\n '@container': {\n [`${sidebarContent} (min-width: ${TWO_COLUMNS_FROM}px)`]: {\n gridTemplateColumns: `repeat(2, ${COLUMN})`,\n },\n [`${sidebarContent} (min-width: ${THREE_COLUMNS_FROM}px)`]: {\n gridTemplateColumns: `repeat(3, ${COLUMN})`,\n },\n },\n})\n","import {AddIcon} from '@sanity/icons/Add'\nimport {ClipboardIcon} from '@sanity/icons/Clipboard'\nimport {RestoreIcon} from '@sanity/icons/Restore'\nimport {Box, Button, Card, Flex, Stack} from '@sanity/ui'\nimport {MotionConfig, Reorder} from 'motion/react'\nimport {useState} from 'react'\n\nimport {useThemer} from './context'\nimport {ImageFileButton} from './ImageFileButton'\nimport {optionsFromImagePalette, titleFromFileName} from './imagePalette'\nimport {PasteThemeDialog} from './PasteThemeDialog'\nimport {ScrollArea} from './ScrollArea'\nimport {ThemeCard} from './ThemeCard'\nimport {TooltipButton} from './TooltipButton'\nimport {useImagePalette} from './useImagePalette'\nimport {usePasteThemeCodes, useThemeCodes} from './useThemeCodes'\n\nimport {cardGrid} from './ThemeList.css'\n\n/**\n * One column at the sidebar's default width; wider sidebars — and the\n * overlay on small screens — fit more cards per row\n */\n/**\n * The flow for picking a theme: a grid of theme cards — the configured theme,\n * the presets and the user's own themes — that drag into the order the user\n * wants, with the entry points to the add and restore flows below. A theme\n * code someone shared pastes right into the list, or through the paste button.\n *\n * @internal\n */\nexport function ThemeList() {\n const {themes, removed, active, send} = useThemer()\n const {addThemeFromClipboard} = useThemeCodes()\n const [pasting, setPasting] = useState(false)\n\n usePasteThemeCodes()\n\n const {busy, pickImage} = useImagePalette((palette, file) =>\n send({\n type: 'theme.add',\n title: titleFromFileName(file.name),\n options: optionsFromImagePalette(palette),\n palette,\n imageUrl: URL.createObjectURL(file),\n }),\n )\n\n return (\n <>\n <ScrollArea padding={3}>\n <Stack gap={4}>\n {/* The group tells a column from a grid by measuring the cards, and\n lets go of the layout animations for users who prefer reduced motion */}\n <MotionConfig reducedMotion=\"user\">\n <Reorder.Group\n as=\"div\"\n className={cardGrid}\n onReorder={(order: string[]) => send({type: 'theme.reorder', order})}\n values={themes.map((theme) => theme.slug)}\n >\n {themes.map((theme) => (\n <ThemeCard active={theme.slug === active.slug} key={theme.slug} theme={theme} />\n ))}\n </Reorder.Group>\n </MotionConfig>\n {/* Trails the list rather than sitting in the footer, so the footer\n does not shift when the first theme gets removed */}\n {removed.length > 0 && (\n <Button\n gap={2}\n icon={RestoreIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.removed'})}\n padding={2}\n text={`Show removed (${removed.length})`}\n width=\"fill\"\n />\n )}\n </Stack>\n </ScrollArea>\n\n <Card borderTop padding={3}>\n <Flex gap={2}>\n <Box flex={1}>\n <TooltipButton\n icon={AddIcon}\n mode=\"ghost\"\n onClick={() => send({type: 'theme.add'})}\n text=\"Add theme\"\n tooltip=\"Add a theme based on the applied one\"\n width=\"fill\"\n />\n </Box>\n <ImageFileButton\n loading={busy}\n mode=\"ghost\"\n onFile={pickImage}\n tooltip=\"Add a theme from the colors of an image\"\n />\n <TooltipButton\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={async () => {\n // Straight from the clipboard where the browser allows; by hand otherwise\n if ((await addThemeFromClipboard()) !== 'added') setPasting(true)\n }}\n tooltip=\"Add a theme from a code someone shared\"\n />\n </Flex>\n </Card>\n\n {pasting && <PasteThemeDialog onClose={() => setPasting(false)} />}\n </>\n )\n}\n","import {BuildThemeOptions, SCHEMES, SchemeThemeOptions} from '../theme/options'\nimport {minimizeOptions} from './options'\n\n/**\n * Serializes theme options into the `buildTheme` call to paste into\n * `sanity.config.ts`, keeping only what differs from the derived defaults in\n * each scheme.\n *\n * Options that boil down to the stock Studio theme need nothing from this\n * package — so they serialize to a bare `buildTheme()` from `@sanity/ui/theme`\n * instead.\n *\n * @internal\n */\nexport function createThemeSnippet(options: BuildThemeOptions): string {\n const minimized = minimizeOptions(options)\n\n if (minimized === null) {\n return \"import {buildTheme} from '@sanity/ui/theme'\\n\\nexport const theme = buildTheme()\\n\"\n }\n\n const lines: string[] = []\n\n for (const scheme of SCHEMES) {\n const values = minimized[scheme]\n\n if (values) lines.push(serializeScheme(scheme, values))\n }\n\n return `import {buildTheme} from '@sanity/themer'\\n\\nexport const theme = buildTheme({\\n${lines.join('\\n')}\\n})\\n`\n}\n\n/** A scheme with up to two options fits on one line, more spread out */\nfunction serializeScheme(scheme: string, values: SchemeThemeOptions): string {\n const entries: string[] = []\n\n if (values.accent !== undefined) entries.push(`accent: '${values.accent}'`)\n if (values.text !== undefined) entries.push(`text: '${values.text}'`)\n if (values.background !== undefined) entries.push(`background: '${values.background}'`)\n if (values.contrast !== undefined) entries.push(`contrast: ${values.contrast}`)\n\n if (entries.length <= 2) {\n return ` ${scheme}: {${entries.join(', ')}},`\n }\n\n return ` ${scheme}: {\\n${entries.map((entry) => ` ${entry},`).join('\\n')}\\n },`\n}\n","import {ClipboardIcon} from '@sanity/icons/Clipboard'\nimport {Box, Button, Card, Dialog, Flex, Stack, Text} from '@sanity/ui'\nimport {Code} from '@sanity/ui/code'\nimport {useToast} from '@sanity/ui/toast'\nimport {registerLanguage} from 'react-refractor'\nimport typescript from 'refractor/typescript'\n\nimport {createThemeSnippet} from './snippet'\nimport {displayTitle, ThemerTheme} from './themes'\n\n// `Code` only highlights languages the surrounding app has registered with\n// react-refractor, and the Studio registers its own set from an async import\n// during startup — a race this dialog keeps losing. Registering the one\n// language the snippet needs keeps it highlighted from the first render.\nregisterLanguage(typescript)\n\n/**\n * Shows the `buildTheme` snippet that makes a theme permanent, in a dialog so\n * that it does not take up sidebar space or make the sidebar scroll as the\n * snippet changes shape with the theme.\n *\n * @internal\n */\nexport function ThemeSnippetDialog(props: {onClose: () => void; theme: ThemerTheme}) {\n const {onClose, theme} = props\n const toast = useToast()\n const snippet = createThemeSnippet(theme.options)\n\n const handleCopy = async () => {\n try {\n await navigator.clipboard.writeText(snippet)\n toast.push({status: 'success', title: 'Copied the theme to the clipboard'})\n } catch {\n toast.push({status: 'error', title: 'Could not copy the theme'})\n }\n }\n\n return (\n <Dialog\n animate\n footer={\n <Flex gap={2} justify=\"flex-end\" padding={3}>\n <Button\n gap={2}\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={() => void handleCopy()}\n padding={2}\n text=\"Copy\"\n />\n </Flex>\n }\n header=\"Add to your config\"\n id=\"themer-snippet\"\n onClickOutside={onClose}\n onClose={onClose}\n width={1}\n >\n <Box padding={4}>\n <Stack gap={4}>\n <Text muted size={1}>\n Export this from <code>sanity.config.ts</code> and pass it as the <code>theme</code> of{' '}\n <code>defineConfig</code> to make “{displayTitle(theme.title)}” the Studio theme without\n this tool.\n </Text>\n <Card border overflow=\"auto\" padding={3} radius={2} tone=\"transparent\">\n <Code language=\"ts\" size={1}>\n {snippet}\n </Code>\n </Card>\n </Stack>\n </Box>\n </Dialog>\n )\n}\n","import {style} from '@vanilla-extract/css'\n\n/** Lets the title shrink and truncate instead of pushing the buttons out */\nexport const title = style({\n minWidth: 0,\n})\n","import {ArrowLeftIcon} from '@sanity/icons/ArrowLeft'\nimport {CloseIcon} from '@sanity/icons/Close'\nimport {CodeBlockIcon} from '@sanity/icons/CodeBlock'\nimport {SplitVerticalIcon} from '@sanity/icons/SplitVertical'\nimport {Box, Card, Flex, Text} from '@sanity/ui'\nimport {useState} from 'react'\n\nimport {useThemer} from './context'\nimport {RemovedThemes} from './RemovedThemes'\nimport {ThemeEditor} from './ThemeEditor'\nimport {ThemeList} from './ThemeList'\nimport {ThemeSnippetDialog} from './ThemeSnippetDialog'\nimport {TooltipButton} from './TooltipButton'\n\nimport {title} from './ThemerSidebar.css'\n\nconst VIEW_TITLES = {\n list: 'Themer',\n edit: 'Edit theme',\n removed: 'Removed themes',\n} as const\n\n/**\n * The themer sidebar: a header that navigates between the flows and toggles\n * the split preview, the flow itself — picking a theme, editing one, or\n * restoring removed ones — and the dialog with the `buildTheme` snippet of\n * the applied theme.\n *\n * @internal\n */\nexport function ThemerSidebar() {\n const {active, split, view, navbarHeight, send} = useThemer()\n const [snippetOpen, setSnippetOpen] = useState(false)\n const inList = view.name === 'list'\n\n return (\n <Card height=\"fill\">\n <Flex direction=\"column\" height=\"fill\">\n {/* The header sits next to the Studio navbar and takes its height, so\n the two bottom borders line up — whatever height the Studio, its\n breakpoint or a custom navbar gives it */}\n <Card\n borderBottom\n padding={3}\n sizing=\"border\"\n style={navbarHeight === null ? undefined : {height: navbarHeight}}\n >\n <Flex align=\"center\" gap={1} height=\"fill\">\n {!inList && (\n <TooltipButton\n icon={ArrowLeftIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'flow.list'})}\n padding={2}\n tooltip=\"Back to the themes\"\n />\n )}\n <Box className={title} flex={1}>\n <Text size={1} textOverflow=\"ellipsis\" weight=\"semibold\">\n {VIEW_TITLES[view.name]}\n </Text>\n </Box>\n {/* Packed without gaps, so the title still fits next to them at the\n narrowest sidebar width */}\n <Flex>\n {view.name !== 'removed' && (\n <>\n <TooltipButton\n aria-pressed={split}\n icon={SplitVerticalIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'preview.toggle'})}\n padding={2}\n selected={split}\n tooltip=\"Show light and dark side by side\"\n />\n <TooltipButton\n icon={CodeBlockIcon}\n mode=\"bleed\"\n onClick={() => setSnippetOpen(true)}\n padding={2}\n tooltip=\"Show the code for the applied theme\"\n />\n </>\n )}\n <TooltipButton\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.close'})}\n padding={2}\n tooltip=\"Close themer\"\n />\n </Flex>\n </Flex>\n </Card>\n\n {view.name === 'list' && <ThemeList />}\n {view.name === 'edit' && <ThemeEditor focusTitle={view.focusTitle} slug={view.slug} />}\n {view.name === 'removed' && <RemovedThemes />}\n </Flex>\n\n {snippetOpen && <ThemeSnippetDialog onClose={() => setSnippetOpen(false)} theme={active} />}\n </Card>\n )\n}\n","import {createContainer, createViewTransition, style} from '@vanilla-extract/css'\n\n/**\n * The view transition group of the sidebar, which the split preview's\n * transition (see `ThemerLayout.css.ts`) leaves alone: it never animates, and\n * the Studio copies animating underneath never paint over it\n */\nexport const sidebarTransition = createViewTransition()\n\n/** The sidebar's content, which the content's layout queries — as wide as the sidebar itself */\nexport const sidebarContent = createContainer()\n\nexport const sidebar = style({\n flex: 'none',\n viewTransitionName: sidebarTransition,\n selectors: {\n // On small screens the sidebar covers the Studio instead of standing next to it\n \"&[data-overlay='true']\": {\n position: 'absolute',\n inset: 0,\n width: 'auto',\n },\n },\n})\n\n/**\n * Draws the sidebar's border and gives the resize handle its card colors. It\n * does not clip, so the handle can straddle the border — the content is\n * clipped one level down instead.\n */\nexport const frame = style({\n position: 'relative',\n})\n\n/**\n * Clips the content, and is the container its layout queries: as wide as\n * the sidebar, whether or not a scrollbar takes room inside — see the theme\n * grid's columns in `ThemeList.css.ts`\n */\nexport const content = style({\n containerType: 'inline-size',\n containerName: sidebarContent,\n})\n\n/**\n * The grab area along the sidebar's left edge. It straddles the border so it\n * is easy to hit, and highlights while hovered, focused or dragged.\n */\nexport const resizeHandle = style({\n 'position': 'absolute',\n 'top': 0,\n 'bottom': 0,\n 'left': -3,\n 'zIndex': 1,\n 'width': 7,\n 'cursor': 'col-resize',\n 'touchAction': 'none',\n '::after': {\n content: '',\n position: 'absolute',\n top: 0,\n bottom: 0,\n left: 2,\n width: 3,\n background: 'var(--card-focus-ring-color)',\n opacity: 0,\n transition: 'opacity 100ms',\n },\n ':focus': {\n outline: 'none',\n },\n 'selectors': {\n \"&:hover::after, &:focus-visible::after, &[data-dragging='true']::after\": {\n opacity: 1,\n },\n },\n})\n","import {Box, Card, Layer} from '@sanity/ui'\nimport {useCallback, useEffect, useRef, useState} from 'react'\n\nimport {MAXIMUM_WIDTH, MINIMUM_WIDTH} from './sidebarWidth'\nimport {ThemerSidebar} from './ThemerSidebar'\n\nimport {content, frame, resizeHandle, sidebar} from './ResizableSidebar.css'\n\n/** How far one arrow key press resizes the sidebar */\nconst KEYBOARD_STEP = 16\n\nconst WIDTH_STORAGE_KEY = 'sanityStudio:themer:width'\n\nfunction clampWidth(width: number): number {\n return Math.min(MAXIMUM_WIDTH, Math.max(MINIMUM_WIDTH, Math.round(width)))\n}\n\nfunction readStoredWidth(): number {\n try {\n const stored = Number(localStorage.getItem(WIDTH_STORAGE_KEY))\n\n return Number.isFinite(stored) && stored > 0 ? clampWidth(stored) : MINIMUM_WIDTH\n } catch {\n return MINIMUM_WIDTH\n }\n}\n\nfunction writeStoredWidth(width: number): void {\n try {\n if (width === MINIMUM_WIDTH) {\n localStorage.removeItem(WIDTH_STORAGE_KEY)\n } else {\n localStorage.setItem(WIDTH_STORAGE_KEY, String(width))\n }\n } catch {\n // Storage can be unavailable (e.g. private browsing) — the width just won't persist\n }\n}\n\n/**\n * The themer sidebar, in a layer along the right edge of the Studio. Its left\n * edge drags (or arrow-keys) it wider, up to twice its default width — the\n * width sticks between sessions. As an overlay (small screens) it covers the\n * Studio edge to edge instead, with nothing to resize.\n *\n * @internal\n */\nexport function ResizableSidebar(props: {overlay: boolean}) {\n const {overlay} = props\n const [width, setWidth] = useState(readStoredWidth)\n const [dragging, setDragging] = useState(false)\n const drag = useRef<{pointerId: number; startX: number; startWidth: number} | null>(null)\n\n useEffect(() => writeStoredWidth(width), [width])\n\n const handlePointerDown = useCallback(\n (event: React.PointerEvent<HTMLDivElement>) => {\n if (event.button !== 0) return\n\n event.preventDefault()\n event.currentTarget.setPointerCapture(event.pointerId)\n drag.current = {pointerId: event.pointerId, startX: event.clientX, startWidth: width}\n setDragging(true)\n },\n [width],\n )\n\n const handlePointerMove = useCallback((event: React.PointerEvent<HTMLDivElement>) => {\n if (!drag.current || drag.current.pointerId !== event.pointerId) return\n\n // The sidebar sits on the right, so dragging left makes it wider\n setWidth(clampWidth(drag.current.startWidth - (event.clientX - drag.current.startX)))\n }, [])\n\n const handlePointerUp = useCallback((event: React.PointerEvent<HTMLDivElement>) => {\n if (!drag.current || drag.current.pointerId !== event.pointerId) return\n\n drag.current = null\n setDragging(false)\n }, [])\n\n const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {\n const steps: Record<string, number> = {\n ArrowLeft: KEYBOARD_STEP,\n ArrowRight: -KEYBOARD_STEP,\n Home: MAXIMUM_WIDTH,\n End: -MAXIMUM_WIDTH,\n }\n const step = steps[event.key]\n\n if (step === undefined) return\n\n event.preventDefault()\n setWidth((current) => clampWidth(current + step))\n }, [])\n\n return (\n <Layer\n className={sidebar}\n data-overlay={overlay}\n style={overlay ? undefined : {width}}\n zOffset={100}\n >\n <Card borderLeft={!overlay} className={frame} height=\"fill\">\n {!overlay && (\n <div\n aria-label=\"Resize the themer\"\n className={resizeHandle}\n aria-orientation=\"vertical\"\n aria-valuemax={MAXIMUM_WIDTH}\n aria-valuemin={MINIMUM_WIDTH}\n aria-valuenow={width}\n data-dragging={dragging}\n onDoubleClick={() => setWidth(MINIMUM_WIDTH)}\n onKeyDown={handleKeyDown}\n onPointerCancel={handlePointerUp}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n // oxlint-disable-next-line prefer-tag-over-role -- a window splitter is a focusable, draggable separator, which an hr is not\n role=\"separator\"\n tabIndex={0}\n />\n )}\n <Box className={content} height=\"fill\" overflow=\"hidden\">\n <ThemerSidebar />\n </Box>\n </Card>\n </Layer>\n )\n}\n","import {useSyncExternalStore} from 'react'\n\nconst QUERY = '(prefers-reduced-motion: reduce)'\n\nfunction subscribe(onChange: () => void): () => void {\n const media = matchMedia(QUERY)\n\n media.addEventListener('change', onChange)\n\n return () => media.removeEventListener('change', onChange)\n}\n\nfunction getSnapshot(): boolean {\n return matchMedia(QUERY).matches\n}\n\nfunction getServerSnapshot(): boolean {\n return false\n}\n\n/**\n * Whether the user prefers reduced motion — live, so that turning the\n * setting on while the Studio is open takes effect right away.\n *\n * @internal\n */\nexport function usePrefersReducedMotion(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n}\n","import {type RefObject, useEffect, useState} from 'react'\n\n/**\n * The Studio navbar's root, the `Card` that draws its bottom border — its\n * test id is stable and unique to the navbar, unlike its `data-ui` name\n */\nconst NAVBAR_SELECTOR = '[data-testid=\"studio-navbar\"]'\n\n/**\n * The height of the Studio navbar inside the given element, in pixels — kept\n * up to date as the navbar renders, remounts and resizes, and `null` until\n * there is one. The themer's header takes this height, so that its bottom\n * border lines up with the navbar's whatever height the Studio version, its\n * breakpoint or a custom navbar gives it.\n *\n * @internal\n */\nexport function useStudioNavbarHeight(ref: RefObject<HTMLElement | null>): number | null {\n const [height, setHeight] = useState<number | null>(null)\n\n useEffect(() => {\n const root = ref.current\n\n if (!root) return undefined\n\n let navbar: Element | null = null\n const resizeObserver = new ResizeObserver(() => {\n if (navbar) setHeight(navbar.getBoundingClientRect().height)\n })\n\n // The navbar can render after the layout (the Studio suspends while it\n // loads) and remount (switching workspaces), so it is looked up again\n // whenever the Studio's DOM changes — cheap, as it sits near the top\n const observeNavbar = () => {\n const next = root.querySelector(NAVBAR_SELECTOR)\n\n if (next === navbar) return\n\n if (navbar) resizeObserver.unobserve(navbar)\n\n navbar = next\n\n if (navbar) {\n resizeObserver.observe(navbar)\n setHeight(navbar.getBoundingClientRect().height)\n } else {\n setHeight(null)\n }\n }\n\n const mutationObserver = new MutationObserver(observeNavbar)\n\n mutationObserver.observe(root, {childList: true, subtree: true})\n observeNavbar()\n\n return () => {\n mutationObserver.disconnect()\n resizeObserver.disconnect()\n }\n }, [ref])\n\n return height\n}\n","import {addTransitionType, startTransition, useDeferredValue, useEffect, useState} from 'react'\n\n/**\n * `useDeferredValue` with a transition type: the returned value lags behind\n * `value` like a deferred one — rendered in a transition, which React\n * animates — and the view transition React starts for it carries the given\n * type (`document.startViewTransition({types})`), so a stylesheet tells it\n * from any other transition on the page through\n * `:active-view-transition-type()`.\n *\n * React only takes types from `startTransition`, and a deferred render is\n * not one. So while the deferred value lags, a transition of the type joins\n * it, with a state update of its own for the type to belong to: React renders\n * every pending transition in one go, and the commit — the view transition\n * — carries the types of all of them. The value itself stays with\n * `useDeferredValue`, which reads the latest `value` as the deferred render\n * runs: should `value` change back before that render commits, nothing\n * changes and nothing animates — a queued state update would commit the\n * stale value and animate it, only to animate back.\n *\n * @internal\n */\nexport function useTypedDeferredValue<T>(value: T, type: string): T {\n const deferred = useDeferredValue(value)\n const [, setTransitions] = useState(0)\n\n useEffect(() => {\n if (Object.is(deferred, value)) return\n\n startTransition(() => {\n addTransitionType(type)\n setTransitions((count) => count + 1)\n })\n }, [deferred, type, value])\n\n return deferred\n}\n","import {\n createViewTransition,\n globalStyle,\n keyframes,\n style,\n styleVariants,\n} from '@vanilla-extract/css'\n\nimport {sidebarTransition} from './ResizableSidebar.css'\n\n/**\n * The view transition classes of the split preview — what React puts in\n * `view-transition-class`, which the pseudo-element rules below select on.\n * Scoped identifiers, so nothing else on the page can mean the same.\n */\nexport const splitTransitionClasses = {\n /** The Studio the user was looking at cross-fades between its two widths */\n resize: createViewTransition('splitResize'),\n /** The Studio and the panel cross-fade to another theme, in place */\n crossfade: createViewTransition('themeCrossfade'),\n /** The split copy slides in from the side, or out to it */\n slideIn: createViewTransition('splitSlideIn'),\n slideOut: createViewTransition('splitSlideOut'),\n /** The split copy drops in from the top, or out to it, where the copies stack */\n dropIn: createViewTransition('splitDropIn'),\n dropOut: createViewTransition('splitDropOut'),\n}\n\n/** The view transition classes of the panel, which slides in from its edge and out to it */\nexport const panelTransitionClasses = {\n slideIn: createViewTransition('panelSlideIn'),\n slideOut: createViewTransition('panelSlideOut'),\n}\n\n/**\n * The transition type of the layout's motions — what `ThemerLayout` adds to\n * every transition it starts, and React passes on to the view transition.\n * `:active-view-transition-type()` tells the layout's transitions from any\n * other on the page while they run.\n */\nexport const layoutTransitionType = 'sanity-themer'\n\n/** How long the layout's motions take, and how they ease — every group they move shares these */\nconst motion = {\n animationDuration: '320ms',\n animationTimingFunction: 'cubic-bezier(0.2, 0, 0, 1)',\n}\n\n/** The layout's `Flex` positions the sidebar overlay on small screens */\nexport const layout = style({\n position: 'relative',\n})\n\n/** A copy of the Studio in a color scheme of its own, for `color-scheme` */\nexport const studioScheme = styleVariants({\n light: {colorScheme: 'light'},\n dark: {colorScheme: 'dark'},\n})\n\nconst slideIn = keyframes({from: {transform: 'translateX(-100%)'}})\nconst slideOut = keyframes({to: {transform: 'translateX(-100%)'}})\nconst dropIn = keyframes({from: {transform: 'translateY(-100%)'}})\nconst dropOut = keyframes({to: {transform: 'translateY(-100%)'}})\nconst slideInFromEnd = keyframes({from: {transform: 'translateX(100%)'}})\nconst slideOutToEnd = keyframes({to: {transform: 'translateX(100%)'}})\n\n/**\n * How the panel and the split preview animate, through React's view\n * transitions: the panel slides in from its edge and the split copy from the\n * far one — a transform, nothing fades — and out the same way; the Studio the\n * user was looking at cross-fades between its two widths, its old and new\n * snapshots stretched to the group's box so it keeps its height. Everything\n * shares one duration and easing, so the edge a panel slides in on and the\n * edge the Studio gives way with stay together.\n */\nglobalStyle(\n [\n `::view-transition-group(.${splitTransitionClasses.resize})`,\n `::view-transition-old(.${splitTransitionClasses.resize})`,\n `::view-transition-new(.${splitTransitionClasses.resize})`,\n `::view-transition-new(.${splitTransitionClasses.slideIn})`,\n `::view-transition-old(.${splitTransitionClasses.slideOut})`,\n `::view-transition-new(.${splitTransitionClasses.dropIn})`,\n `::view-transition-old(.${splitTransitionClasses.dropOut})`,\n `::view-transition-new(.${panelTransitionClasses.slideIn})`,\n `::view-transition-old(.${panelTransitionClasses.slideOut})`,\n `::view-transition-old(.${splitTransitionClasses.crossfade})`,\n `::view-transition-new(.${splitTransitionClasses.crossfade})`,\n ].join(', '),\n motion,\n)\n\n/**\n * Whatever else on the page has a `view-transition-name` of its own — an\n * avatar the Studio names so it moves as one piece instead of stretching\n * along with the navbar's snapshot, say; names the Studio makes up as it\n * renders, so the split copy's elements have names of their own — takes part\n * in the layout's transitions too, as a group of its own that the browser\n * moves and cross-fades from where it was to where it ends up: at its default\n * quarter of a second and `ease`, out of step with the navbar it sits in.\n * While one of the layout's transitions runs, every group keeps the layout's\n * time instead. Only then: the Studio's own transitions are none of the\n * layout's business and keep their own — and the layout's own groups, which\n * the rules above set to the same time, are no different for it.\n */\nglobalStyle(\n [\n `:root:active-view-transition-type(${layoutTransitionType})::view-transition-group(*)`,\n `:root:active-view-transition-type(${layoutTransitionType})::view-transition-old(*)`,\n `:root:active-view-transition-type(${layoutTransitionType})::view-transition-new(*)`,\n ].join(', '),\n motion,\n)\n\nglobalStyle(\n [\n `::view-transition-old(.${splitTransitionClasses.resize})`,\n `::view-transition-new(.${splitTransitionClasses.resize})`,\n ].join(', '),\n {\n inlineSize: '100%',\n blockSize: '100%',\n objectFit: 'fill',\n },\n)\n\nglobalStyle(`::view-transition-new(.${splitTransitionClasses.slideIn})`, {animationName: slideIn})\nglobalStyle(`::view-transition-old(.${splitTransitionClasses.slideOut})`, {\n animationName: slideOut,\n})\nglobalStyle(`::view-transition-new(.${splitTransitionClasses.dropIn})`, {animationName: dropIn})\nglobalStyle(`::view-transition-old(.${splitTransitionClasses.dropOut})`, {animationName: dropOut})\nglobalStyle(`::view-transition-new(.${panelTransitionClasses.slideIn})`, {\n animationName: slideInFromEnd,\n})\nglobalStyle(`::view-transition-old(.${panelTransitionClasses.slideOut})`, {\n animationName: slideOutToEnd,\n})\n\n/**\n * While the split preview toggles, the sidebar — a group of its own, which\n * `ResizableSidebar` names — does not animate at all: its new snapshot simply\n * shows, stacked above the Studio copies where it covers the Studio (groups of\n * elements that only exist in the new state, the arriving copy, would\n * otherwise be stacked last, over it). While the panel itself opens or closes,\n * or cross-fades to another theme, React names it instead, and the rules\n * above apply.\n *\n */\nglobalStyle(`::view-transition-group(${sidebarTransition})`, {zIndex: 1})\nglobalStyle(\n [\n `::view-transition-group(${sidebarTransition})`,\n `::view-transition-image-pair(${sidebarTransition})`,\n `::view-transition-old(${sidebarTransition})`,\n `::view-transition-new(${sidebarTransition})`,\n ].join(', '),\n {animation: 'none'},\n)\n\n/**\n * Someone who prefers reduced motion sees no motion here: the layout does not\n * start these transitions in the first place (see `ThemerLayout`), and should\n * one run anyway — the preference changed after the layout read it, the\n * Studio ran one of its own over these groups — none of their animations does\n * anything, nor those of any other group the layout's transition moves: the\n * new state simply shows.\n */\nglobalStyle(\n ['group', 'image-pair', 'old', 'new']\n .flatMap((part) => {\n const groups = [\n ...Object.values(splitTransitionClasses).map((className) => `.${className}`),\n ...Object.values(panelTransitionClasses).map((className) => `.${className}`),\n sidebarTransition,\n ]\n const selectors = groups.map((group) => `::view-transition-${part}(${group})`)\n\n selectors.push(\n `:root:active-view-transition-type(${layoutTransitionType})::view-transition-${part}(*)`,\n )\n\n return selectors\n })\n .join(', '),\n {\n '@media': {\n '(prefers-reduced-motion: reduce)': {\n animation: 'none',\n },\n },\n },\n)\n","import {Card, Flex, ThemeProvider, useMediaIndex} from '@sanity/ui'\nimport {type RootTheme, type ThemeColorSchemeKey} from '@sanity/ui/theme'\nimport {useActor, useSelector} from '@xstate/react'\nimport {Activity, useEffect, useMemo, useRef, useState, ViewTransition} from 'react'\nimport {type LayoutProps, useColorSchemeValue} from 'sanity'\n\nimport {buildTheme} from '../theme/buildTheme'\nimport {BuildThemeOptions} from '../theme/options'\nimport {ThemerContext, ThemerContextValue, ThemerView} from './context'\nimport {selectStoredState, ThemerInput, themerMachine, ThemerSnapshot} from './machine'\nimport {ResizableSidebar} from './ResizableSidebar'\nimport {readStoredState} from './storage'\nimport {resolveThemes, ThemerState} from './themes'\nimport {usePrefersReducedMotion} from './usePrefersReducedMotion'\nimport {useStudioNavbarHeight} from './useStudioNavbarHeight'\nimport {useTypedDeferredValue} from './useTypedDeferredValue'\n\nimport {\n layout,\n layoutTransitionType,\n panelTransitionClasses,\n splitTransitionClasses,\n studioScheme,\n} from './ThemerLayout.css'\n\n/**\n * Below this media index the Studio collapses its navbar into a drawer, and\n * the themer follows suit: the sidebar covers the Studio instead of standing\n * next to it, and the split preview stacks instead of sitting side by side.\n */\nconst MOBILE_MEDIA_INDEX = 1\n\nfunction sameStoredState(a: ThemerState, b: ThemerState): boolean {\n return (\n a.active === b.active && a.custom === b.custom && a.removed === b.removed && a.order === b.order\n )\n}\n\nfunction selectView(snapshot: ThemerSnapshot): ThemerView {\n const {editing} = snapshot.context\n\n if (snapshot.matches({flow: 'edit'}) && editing) {\n return {name: 'edit', slug: editing.slug, focusTitle: editing.focusTitle}\n }\n\n if (snapshot.matches({flow: 'removed'})) {\n return {name: 'removed'}\n }\n\n return {name: 'list'}\n}\n\nfunction sameView(a: ThemerView, b: ThemerView): boolean {\n if (a.name !== b.name) return false\n\n return (\n a.name !== 'edit' || b.name !== 'edit' || (a.slug === b.slug && a.focusTitle === b.focusTitle)\n )\n}\n\n/**\n * Wraps the whole Studio so that the theme picked in the themer sidebar\n * applies everywhere while the user browses around, runs the themer machine\n * that the navbar toggle and the sidebar share — the user's themes, which one\n * is applied, which flow the sidebar is in and how the Studio is previewed —\n * and renders the sidebar next to the Studio. The sidebar sits at the\n * `layout` level rather than in `activeToolLayout` because the split preview\n * renders the Studio twice, and the sidebar must not come along.\n *\n * The Studio next to the sidebar always follows the appearance setting\n * (light/dark/system) and the picked theme like any other theme would. The\n * split preview adds a second copy in the opposite scheme on the far side —\n * or on top, on small screens — through React's view transitions (React\n * 19.3), styled in `ThemerLayout.css.ts`.\n *\n * The machine says what shows and when the layout is in motion (its `panel`,\n * `split` and `moving` tags); the layout defers what shows, which puts the\n * panel's and the copy's mounts in a transition — what lets React animate\n * them — and picks the view transition classes from the tags.\n *\n * Whatever else on the page has a `view-transition-name` — an avatar the\n * Studio names so it moves as one piece — comes along in step: every\n * transition the layout starts carries `layoutTransitionType`, which the\n * stylesheet keys on to move every group in the layout's time (see\n * `ThemerLayout.css.ts`).\n *\n * @internal\n */\nexport function ThemerLayout(props: LayoutProps & {baseOptions: BuildThemeOptions}) {\n const {baseOptions, ...layoutProps} = props\n const [input] = useState<ThemerInput>(() => ({baseOptions, stored: readStoredState()}))\n const [snapshot, send, actorRef] = useActor(themerMachine, {input})\n const stored = useSelector(actorRef, selectStoredState, sameStoredState)\n const view = useSelector(actorRef, selectView, sameView)\n const open = snapshot.hasTag('panel')\n const split = snapshot.hasTag('split')\n const moving = snapshot.hasTag('moving')\n const switching = snapshot.hasTag('switching')\n const {images} = snapshot.context\n const mobile = useMediaIndex() <= MOBILE_MEDIA_INDEX\n const studioRef = useRef<HTMLDivElement | null>(null)\n const navbarHeight = useStudioNavbarHeight(studioRef)\n\n // The machine publishes synchronously, which React does not animate. What\n // shows is deferred: that renders the panel's and the copy's mounts in a\n // transition — once the sidebar's own changes (the toggle's pressed state)\n // have committed, so nothing in the sidebar changes while it runs. The\n // transition carries the layout's type, which tells the stylesheet that the\n // view transition running is the layout's. Someone who prefers reduced\n // motion gets no view transition at all: nothing is deferred, so there is\n // no transition render for React to animate, no boundary has a class, and\n // what shows changes along with the machine — and should one run anyway,\n // the stylesheet gives its animations nothing to do\n const reduceMotion = usePrefersReducedMotion()\n const deferredOpen = useTypedDeferredValue(reduceMotion ? null : open, layoutTransitionType)\n const deferredSplit = useTypedDeferredValue(reduceMotion ? null : split, layoutTransitionType)\n const shownOpen = deferredOpen ?? open\n const shownSplit = deferredSplit ?? split\n\n const {themes, removed, active} = useMemo(\n () => resolveThemes(stored, baseOptions),\n [stored, baseOptions],\n )\n\n // The theme identity must be stable between renders: it feeds the\n // styled-components theme context for the whole Studio, and rebuilding it\n // would re-render everything. Keying on the options object keeps it stable\n // across unrelated changes, like renaming a theme.\n const activeOptions = active.source === 'config' ? null : active.options\n const theme = useMemo(\n () => (activeOptions === null ? null : buildTheme(activeOptions)),\n [activeOptions],\n )\n\n // The applied theme is deferred the same way, and the Studio and the panel\n // cross-fade to it while the machine says a theme is being switched to —\n // edits to the applied theme's colors render deferred too, which keeps the\n // pickers responsive, but with nothing to animate them they simply show\n const deferredTheme = useTypedDeferredValue(\n reduceMotion ? undefined : theme,\n layoutTransitionType,\n )\n const shownTheme = deferredTheme === undefined ? theme : deferredTheme\n\n // The Studio paints the body with its configured theme from above this\n // layout, and Safari tints its chrome from the body — so the applied theme\n // has to reach the body too, in the scheme the Studio is showing\n const scheme = useColorSchemeValue()\n const oppositeScheme: ThemeColorSchemeKey = scheme === 'dark' ? 'light' : 'dark'\n\n useEffect(() => {\n const background = shownTheme?.v2?.color[scheme].default.bg\n\n if (background === undefined) return undefined\n\n const {style} = document.body\n const previous = style.backgroundColor\n\n style.backgroundColor = background\n\n return () => {\n style.backgroundColor = previous\n }\n }, [scheme, shownTheme])\n\n // The machine releases the image of a palette as it is replaced or its\n // theme deleted; whatever it still holds goes with the layout — the actor\n // keeps its last snapshot once stopped\n useEffect(\n () => () => {\n for (const url of Object.values(actorRef.getSnapshot().context.images)) {\n URL.revokeObjectURL(url)\n }\n },\n [actorRef],\n )\n\n const context = useMemo<ThemerContextValue>(\n () => ({\n baseOptions,\n themes,\n removed,\n active,\n images,\n view,\n open,\n split,\n mobile,\n navbarHeight,\n send,\n }),\n [active, baseOptions, images, mobile, navbarHeight, open, removed, send, split, themes, view],\n )\n\n const studio = layoutProps.renderDefault(layoutProps)\n\n // Every view transition of the layout tells the machine its motion is under way\n const transitioned = () => send({type: 'layout.transitioned'})\n\n // What the view transitions do this render, from what the machine says\n const classes = reduceMotion\n ? {\n copyEnter: 'none',\n copyExit: 'none',\n panelEnter: 'none',\n panelExit: 'none',\n update: 'none',\n studioUpdate: 'none',\n }\n : {\n copyEnter: mobile ? splitTransitionClasses.dropIn : splitTransitionClasses.slideIn,\n copyExit: mobile ? splitTransitionClasses.dropOut : splitTransitionClasses.slideOut,\n panelEnter: panelTransitionClasses.slideIn,\n panelExit: panelTransitionClasses.slideOut,\n // The copy and the panel cross-fade to another theme, in place\n update: switching ? splitTransitionClasses.crossfade : 'none',\n // The Studio too — unless it is giving way or taking room, which cross-fades as it goes\n studioUpdate: moving\n ? splitTransitionClasses.resize\n : switching\n ? splitTransitionClasses.crossfade\n : 'none',\n }\n\n return (\n <ThemerContext.Provider value={context}>\n <Flex className={layout} direction={mobile ? 'column' : 'row'} height=\"fill\" sizing=\"border\">\n {/* The opposite scheme comes first — on the far side of the sidebar,\n or on top on small screens — so the Studio the user was looking at\n stays where it is, mounted, in its own scheme. A whole second\n Studio is too costly to keep around (its styled-components alone\n insert CSS as they render), so it only exists while the sidebar is\n open — hidden until the split shows, warmed up for the transition */}\n {shownOpen && (\n <Activity mode={shownSplit ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"opposite\"\n enter={classes.copyEnter}\n exit={classes.copyExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <StudioPreview\n borderBottom={mobile}\n borderRight={!mobile}\n scheme={oppositeScheme}\n theme={shownTheme}\n >\n {studio}\n </StudioPreview>\n </ViewTransition>\n </Activity>\n )}\n {/* The Studio gives way and takes room while the machine says the\n layout is moving, and cross-fades while it says a theme is being\n switched to — the Studio updates in transitions of its own all the\n time, and none of those may animate it. Once a transition is under\n way the machine hears of it, and the Studio is its own again before\n the next commit */}\n <ViewTransition key=\"primary\" onUpdate={transitioned} update={classes.studioUpdate}>\n <StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>\n </ViewTransition>\n\n {/* The sidebar is small and cheap: it stays mounted, hidden while\n closed, keeping its state and ready to show */}\n <Activity mode={shownOpen ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n <ResizableSidebar overlay={mobile} />\n </ThemeProvider>\n </ViewTransition>\n </Activity>\n </Flex>\n </ThemerContext.Provider>\n )\n}\n\n/**\n * One copy of the Studio, in the given color scheme — or, without one, in the\n * scheme the Studio is showing. The configured theme (`null`) goes through\n * the provider too, inheriting the Studio's, so picking a theme swaps it\n * instead of remounting the Studio under a new provider.\n *\n * The two copies of the split preview share the router, the document store\n * and every other provider above the layout — only the scheme differs — so\n * they stay in sync while navigating. The `color-scheme` of a forced scheme\n * keeps native form controls and scrollbars in step with it.\n */\nfunction StudioPreview(props: {\n borderBottom?: boolean\n borderRight?: boolean\n children: React.ReactNode\n ref?: React.Ref<HTMLDivElement>\n scheme?: ThemeColorSchemeKey\n theme: RootTheme | null\n}) {\n const {borderBottom, borderRight, children, ref, scheme, theme} = props\n\n return (\n <ThemeProvider scheme={scheme} theme={theme ?? undefined}>\n <Card\n borderBottom={borderBottom}\n borderRight={borderRight}\n className={scheme ? studioScheme[scheme] : undefined}\n flex={1}\n height=\"fill\"\n overflow=\"hidden\"\n ref={ref}\n >\n {children}\n </Card>\n </ThemeProvider>\n )\n}\n","import {ColorWheelIcon} from '@sanity/icons/ColorWheel'\nimport {Button, Text} from '@sanity/ui'\nimport {Tooltip} from '@sanity/ui/tooltip'\nimport {type NavbarProps} from 'sanity'\n\nimport {useThemer} from './context'\n\nfunction ThemerNavbarButton() {\n const {open, send} = useThemer()\n\n return (\n <Tooltip animate content={<Text size={1}>Themer</Text>} portal>\n <Button\n aria-label=\"Themer\"\n icon={ColorWheelIcon}\n mode=\"bleed\"\n onClick={() => send({type: 'sidebar.toggle'})}\n // The Studio's own navbar buttons go through a wrapper that pins them\n // to this padding, where `@sanity/ui` defaults to a roomier 3\n padding={2}\n selected={open}\n />\n </Tooltip>\n )\n}\n\n/**\n * Adds the toggle that opens and closes the themer sidebar to the Studio\n * navbar — an icon button with a tooltip in the top bar (like the Tasks\n * toggle), and a regular titled action in the narrow-screen sidebar menu.\n *\n * In the split preview every Studio copy renders its own navbar, so the\n * toggle shows up in both — they drive the same sidebar.\n *\n * @internal\n */\nexport function ThemerNavbar(props: NavbarProps) {\n const {open, send} = useThemer()\n\n return props.renderDefault({\n ...props,\n __internal_actions: [\n ...(props.__internal_actions ?? []),\n {\n location: 'topbar',\n name: 'themer-topbar',\n render: () => <ThemerNavbarButton />,\n },\n {\n icon: ColorWheelIcon,\n location: 'sidebar',\n name: 'themer-sidebar',\n onAction: () => send({type: 'sidebar.toggle'}),\n selected: open,\n title: 'Themer',\n },\n ],\n })\n}\n","import {definePlugin, type LayoutProps} from 'sanity'\n\nimport {BuildThemeOptions} from '../theme/options'\nimport {ThemerLayout} from './ThemerLayout'\nimport {ThemerNavbar} from './ThemerNavbar'\n\n/**\n * Options for the {@link themerTool} plugin.\n *\n * This is experimental and may change or be removed in any release without\n * notice — use at your own risk.\n *\n * @alpha\n */\nexport interface ThemerToolOptions {\n /**\n * The `buildTheme` options that the Studio's configured theme was generated\n * from — the themer starts editing from these, so pass the same object that\n * the `theme` in the Studio config uses:\n *\n * ```ts\n * const config: BuildThemeOptions = {light: {accent: '#1cb485'}, dark: {accent: '#22fca8'}}\n *\n * export default defineConfig({\n * theme: buildTheme(config),\n * plugins: [themerTool({config})],\n * })\n * ```\n */\n config?: BuildThemeOptions\n}\n\n/**\n * A Studio plugin that adds a themer sidebar for `buildTheme` themes: a\n * navbar toggle opens the sidebar next to the Studio, with a list of themes —\n * the configured theme, the presets and your own — each previewed as a tiny\n * Studio in both color schemes. Picking one applies it live to the whole\n * Studio while you browse around; your own themes can be edited with\n * accent/text/background pickers and a contrast slider per scheme, added,\n * duplicated, given the colors of an image (its palette is read on device),\n * removed and restored, and a dialog shows the `buildTheme` snippet that\n * makes the applied theme permanent. Toggle between light and dark mode with\n * the regular appearance menu — the preview follows it — or split the\n * preview to see the whole Studio in light and dark side by side.\n *\n * ```ts\n * import {themerTool} from '@sanity/themer/tool'\n * import {defineConfig} from 'sanity'\n *\n * export default defineConfig({\n * plugins: [themerTool()],\n * // ...rest of the config\n * })\n * ```\n *\n * This is experimental and may change or be removed in any release without\n * notice — use at your own risk.\n *\n * @alpha\n */\nexport const themerTool = definePlugin<ThemerToolOptions | void>((options) => {\n // No options generate the stock theme, which is what a Studio without a\n // `theme` in its config gets\n const baseOptions = options?.config ?? {}\n\n function ThemerLayoutWithOptions(props: LayoutProps) {\n return <ThemerLayout {...props} baseOptions={baseOptions} />\n }\n\n return {\n name: '@sanity/themer/tool',\n studio: {\n components: {\n layout: ThemerLayoutWithOptions,\n navbar: ThemerNavbar,\n },\n },\n }\n})\n"],"mappings":"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"}
|