First pass at custom theme

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-01-12 15:06:54 +00:00
parent 6c1cb93bf4
commit b81776dfaa
11 changed files with 463 additions and 15 deletions
+2
View File
@@ -9,11 +9,13 @@
"watch-css": "node scripts/watch-css.js"
},
"dependencies": {
"@codemirror/lang-sass": "^6.0.2",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@next/mdx": "^15.1.4",
"@storybook/react": "^8.4.7",
"@types/mdx": "^2.0.13",
"@uiw/react-codemirror": "^4.23.7",
"next": "14.2.23",
"react": "^18",
"react-dom": "^18",
+34
View File
@@ -35,3 +35,37 @@ iframe {
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
.ͼ2 .cm-gutters {
background-color: var(--canon-surface-2);
border-right: 1px solid var(--canon-border-base);
transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out;
}
.ͼ1 .cm-lineNumbers .cm-gutterElement {
padding-right: 8px;
padding-left: 8px;
font-size: 0.875rem;
display: flex;
align-items: center;
justify-content: flex-end;
}
.cm-line {
font-size: 0.875rem;
}
.ͼ1 .cm-line {
padding-left: 12px;
padding-top: 1px;
padding-bottom: 1px;
}
.cm-focused {
outline: none !important;
}
.cm-editor {
background-color: var(--canon-surface-1);
transition: background-color 0.2s ease-in-out;
}
+2 -1
View File
@@ -2,7 +2,7 @@ import type { Metadata } from 'next';
import { Sidebar } from '../components/Sidebar';
import { Toolbar } from '@/components/Toolbar';
import { Providers } from './providers';
import { CustomTheme } from '@/components/CustomTheme';
import styles from './page.module.css';
import './globals.css';
@@ -35,6 +35,7 @@ export default function RootLayout({
<Toolbar />
{children}
</div>
<CustomTheme />
</div>
</Providers>
</body>
@@ -0,0 +1,94 @@
'use client';
import { useEffect, useState, useCallback } from 'react';
import CodeMirror from '@uiw/react-codemirror';
import { sass } from '@codemirror/lang-sass';
import styles from './styles.module.css';
import { usePlayground } from '@/utils/playground-context';
import { AnimatePresence, motion } from 'framer-motion';
export const CustomTheme = () => {
const [customTheme, setCustomTheme] = useState<string | undefined>(undefined);
const { selectedThemeName } = usePlayground();
const [isClient, setIsClient] = useState(false);
useEffect(() => {
if (selectedThemeName === 'custom') {
const storedTheme = localStorage.getItem('customThemeCss') || '';
setCustomTheme(storedTheme);
let styleElement = document.getElementById(
'custom-theme-style',
) as HTMLStyleElement;
if (!styleElement) {
styleElement = document.createElement('style');
styleElement.id = 'custom-theme-style';
document.head.appendChild(styleElement);
}
styleElement.textContent = storedTheme;
} else {
const styleElement = document.getElementById(
'custom-theme-style',
) as HTMLStyleElement;
if (styleElement) {
styleElement.remove();
}
}
}, [selectedThemeName]);
useEffect(() => {
setIsClient(true);
}, []);
const handleSave = () => {
if (customTheme) {
localStorage.setItem('customThemeCss', customTheme);
let styleElement = document.getElementById(
'custom-theme-style',
) as HTMLStyleElement;
if (!styleElement) {
styleElement = document.createElement('style');
styleElement.id = 'custom-theme-style';
document.head.appendChild(styleElement);
}
styleElement.textContent = customTheme;
}
};
const onChange = useCallback((val: string) => {
setCustomTheme(val);
}, []);
return (
<AnimatePresence>
{isClient && selectedThemeName === 'custom' && (
<motion.div
className={styles.container}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
>
<div className={styles.header}>
<div className={styles.headerLeft}>Custom Theme</div>
<button className={styles.button} onClick={handleSave}>
Save
</button>
</div>
<CodeMirror
value={customTheme}
height="300px"
extensions={[sass()]}
onChange={onChange}
className={styles.editor}
basicSetup={{ foldGutter: false }}
/>
</motion.div>
)}
</AnimatePresence>
);
};
@@ -0,0 +1 @@
export { CustomTheme } from './customTheme';
@@ -0,0 +1,41 @@
.container {
position: fixed;
bottom: 16px;
right: 16px;
width: 36%;
height: 348px;
background-color: var(--canon-surface-1);
border-radius: 0.375rem;
border: 1px solid var(--canon-border-base);
display: flex;
flex-direction: column;
overflow: hidden;
transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out;
}
.editor {
flex: 1;
}
.header {
height: 46px;
flex-shrink: 0;
border-bottom: 1px solid var(--canon-border-base);
background-color: var(--canon-surface-1);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 12px 0 16px;
transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out;
}
.button {
all: unset;
height: 28px;
padding: 0 8px;
color: #fff;
background-color: #000;
border-radius: 0.25rem;
cursor: pointer;
font-size: 0.875rem;
}
+1 -2
View File
@@ -2,8 +2,7 @@
import Link from 'next/link';
import { components, overview, layoutComponents, theme } from '@/utils/data';
import { Box } from '../../../../packages/canon/src/components/Box';
import { Text } from '../../../../packages/canon/src/components/Text';
import { Box } from '../../../../packages/canon';
import { motion } from 'framer-motion';
import styles from './Sidebar.module.css';
import { usePathname } from 'next/navigation';
@@ -6,8 +6,9 @@ import { Icon } from '@backstage/canon';
import { usePlayground } from '@/utils/playground-context';
const themes = [
{ name: 'Backstage Legacy', value: 'legacy' },
{ name: 'Backstage Default', value: 'default' },
{ name: 'Backstage Legacy', value: 'legacy' },
{ name: 'Custom theme', value: 'custom' },
];
export const ThemeNameSelector = () => {
@@ -31,7 +32,7 @@ export const ThemeNameSelector = () => {
<Select.Positioner className={styles.Positioner} sideOffset={8}>
<Select.Popup className={styles.Popup}>
{themes.map(({ name, value }) => (
<Select.Item className={styles.Item} value={value}>
<Select.Item className={styles.Item} value={value} key={value}>
<Select.ItemIndicator className={styles.ItemIndicator}>
<Icon name="check" />
</Select.ItemIndicator>
-1
View File
@@ -2,7 +2,6 @@ import React, { ReactNode } from 'react';
import type { MDXComponents } from 'mdx/types';
import Image, { ImageProps } from 'next/image';
import { CodeBlock } from '@/components/CodeBlock';
import { Heading } from '../../packages/canon/src/components/Heading';
import { Box } from '../../packages/canon/src/components/Box';
export function useMDXComponents(components: MDXComponents): MDXComponents {
+14 -9
View File
@@ -7,16 +7,19 @@ import React, {
} from 'react';
import { components } from './data';
type Theme = 'light' | 'dark';
type ThemeName = 'legacy' | 'default' | 'custom';
// Create a context with an empty array as the default value
const PlaygroundContext = createContext<{
selectedScreenSizes: string[];
setSelectedScreenSizes: (screenSizes: string[]) => void;
selectedComponents: string[];
setSelectedComponents: (components: string[]) => void;
selectedTheme: string | null;
setSelectedTheme: (theme: string) => void;
selectedThemeName: string;
setSelectedThemeName: (themeName: string) => void;
selectedTheme: Theme;
setSelectedTheme: (theme: Theme) => void;
selectedThemeName: ThemeName;
setSelectedThemeName: (themeName: ThemeName) => void;
}>({
selectedScreenSizes: [],
setSelectedScreenSizes: () => {},
@@ -37,12 +40,14 @@ export const PlaygroundProvider = ({ children }: { children: ReactNode }) => {
const [selectedComponents, setSelectedComponents] = useState<string[]>(
components.map(component => component.slug),
);
const [selectedTheme, setSelectedTheme] = useState<string | null>(() => {
return isBrowser ? localStorage.getItem('theme') : 'light';
});
const [selectedThemeName, setSelectedThemeName] = useState<string>(() => {
const [selectedTheme, setSelectedTheme] = useState<Theme>(() => {
return isBrowser
? localStorage.getItem('theme-name') || 'default'
? (localStorage.getItem('theme') as Theme) || 'light'
: 'light';
});
const [selectedThemeName, setSelectedThemeName] = useState<ThemeName>(() => {
return isBrowser
? (localStorage.getItem('theme-name') as ThemeName) || 'default'
: 'default';
});
+271
View File
@@ -5,6 +5,133 @@ __metadata:
version: 6
cacheKey: 8
"@babel/runtime@npm:^7.18.6":
version: 7.26.0
resolution: "@babel/runtime@npm:7.26.0"
dependencies:
regenerator-runtime: ^0.14.0
checksum: c8e2c0504ab271b3467a261a8f119bf2603eb857a0d71e37791f4e3fae00f681365073cc79f141ddaa90c6077c60ba56448004ad5429d07ac73532be9f7cf28a
languageName: node
linkType: hard
"@codemirror/autocomplete@npm:^6.0.0":
version: 6.18.4
resolution: "@codemirror/autocomplete@npm:6.18.4"
dependencies:
"@codemirror/language": ^6.0.0
"@codemirror/state": ^6.0.0
"@codemirror/view": ^6.17.0
"@lezer/common": ^1.0.0
checksum: 4216f45a17f6cfd8d33df53f940396f7d3707662570bf3a79d8d333f926e273a265fac13c362e29e3fa57ccdf444f1a047862f5f56c672cfc669c87ee975858f
languageName: node
linkType: hard
"@codemirror/commands@npm:^6.0.0, @codemirror/commands@npm:^6.1.0":
version: 6.8.0
resolution: "@codemirror/commands@npm:6.8.0"
dependencies:
"@codemirror/language": ^6.0.0
"@codemirror/state": ^6.4.0
"@codemirror/view": ^6.27.0
"@lezer/common": ^1.1.0
checksum: 7d819bab4830ec7b8c5dffdec4b035dfa664bfd1d2675e639e08a459df65f45be111e1b8b569b1a8a3253d5980cf2ecf4394d8a13509996cca1b65cc16d47a4e
languageName: node
linkType: hard
"@codemirror/lang-css@npm:^6.2.0":
version: 6.3.1
resolution: "@codemirror/lang-css@npm:6.3.1"
dependencies:
"@codemirror/autocomplete": ^6.0.0
"@codemirror/language": ^6.0.0
"@codemirror/state": ^6.0.0
"@lezer/common": ^1.0.2
"@lezer/css": ^1.1.7
checksum: ed175d75d75bc0a059d1e60b3dcd8464d570da14fc97388439943c9c43e1e9146e37b83fe2ccaad9cd387420b7b411ea1d24ede78ecd1f2045a38acbb4dd36bc
languageName: node
linkType: hard
"@codemirror/lang-sass@npm:^6.0.2":
version: 6.0.2
resolution: "@codemirror/lang-sass@npm:6.0.2"
dependencies:
"@codemirror/lang-css": ^6.2.0
"@codemirror/language": ^6.0.0
"@codemirror/state": ^6.0.0
"@lezer/common": ^1.0.2
"@lezer/sass": ^1.0.0
checksum: e7665aaab70476a952522b143fd7bd59f6c025746cbf7b542f6965f94eecac483b4afd03f6da98aaa1572e379194309b241c5264eff05c681c637aa26651b9ab
languageName: node
linkType: hard
"@codemirror/language@npm:^6.0.0":
version: 6.10.8
resolution: "@codemirror/language@npm:6.10.8"
dependencies:
"@codemirror/state": ^6.0.0
"@codemirror/view": ^6.23.0
"@lezer/common": ^1.1.0
"@lezer/highlight": ^1.0.0
"@lezer/lr": ^1.0.0
style-mod: ^4.0.0
checksum: 679b69d69faa94f028f996a7005d0c6c2a2e4cd7a7a2614f615c23d7b642c31fc1837915248e864cb1ad59a2f032d1a7a8ef486b5f9904e5f6fbe6f7d2882c38
languageName: node
linkType: hard
"@codemirror/lint@npm:^6.0.0":
version: 6.8.4
resolution: "@codemirror/lint@npm:6.8.4"
dependencies:
"@codemirror/state": ^6.0.0
"@codemirror/view": ^6.35.0
crelt: ^1.0.5
checksum: 640e3dd44eb167d952eb5c5b8518919ba46e164aa3471776342f7f9361e676b4627a76a9f01d51b22127b97413f2bc9b8c60299d8dfdd5fc8ad0225d42de7669
languageName: node
linkType: hard
"@codemirror/search@npm:^6.0.0":
version: 6.5.8
resolution: "@codemirror/search@npm:6.5.8"
dependencies:
"@codemirror/state": ^6.0.0
"@codemirror/view": ^6.0.0
crelt: ^1.0.5
checksum: 0f9633037492a7b647b606c30255ea42c4327319e643be7ea3aa2913ed8e4aa662589f457e376636521c7d4d1215fae0e8939f127db9c0790b19ae3b654c3bc4
languageName: node
linkType: hard
"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.1.1, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.5.0":
version: 6.5.1
resolution: "@codemirror/state@npm:6.5.1"
dependencies:
"@marijn/find-cluster-break": ^1.0.0
checksum: b7d6de9a87d5b55dadfadaeb6e1c991e0a91845d3cdd0bd953391f05363fcbaf21de79a4eec2816ab8c3e31293faeca82cc2bb729d080779df94b14e28ae0d8a
languageName: node
linkType: hard
"@codemirror/theme-one-dark@npm:^6.0.0":
version: 6.1.2
resolution: "@codemirror/theme-one-dark@npm:6.1.2"
dependencies:
"@codemirror/language": ^6.0.0
"@codemirror/state": ^6.0.0
"@codemirror/view": ^6.0.0
"@lezer/highlight": ^1.0.0
checksum: 29bc09f79534115f62658caf3d0db527fe347d058b69a8c7f580ae636827377aadd0606fd0d83dbab8d6f3b0a5df53d3253c619341b5fb93d2c8291a8efb9556
languageName: node
linkType: hard
"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.35.0":
version: 6.36.2
resolution: "@codemirror/view@npm:6.36.2"
dependencies:
"@codemirror/state": ^6.5.0
style-mod: ^4.1.0
w3c-keyname: ^2.2.4
checksum: a58c64b623ddc65bb864917297f3b37f8e95280deec442024c43a9513b26352c829665c5d98e4dfcae104e8ecdfdb774d94a395a29da98a919c83482d2c14152
languageName: node
linkType: hard
"@esbuild/aix-ppc64@npm:0.24.2":
version: 0.24.2
resolution: "@esbuild/aix-ppc64@npm:0.24.2"
@@ -261,6 +388,60 @@ __metadata:
languageName: node
linkType: hard
"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.0.2, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0":
version: 1.2.3
resolution: "@lezer/common@npm:1.2.3"
checksum: 9b5f52d949adae69d077f56c0b1c2295923108c3dfb241dd9f17654ff708f3eab81ff9fa7f0d0e4a668eabdcb9d961c73e75caca87c966ca1436e30e49130fcb
languageName: node
linkType: hard
"@lezer/css@npm:^1.1.7":
version: 1.1.9
resolution: "@lezer/css@npm:1.1.9"
dependencies:
"@lezer/common": ^1.2.0
"@lezer/highlight": ^1.0.0
"@lezer/lr": ^1.0.0
checksum: 25c63475061a3c9f87961a7f85c5f547f14fb7e81b0864675d2206999a874a0559d676145c74c6ccde39519dbc8aa33e216265f5366d08060507b6c9e875fe0f
languageName: node
linkType: hard
"@lezer/highlight@npm:^1.0.0":
version: 1.2.1
resolution: "@lezer/highlight@npm:1.2.1"
dependencies:
"@lezer/common": ^1.0.0
checksum: a8822d7e37f79ff64669eb2df4a9f9d16580e88f2b276a646092e19a9bdccac304e92510e200e35869a8b1f6c27eba5972c508d347a277e9b722d582ab7a23d5
languageName: node
linkType: hard
"@lezer/lr@npm:^1.0.0":
version: 1.4.2
resolution: "@lezer/lr@npm:1.4.2"
dependencies:
"@lezer/common": ^1.0.0
checksum: 94318ad046c7dfcc8d37e26cb85b99623c39aef60aa51ec2abb30928e7a649f38fa5520f34bd5b356f1db11b6991999589f039e87c8949b0f163be3764f029d8
languageName: node
linkType: hard
"@lezer/sass@npm:^1.0.0":
version: 1.0.7
resolution: "@lezer/sass@npm:1.0.7"
dependencies:
"@lezer/common": ^1.2.0
"@lezer/highlight": ^1.0.0
"@lezer/lr": ^1.0.0
checksum: 32d37aa5d3143033f0b886c33a293b1d28b7eecdf2cb979cf7b4cbba0587be227dfed246c27116060ac9b2b2c0fe4b09df4b672690dd0593e876529583e7ef2a
languageName: node
linkType: hard
"@marijn/find-cluster-break@npm:^1.0.0":
version: 1.0.2
resolution: "@marijn/find-cluster-break@npm:1.0.2"
checksum: 0d836de25e04d58325813401ef3c2d34caf040da985a5935fcbc9d84e7b47a21bdb15f57d70c2bf0960bd29ed3dbbb1afd00cdd0fc4fafbee7fd0ffe7d508ae1
languageName: node
linkType: hard
"@mdx-js/loader@npm:^3.1.0":
version: 3.1.0
resolution: "@mdx-js/loader@npm:3.1.0"
@@ -904,6 +1085,51 @@ __metadata:
languageName: node
linkType: hard
"@uiw/codemirror-extensions-basic-setup@npm:4.23.7":
version: 4.23.7
resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.23.7"
dependencies:
"@codemirror/autocomplete": ^6.0.0
"@codemirror/commands": ^6.0.0
"@codemirror/language": ^6.0.0
"@codemirror/lint": ^6.0.0
"@codemirror/search": ^6.0.0
"@codemirror/state": ^6.0.0
"@codemirror/view": ^6.0.0
peerDependencies:
"@codemirror/autocomplete": ">=6.0.0"
"@codemirror/commands": ">=6.0.0"
"@codemirror/language": ">=6.0.0"
"@codemirror/lint": ">=6.0.0"
"@codemirror/search": ">=6.0.0"
"@codemirror/state": ">=6.0.0"
"@codemirror/view": ">=6.0.0"
checksum: 656c5f78db4c6216a9a7eeb65455f52b002b7a5426bb1b05f11b896d5dba9887c7b9ee5aee9fe369d6c7b3c69128ee842cb5c8fe029cd07c2e48ec9358039a30
languageName: node
linkType: hard
"@uiw/react-codemirror@npm:^4.23.7":
version: 4.23.7
resolution: "@uiw/react-codemirror@npm:4.23.7"
dependencies:
"@babel/runtime": ^7.18.6
"@codemirror/commands": ^6.1.0
"@codemirror/state": ^6.1.1
"@codemirror/theme-one-dark": ^6.0.0
"@uiw/codemirror-extensions-basic-setup": 4.23.7
codemirror: ^6.0.0
peerDependencies:
"@babel/runtime": ">=7.11.0"
"@codemirror/state": ">=6.0.0"
"@codemirror/theme-one-dark": ">=6.0.0"
"@codemirror/view": ">=6.0.0"
codemirror: ">=6.0.0"
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: 5f6318b290f479c4718129578bdccbbdf64e3b588fdd79e3d80224779219eae7e249d40e4f88c864ed6ae40ea93036042313e983a022349563698593a2bf2e6c
languageName: node
linkType: hard
"@ungap/structured-clone@npm:^1.0.0, @ungap/structured-clone@npm:^1.2.0":
version: 1.2.1
resolution: "@ungap/structured-clone@npm:1.2.1"
@@ -1254,6 +1480,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "canon-docs@workspace:."
dependencies:
"@codemirror/lang-sass": ^6.0.2
"@mdx-js/loader": ^3.1.0
"@mdx-js/react": ^3.1.0
"@next/mdx": ^15.1.4
@@ -1262,6 +1489,7 @@ __metadata:
"@types/node": ^20
"@types/react": ^18
"@types/react-dom": ^18
"@uiw/react-codemirror": ^4.23.7
concurrently: ^9.1.2
eslint: ^8
eslint-config-next: 14.2.23
@@ -1339,6 +1567,21 @@ __metadata:
languageName: node
linkType: hard
"codemirror@npm:^6.0.0":
version: 6.0.1
resolution: "codemirror@npm:6.0.1"
dependencies:
"@codemirror/autocomplete": ^6.0.0
"@codemirror/commands": ^6.0.0
"@codemirror/language": ^6.0.0
"@codemirror/lint": ^6.0.0
"@codemirror/search": ^6.0.0
"@codemirror/state": ^6.0.0
"@codemirror/view": ^6.0.0
checksum: 1a78f7077ac5801bdbff162aa0c61bf2b974603c7e9a477198c3ce50c789af674a061d7c293c58b73807eda345c2b5228c38ad2aabb9319d552d5486f785cbef
languageName: node
linkType: hard
"collapse-white-space@npm:^2.0.0":
version: 2.1.0
resolution: "collapse-white-space@npm:2.1.0"
@@ -1394,6 +1637,13 @@ __metadata:
languageName: node
linkType: hard
"crelt@npm:^1.0.5":
version: 1.0.6
resolution: "crelt@npm:1.0.6"
checksum: dad842093371ad702afbc0531bfca2b0a8dd920b23a42f26e66dabbed9aad9acd5b9030496359545ef3937c3aced0fd4ac39f7a2d280a23ddf9eb7fdcb94a69f
languageName: node
linkType: hard
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2":
version: 7.0.6
resolution: "cross-spawn@npm:7.0.6"
@@ -4380,6 +4630,13 @@ __metadata:
languageName: node
linkType: hard
"regenerator-runtime@npm:^0.14.0":
version: 0.14.1
resolution: "regenerator-runtime@npm:0.14.1"
checksum: 9f57c93277b5585d3c83b0cf76be47b473ae8c6d9142a46ce8b0291a04bb2cf902059f0f8445dcabb3fb7378e5fe4bb4ea1e008876343d42e46d3b484534ce38
languageName: node
linkType: hard
"regex-recursion@npm:^5.1.1":
version: 5.1.1
resolution: "regex-recursion@npm:5.1.1"
@@ -4971,6 +5228,13 @@ __metadata:
languageName: node
linkType: hard
"style-mod@npm:^4.0.0, style-mod@npm:^4.1.0":
version: 4.1.2
resolution: "style-mod@npm:4.1.2"
checksum: 7c5c3e82747f9bcf5f288d8d07f50848e4630fe5ff7bfe4d94cc87d6b6a2588227cbf21b4c792ac6406e5852293300a75e710714479a5c59a06af677f0825ef8
languageName: node
linkType: hard
"style-to-object@npm:^1.0.0":
version: 1.0.8
resolution: "style-to-object@npm:1.0.8"
@@ -5331,6 +5595,13 @@ __metadata:
languageName: node
linkType: hard
"w3c-keyname@npm:^2.2.4":
version: 2.2.8
resolution: "w3c-keyname@npm:2.2.8"
checksum: 95bafa4c04fa2f685a86ca1000069c1ec43ace1f8776c10f226a73296caeddd83f893db885c2c220ebeb6c52d424e3b54d7c0c1e963bbf204038ff1a944fbb07
languageName: node
linkType: hard
"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1":
version: 1.1.1
resolution: "which-boxed-primitive@npm:1.1.1"