Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/young-breads-fall.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@node-core/ui-components': patch
---

Add light mode theming to code elements
16 changes: 16 additions & 0 deletions packages/rehype-shiki/src/__tests__/highlighter.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ mock.module('shiki/themes/nord.mjs', {
defaultExport: { name: 'nord', colors: { 'editor.background': '#2e3440' } },
});

mock.module('shiki/themes/github-light.mjs', {
defaultExport: {
name: 'github-light',
colors: { 'editor.background': '#fff' },
},
});

describe('createHighlighter', async () => {
const { default: createHighlighter } = await import('../highlighter.mjs');

Expand Down Expand Up @@ -70,6 +77,15 @@ describe('createHighlighter', async () => {
const [, options] = mockShiki.codeToHtml.mock.calls.at(-1).arguments;
assert.strictEqual(options.lang, 'text');
});

it('emits light and dark syntax colors with light as the default', () => {
const highlighter = createHighlighter({});
highlighter.highlightToHtml('const x = 1;', 'javascript');

const [, options] = mockShiki.codeToHtml.mock.calls.at(-1).arguments;
assert.deepStrictEqual(Object.keys(options.themes), ['light', 'dark']);
assert.strictEqual(options.defaultColor, 'light');
});
});

describe('highlightToHast', () => {
Expand Down
27 changes: 21 additions & 6 deletions packages/rehype-shiki/src/highlighter.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { createHighlighterCoreSync, isSpecialLang } from '@shikijs/core';
import shikiGitHubLightTheme from 'shiki/themes/github-light.mjs';
import shikiNordTheme from 'shiki/themes/nord.mjs';

const DEFAULT_THEME = {
const DEFAULT_DARK_THEME = {
// We are updating this color because the background color and comment text color
// in the Codebox component do not comply with accessibility standards.
// See: https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html
colorReplacements: { '#616e88': '#707e99' },
...shikiNordTheme,
colorReplacements: {
...shikiNordTheme.colorReplacements,
'#616e88': '#707e99',
},
};

const DEFAULT_THEMES = {
light: shikiGitHubLightTheme,
dark: DEFAULT_DARK_THEME,
};

const FALLBACK_LANGUAGE = 'text';
Expand Down Expand Up @@ -42,12 +51,18 @@ export const getLanguageByName = (language, langs) => {
* @returns {SyntaxHighlighter}
*/
const createHighlighter = ({ coreOptions = {}, highlighterOptions = {} }) => {
const usesCustomTheme =
'theme' in highlighterOptions || 'themes' in highlighterOptions;
const options = {
themes: [DEFAULT_THEME],
themes: Object.values(DEFAULT_THEMES),
...coreOptions,
};
const shiki = createHighlighterCoreSync(options);
const theme = options.themes[0];
const themeOptions = usesCustomTheme
? {}
: coreOptions.themes
? { theme: options.themes[0] }
: { themes: DEFAULT_THEMES, defaultColor: 'light' };

const loadedLanguages = new Set(
shiki.getLoadedLanguages().map(lang => lang.toLowerCase())
Expand Down Expand Up @@ -86,7 +101,7 @@ const createHighlighter = ({ coreOptions = {}, highlighterOptions = {} }) => {
shiki
.codeToHtml(code, {
lang: resolveLanguage(lang),
theme,
...themeOptions,
meta,
...highlighterOptions,
})
Expand All @@ -105,7 +120,7 @@ const createHighlighter = ({ coreOptions = {}, highlighterOptions = {} }) => {
const highlightToHast = (code, lang, meta = {}) =>
shiki.codeToHast(code, {
lang: resolveLanguage(lang),
theme,
...themeOptions,
meta,
...highlighterOptions,
});
Expand Down
13 changes: 13 additions & 0 deletions packages/rehype-shiki/src/index.css
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
@import './transformers/twoslash/index.css';

/*
* Shiki renders the light palette as the inline default and stores the dark
* palette in custom properties. This selector also covers snippets whose
* highlighted HTML is inserted without the outer `.shiki` element.
*/
[data-theme='dark'] span[style*='--shiki-dark'] {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
8 changes: 7 additions & 1 deletion packages/rehype-shiki/src/transformers/twoslash/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@
min-width: 100%;
padding: 6px 12px;
white-space: pre-wrap;
background-color: var(--color-neutral-950);
color: var(--color-neutral-900);
background-color: var(--color-neutral-100);

span {
display: inline-block;
}
}

[data-theme='dark'] .twoslash-popup-code:not(:has(div)) {
color: var(--color-white);
background-color: var(--color-neutral-950);
}

.twoslash-completion-list {
display: block;
width: auto;
Expand Down
16 changes: 10 additions & 6 deletions packages/ui-components/src/Common/BaseButton/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,25 @@

&.neutral {
@apply rounded-sm
bg-neutral-900
text-white
bg-neutral-100
text-neutral-900
dark:bg-neutral-900
dark:text-neutral-200;

&:hover:not([aria-disabled='true']) {
@apply bg-neutral-800;
@apply bg-neutral-200
dark:bg-neutral-800;
}

&[aria-disabled='true'] {
@apply bg-neutral-900
opacity-50;
@apply bg-neutral-100
opacity-50
dark:bg-neutral-900;
}

&:focus {
@apply bg-neutral-800;
@apply bg-neutral-200
dark:bg-neutral-800;
}
}

Expand Down
24 changes: 15 additions & 9 deletions packages/ui-components/src/Common/BaseCodeBox/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
overflow-x-hidden
rounded-sm
border
border-neutral-900
bg-neutral-950;
border-neutral-200
bg-white
dark:border-neutral-900
dark:bg-neutral-950;

.content {
@apply m-0
Expand All @@ -26,8 +28,9 @@
p-0
text-sm
leading-snug
text-neutral-400
[counter-reset:line];
text-neutral-800
[counter-reset:line]
dark:text-neutral-400;

& > [class='line'] {
@apply relative
Expand All @@ -48,9 +51,10 @@
mr-4
w-4.5
text-right
text-neutral-600
text-neutral-500
[content:counter(line)]
[counter-increment:line];
[counter-increment:line]
dark:text-neutral-600;
}
}

Expand All @@ -74,14 +78,16 @@
items-center
justify-between
border-t
border-t-neutral-900
border-t-neutral-200
px-3
py-2
text-sm
font-medium;
font-medium
dark:border-t-neutral-900;

& > .language {
@apply text-neutral-400;
@apply text-neutral-700
dark:text-neutral-400;
}

& > .action {
Expand Down
31 changes: 20 additions & 11 deletions packages/ui-components/src/Common/CodeTabs/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,26 @@
rounded-t
border-x
border-t
border-neutral-900
bg-neutral-950
border-neutral-200
bg-neutral-100
px-2
pt-2
md:px-4;
md:px-4
dark:border-neutral-900
dark:bg-neutral-950;

.trigger {
@apply border-b
border-b-transparent
px-1
text-neutral-200;
text-neutral-700
dark:text-neutral-200;

&[data-state='active'] {
@apply border-b-brand-400
text-brand-400;
@apply border-b-brand-600
text-brand-700
dark:border-b-brand-400
dark:text-brand-400;
}
}

Expand All @@ -33,21 +38,25 @@
items-center
gap-2
text-center
text-neutral-200
text-neutral-700
motion-safe:transition-colors
lg:flex;
lg:flex
dark:text-neutral-200;

& > .icon {
@apply size-4
text-neutral-300;
text-neutral-600
dark:text-neutral-300;
}

&:is(:link, :visited) {
&:hover {
@apply text-neutral-400;
@apply text-neutral-900
dark:text-neutral-400;

& > .icon {
@apply text-neutral-600;
@apply text-neutral-800
dark:text-neutral-600;
}
}
}
Expand Down
Loading