Skip to content

Commit de4e823

Browse files
authored
Merge pull request #2 from PDFTron/lr-dark-theme
Dark theme
2 parents 2ab8759 + 5e075cb commit de4e823

21 files changed

+273
-89
lines changed

.storybook/manager-head.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
<link rel="icon" type="image/png" href="./favicon.png" sizes="291x291" />
33
<script>
44
const STORYBOOK_IFRAME_SELECTOR = '#storybook-preview-iframe';
5-
const CODE_PREVIEW_SELECTOR = '#root > div:not(#story-root):not([class])';
5+
const CODE_PREVIEW_SELECTOR =
6+
'#root > div:not(#story-root):not(#playground-theme-button):not([class])';
7+
const THEME_BUTTON_SELECTOR = '#playground-theme-button';
68
const DOCS_ROOT_SELECTOR = '#docs-root';
79

810
function withIframe(iframe) {
@@ -13,8 +15,10 @@
1315
const zoom = Number(iframeBody.style.zoom || 1);
1416
const adjustedZoom = 1 / zoom;
1517
const codePreviewWindow = iframeBody.querySelector(CODE_PREVIEW_SELECTOR);
18+
const themeButton = iframeBody.querySelector(THEME_BUTTON_SELECTOR);
1619
const docsRoot = iframeBody.querySelector(DOCS_ROOT_SELECTOR);
1720
if (codePreviewWindow) codePreviewWindow.style.zoom = adjustedZoom;
21+
if (themeButton) themeButton.style.zoom = adjustedZoom;
1822
if (docsRoot) docsRoot.style.zoom = adjustedZoom;
1923
});
2024

.storybook/preview.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { withInfo } from '@storybook/addon-info';
33
import { withKnobs } from '@storybook/addon-knobs';
44
import { addDecorator, addParameters } from '@storybook/react';
55
import React from 'react';
6-
import '../src/index.scss';
76
import './style.scss';
87
import theme from './theme';
8+
import { withTheme } from './withTheme';
99

1010
/* --- Add global decorators. --- */
1111

@@ -29,6 +29,7 @@ addDecorator(
2929
}),
3030
);
3131
addDecorator(withKnobs);
32+
addDecorator(withTheme);
3233

3334
/* --- Add global parameters. --- */
3435

.storybook/style.scss

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@import url('https://fonts.googleapis.com/css?family=Lato:400,400i,700,700i&display=swap');
22
@import url('https://fonts.googleapis.com/css?family=IBM+Plex+Mono:400,400i,700,700i&display=swap');
3+
@import '../src/index.scss';
34

45
html,
56
body {
@@ -8,7 +9,27 @@ body {
89
margin: 0 !important;
910
}
1011

11-
$color-background-canvas: #eff5f5;
12+
#playground-theme-button {
13+
position: absolute;
14+
top: 0;
15+
right: 0;
16+
z-index: $z-index-dragging;
17+
border-left: 1px solid rgba(255, 255, 255, 0.1);
18+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
19+
background-color: rgb(51, 51, 51);
20+
border-bottom-left-radius: 4px;
21+
width: 90px;
22+
@include flex-center;
23+
cursor: pointer;
24+
user-select: none;
25+
26+
&.playground-theme-button__docs {
27+
top: 4px;
28+
border-top: 1px solid rgba(255, 255, 255, 0.1);
29+
border-top-left-radius: 4px;
30+
position: fixed;
31+
}
32+
}
1233

1334
.sb-show-main > #root {
1435
background-color: $color-background-canvas;
@@ -56,18 +77,18 @@ $color-background-canvas: #eff5f5;
5677
display: flex;
5778
flex-direction: column;
5879

59-
// Story root or div with class should be auto flexed.
60-
> #story-root,
61-
> div[class] {
80+
// Story root should be auto flexed.
81+
> #story-root {
6282
flex: 1 1 auto;
6383
overflow: auto;
6484
}
6585

6686
// Non story root without class is the code preview.
67-
> div:not(#story-root):not([class]) {
87+
> div:not(#story-root):not(#playground-theme-button):not([class]) {
6888
flex-shrink: 0;
6989
max-height: 40%;
7090
overflow: auto;
91+
border-top: 1px solid rgba(255, 255, 255, 0.1);
7192

7293
> div > *:not(div) {
7394
display: none;

.storybook/withTheme.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { StoryContext, StoryFn } from '@storybook/addons';
2+
import React, { useRef, useState } from 'react';
3+
4+
export function withTheme<StoryFnReturnType>(storyFn: StoryFn<StoryFnReturnType>, context: StoryContext) {
5+
const html = useRef(document.documentElement);
6+
7+
const [theme, setTheme] = useState<'' | 'dark'>(() =>
8+
html.current.getAttribute('data-theme') === 'dark' ? 'dark' : '',
9+
);
10+
const toggleTheme = () => {
11+
const dark = html.current.getAttribute('data-theme') === 'dark';
12+
const newTheme = dark ? '' : 'dark';
13+
html.current.setAttribute('data-theme', newTheme);
14+
setTheme(newTheme);
15+
};
16+
17+
return (
18+
<>
19+
<div id="playground-theme-button" onClick={toggleTheme}>
20+
{theme === 'dark' ? '☀️' : '🌙'}
21+
</div>
22+
{storyFn(context)}
23+
</>
24+
);
25+
}

commitlint.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
module.exports = {
22
extends: ['@commitlint/config-conventional'],
3+
rules: {
4+
'scope-case': [0, 'always', 'lower-case'],
5+
},
36
};

scripts/parse_scss.js

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,48 @@ const scss = require('postcss-scss');
33

44
/* --- Helpers. --- */
55

6+
/**
7+
* Get the Sass AST from a given file.
8+
* @param {string} path Path to a Sass file.
9+
*/
610
function getAST(path) {
711
const contents = fs.readFileSync(path);
812
return scss.parse(contents.toString());
913
}
1014

11-
function propMap(cssVariables) {
15+
/**
16+
* Returns a function which can be used to map any prop/value nodes from a Sass
17+
* AST.
18+
*/
19+
function propMap() {
20+
const themeAst = getAST('src/styles/_theme.scss');
21+
const themeNodes = themeAst.nodes;
22+
23+
const cssVariables = themeNodes
24+
.find(n => n.selector === ':root')
25+
.nodes.filter(n => n.prop && n.value)
26+
.map(d => ({ prop: `var(${d.prop})`, value: d.value }));
27+
28+
const darkCssVariables = themeNodes
29+
.find(n => n.selector === "html[data-theme='dark']")
30+
.nodes.filter(n => n.prop && n.value)
31+
.map(d => ({ prop: `var(${d.prop})`, value: d.value }));
32+
1233
return declaration => {
1334
const { p, v } = { p: declaration.prop, v: declaration.value };
1435
const scss = p;
1536
const css = v;
1637
const value = cssVariables.find(c => c.prop === v).value;
17-
return { scss, css, value };
38+
const dark = (darkCssVariables.find(c => c.prop === v) || {}).value;
39+
return { scss, css, value, dark };
1840
};
1941
}
2042

43+
/**
44+
* Creates a JS or TS file which default exports the AST.
45+
* @param {string} path Path to the file.
46+
* @param {Object} ast The AST object to convert to JS or TS.
47+
*/
2148
function printFile(path, ast) {
2249
fs.writeFileSync(path, `export default ${JSON.stringify(ast, null, 2)}`);
2350
}
@@ -102,16 +129,11 @@ function main() {
102129
mixins();
103130

104131
const ast = getAST('src/styles/_variables.scss');
105-
const themeAst = getAST('src/styles/_theme.scss');
106-
107132
const nodes = ast.nodes;
108-
const themeNodes = themeAst.nodes;
109133

110-
const cssVariables = themeNodes
111-
.find(n => n.selector === ':root')
112-
.nodes.map(d => ({ prop: `var(${d.prop})`, value: d.value }));
134+
const mapper = propMap();
113135

114-
const mapper = propMap(cssVariables);
136+
/* --- Generate variables and colors. --- */
115137

116138
const declarations = (() => {
117139
const color = [];
@@ -147,7 +169,7 @@ function main() {
147169
};
148170
})();
149171

150-
/* --- Generate theme variables and colors. --- */
172+
/* --- Map colors and sort by type. --- */
151173

152174
const allColors = declarations.color.map(mapper);
153175

@@ -156,22 +178,22 @@ function main() {
156178
const font = [];
157179
const gray = [];
158180
const blueGray = [];
159-
const white = [];
181+
const contrast = [];
160182
const other = [];
161183

162184
allColors.forEach(c => {
163185
if (c.scss.startsWith('$color-theme')) return theme.push(c);
164186
if (c.scss.startsWith('$color-font')) return font.push(c);
165187
if (c.scss.startsWith('$color-gray')) return gray.push(c);
166188
if (c.scss.startsWith('$color-blue-gray')) return blueGray.push(c);
167-
if (c.scss.startsWith('$color-white')) return white.push(c);
189+
if (c.scss.startsWith('$color-contrast')) return contrast.push(c);
168190
other.push(c);
169191
});
170192

171-
return { theme, font, gray, blueGray, white, other };
193+
return { theme, font, gray, blueGray, contrast, other };
172194
})();
173195

174-
/* --- Generate others. --- */
196+
/* --- Map variables. --- */
175197

176198
const fontSize = declarations.fontSize.map(mapper);
177199
const fontWeight = declarations.fontWeight.map(mapper);

src/__stories__/2_style/1_colors.stories.mdx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,30 @@ div {
2727
font-color: $color-font-primary;
2828
}
2929
```
30+
31+
## Themes
32+
33+
You may have noticed the theme button on the top right, which can also be found
34+
on any of the Components. The toolkit ships with a default light and dark theme,
35+
and you can switch between them using this button! To see what the colors would
36+
be for each theme, scroll down to the
37+
[CSS and Sass Variables](#css-and-sass-variables) section and click the theme
38+
button.
39+
40+
To set the theme, you need to set the `data-theme` attribute of your `html` to
41+
`'dark'`. This can be done right in the HTML if you want to permanently enable
42+
dark theme:
43+
44+
```html
45+
<html data-theme="dark">
46+
```
47+
48+
Or programmatically in JavaScript (this will allow switching themes dynamically):
49+
50+
```js
51+
document.documentElement.setAttribute('data-theme', 'dark');
52+
```
53+
3054
## Overriding
3155

3256
These can be overridden by setting the variables yourself in the root. Since the
@@ -35,10 +59,18 @@ colors used across the toolkit. Here's an example of overriding the theme colors
3559

3660
```css
3761
/* your_custom_theme.css */
62+
63+
/* Overriding light theme. */
3864
:root {
3965
--color-font-primary: red;
4066
--color-font-secondary: orange;
4167
}
68+
69+
/* Overriding dark theme. */
70+
html[data-theme='dark'] {
71+
--color-font-primary: green;
72+
--color-font-secondary: blue;
73+
}
4274
```
4375

4476
## CSS and Sass Variables

src/__stories__/2_style/3_mixins.stories.mdx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ The WebViewer React Toolkit ships with some Sass mixins that wrap common
99
patterns. These can only be used in Sass files, but the code is provided below
1010
so that you can implement them in CSS if you wish.
1111

12+
> Note: These mixins rely on Sass variables. If you wish to use these, you must
13+
> also include the variables file.
14+
>
1215
> For more on Sass mixins, check out the
1316
> [Sass documentation](https://sass-lang.com/documentation/at-rules/mixin).
1417
@@ -28,8 +31,9 @@ div {
2831

2932
## Mixins
3033

31-
The following mixins show the sdinfpwionfipwenpfinYou can click any of these to copy the
32-
value to your clipboard.
34+
The following mixins show the value you can include in your code, as well as the
35+
raw values in case you wish to modify them in your own code. You can click any
36+
of these to copy the value to your clipboard.
3337

3438
<!-- Below here is auto-generated. DO NOT EDIT. -->
3539
<!-- GENERATE_ENTRY -->
@@ -129,12 +133,7 @@ value to your clipboard.
129133
// This provides the animation for skeleton loading.
130134
@mixin skeleton {
131135
animation: timeline 2s linear infinite;
132-
background: linear-gradient(
133-
to right,
134-
$color-gray-1 0%,
135-
$color-gray-2 10%,
136-
$color-gray-1 20%
137-
);
136+
background: linear-gradient(to right, #eee 0%, #ddd 10%, #eee 20%);
138137
background-size: 600px auto;
139138
}
140139

0 commit comments

Comments
 (0)