This commit is contained in:
hobbes7878 2024-08-20 21:11:47 +01:00
parent 203b102b55
commit 17f705d167
65 changed files with 7207 additions and 18129 deletions

View file

@ -1,5 +1,5 @@
{ {
"name": "Node.js", "name": "Node.js",
"image": "mcr.microsoft.com/devcontainers/javascript-node:0-16", "image": "mcr.microsoft.com/devcontainers/javascript-node:0-20",
"postCreateCommand": "yarn install" "postCreateCommand": "npm install"
} }

View file

@ -1,52 +0,0 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
ignorePatterns: ['node_modules', 'docs/**', '.eslintrc.cjs'],
extends: ['standard', 'plugin:svelte/recommended'],
plugins: ['@typescript-eslint'],
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
extraFileExtensions: ['.svelte'],
},
env: {
browser: true,
es2022: true,
},
rules: {
indent: ['error', 2, { SwitchCase: 1 }],
semi: ['error', 'always'],
'comma-dangle': [
'error',
{
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'never',
functions: 'never',
},
],
'operator-linebreak': ['error', 'after'],
'space-before-function-paren': ['error', 'never'],
},
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
},
rules: {
'no-multiple-empty-lines': ['error', { max: 2, maxBOF: 2 }],
'import/first': 'off',
'import/no-duplicates': 'off',
'import/no-mutable-exports': 'off',
'import/no-unresolved': 'off',
'svelte/no-at-html-tags': 'off',
indent: ['error', 2],
},
},
],
};

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
docs/

View file

@ -1,15 +0,0 @@
{
"svelteStrictMode": true,
"arrowParens": "always",
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "css",
"printWidth": 80,
"proseWrap": "preserve",
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false
}

10
.prettierrc.js Normal file
View file

@ -0,0 +1,10 @@
import { svelte as svelteConfig } from '@reuters-graphics/yaks-prettier';
/**
* @type {import("prettier").Config}
*/
const config = {
...svelteConfig,
};
export default config;

View file

@ -4,6 +4,7 @@ export default create({
base: 'light', base: 'light',
brandTitle: 'Reuters Graphics components', brandTitle: 'Reuters Graphics components',
brandUrl: 'https://reuters-graphics.github.io/graphics-components/', brandUrl: 'https://reuters-graphics.github.io/graphics-components/',
brandImage: 'https://graphics.thomsonreuters.com/style-assets/images/logos/reuters-graphics-logo/svg/graphics-logo-color-dark.svg', brandImage:
'https://graphics.thomsonreuters.com/style-assets/images/logos/reuters-graphics-logo/svg/graphics-logo-color-dark.svg',
brandTarget: '_self', brandTarget: '_self',
}); });

View file

@ -7,4 +7,4 @@
<Article> <Article>
<slot /> <slot />
</Article> </Article>
</Theme> </Theme>

View file

@ -22,4 +22,4 @@ addons.setConfig({
fullscreen: { hidden: false }, fullscreen: { hidden: false },
}, },
theme, theme,
}); });

View file

@ -12,7 +12,7 @@ SyntaxHighlighter.registerLanguage('svelte', svelte);
SyntaxHighlighter.registerLanguage('markdown', markdown); SyntaxHighlighter.registerLanguage('markdown', markdown);
export const parameters = { export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" }, actions: { argTypesRegex: '^on[A-Z].*' },
viewMode: 'docs', viewMode: 'docs',
previewTabs: { 'storybook/docs/panel': { index: -1 } }, previewTabs: { 'storybook/docs/panel': { index: -1 } },
controls: { controls: {
@ -53,23 +53,19 @@ export const parameters = {
'Styles', 'Styles',
[ [
'Intro', 'Intro',
'Colours', [ 'Colours',
'Intro', ['Intro', 'Primary', 'Thematic', '*'],
'Primary', 'Tokens',
'Thematic', ['Intro', 'Typography', '*'],
'*',
],
'Tokens', [
'Intro',
'Typography',
'*',
],
], ],
'Actions', 'Actions',
['Intro', '*'], ['Intro', '*'],
'Contributing', 'Contributing',
[ [
'Quickstart', 'Component Basics', '*', 'Writing Stories', 'Quickstart',
'Component Basics',
'*',
'Writing Stories',
'Recipes: Basic story', 'Recipes: Basic story',
'Recipes: Story with custom docs', 'Recipes: Story with custom docs',
'Recipes: Story with custom controls', 'Recipes: Story with custom controls',
@ -79,7 +75,7 @@ export const parameters = {
], ],
], ],
}, },
} },
}; };
export const decorators = [() => Wrapper]; export const decorators = [() => Wrapper];

View file

@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
declare function svelte(Prism: any): void; declare function svelte(Prism: any): void;
declare namespace svelte { declare namespace svelte {
let displayName: string; let displayName: string;
let aliases: any[]; let aliases: any[];
} }
export default svelte; export default svelte;

View file

@ -1,5 +1,5 @@
svelte.displayName = 'svelte' svelte.displayName = 'svelte';
svelte.aliases = [] svelte.aliases = [];
export default function svelte(Prism) { export default function svelte(Prism) {
const blocks = '(if|else if|await|then|catch|each|html|debug)'; const blocks = '(if|else if|await|then|catch|each|html|debug)';
@ -48,7 +48,8 @@ export default function svelte(Prism) {
}, },
}, },
tag: { tag: {
pattern: /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?:"[^"]*"|'[^']*'|{[\s\S]+?}(?=[\s/>])))|(?=[\s/>])))+)?\s*\/?>/i, pattern:
/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?:"[^"]*"|'[^']*'|{[\s\S]+?}(?=[\s/>])))|(?=[\s/>])))+)?\s*\/?>/i,
greedy: true, greedy: true,
inside: { inside: {
tag: { tag: {
@ -59,7 +60,8 @@ export default function svelte(Prism) {
}, },
}, },
'language-javascript': { 'language-javascript': {
pattern: /\{(?:(?:\{(?:(?:\{(?:[^{}])*\})|(?:[^{}]))*\})|(?:[^{}]))*\}/, pattern:
/\{(?:(?:\{(?:(?:\{(?:[^{}])*\})|(?:[^{}]))*\})|(?:[^{}]))*\}/,
inside: Prism.languages['javascript'], inside: Prism.languages['javascript'],
}, },
'attr-value': { 'attr-value': {
@ -97,7 +99,7 @@ export default function svelte(Prism) {
Prism.languages.svelte['tag'].inside['attr-value'].inside['entity'] = Prism.languages.svelte['tag'].inside['attr-value'].inside['entity'] =
Prism.languages.svelte['entity']; Prism.languages.svelte['entity'];
Prism.hooks.add('wrap', env => { Prism.hooks.add('wrap', (env) => {
if (env.type === 'entity') { if (env.type === 'entity') {
env.attributes['title'] = env.content.replace(/&amp;/, '&'); env.attributes['title'] = env.content.replace(/&amp;/, '&');
} }
@ -144,4 +146,4 @@ export default function svelte(Prism) {
Prism.languages.svelte.tag.addInlined('style', 'css'); Prism.languages.svelte.tag.addInlined('style', 'css');
Prism.languages.svelte.tag.addInlined('script', 'javascript'); Prism.languages.svelte.tag.addInlined('script', 'javascript');
} }

View file

@ -1,7 +1,5 @@
{ {
"i18n-ally.localesPaths": ["locales"], "eslint.validate": ["javascript", "javascriptreact", "svelte", "jsx"],
"i18n-ally.keystyle": "nested",
"eslint.validate": ["javascript", "svelte", "jsx"],
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit" "source.fixAll.eslint": "explicit"

View file

@ -28,13 +28,14 @@ const excludedTypeDefs = [
'**/*.stories.svelte.d.ts', '**/*.stories.svelte.d.ts',
]; ];
const prettifyImport = (filename) => { const prettifyImport = (filename) => {
return filename return (
// strip index.js filename
.replace(/\/index\.js$|(\/[^/]+)\.js$/, '$1') // strip index.js
// normalize SCSS partials .replace(/\/index\.js$|(\/[^/]+)\.js$/, '$1')
.replace(/\/_?([^/]+)\.scss$/, '/$1'); // normalize SCSS partials
.replace(/\/_?([^/]+)\.scss$/, '/$1')
);
}; };
/** /**
@ -47,28 +48,32 @@ const build = async () => {
// Extract types // Extract types
await emitDts({ await emitDts({
libRoot: LIB, libRoot: LIB,
svelteShimsPath: require.resolve('svelte2tsx/svelte-shims.d.ts'), svelteShimsPath: require.resolve('svelte2tsx/svelte-shims.d.ts'),
declarationDir: TYPES, declarationDir: TYPES,
}); });
// Cleanup unwanted types // Cleanup unwanted types
fs.rmSync(path.join(TYPES, 'docs'), { recursive: true, force: true }); fs.rmSync(path.join(TYPES, 'docs'), { recursive: true, force: true });
const types = await glob('**/*', { cwd: TYPES, filesOnly: true }); const types = await glob('**/*', { cwd: TYPES, filesOnly: true });
for (const t of types) { for (const t of types) {
if(picomatch.isMatch(t, excludedTypeDefs)) fs.unlinkSync(path.join(TYPES, t)); if (picomatch.isMatch(t, excludedTypeDefs))
fs.unlinkSync(path.join(TYPES, t));
} }
const pkgExports = { const pkgExports = {
'./package.json': './package.json' './package.json': './package.json',
}; };
const files = await glob('**/*.{js,json,ts,svelte,css,scss}', { cwd: LIB, filesOnly: true }); const files = await glob('**/*.{js,json,ts,svelte,css,scss}', {
cwd: LIB,
filesOnly: true,
});
for (const file of files) { for (const file of files) {
if(picomatch.isMatch(file, excludePatterns)) continue; if (picomatch.isMatch(file, excludePatterns)) continue;
if (file.endsWith('.svelte')) { if (file.endsWith('.svelte')) {
await processSvelte(file); await processSvelte(file);
} else if(file.endsWith('.ts') && !file.endsWith('.d.ts')) { } else if (file.endsWith('.ts') && !file.endsWith('.d.ts')) {
await processTypescript(file); await processTypescript(file);
} else { } else {
await processOther(file); await processOther(file);
@ -83,12 +88,12 @@ const build = async () => {
}; };
const pkg = fs.readJSONSync(PACKAGE); const pkg = fs.readJSONSync(PACKAGE);
pkg.type = 'module'; pkg.type = 'module';
pkg.types = './dist/@types/index.d.ts', pkg.types = './dist/@types/index.d.ts';
pkg.files = ['dist']; pkg.files = ['dist'];
pkg.private = false; pkg.private = false;
pkg.exports = pkgExports; pkg.exports = pkgExports;
pkg.svelte = './dist/index.js'; pkg.svelte = './dist/index.js';
fs.writeFileSync(PACKAGE, JSON.stringify(pkg, null, 2)); fs.writeFileSync(PACKAGE, JSON.stringify(pkg, null, 2));
} };
build(); build();

View file

@ -8,4 +8,4 @@ export default async (file) => {
const writePath = path.join(DIST, file); const writePath = path.join(DIST, file);
fs.ensureDirSync(path.dirname(writePath)); fs.ensureDirSync(path.dirname(writePath));
fs.copyFileSync(filename, writePath); fs.copyFileSync(filename, writePath);
} };

View file

@ -5,16 +5,22 @@ import path from 'path';
import { preprocess as svelte } from 'svelte/compiler'; import { preprocess as svelte } from 'svelte/compiler';
import { sveltePreprocess } from '../../preprocess/index.js'; import { sveltePreprocess } from '../../preprocess/index.js';
const stripLangTags = (source) => const stripLangTags = (source) =>
source source
.replace(/(<!--[^]*?-->)|(<script[^>]*?)\s(?:type|lang)=(["']).*?\3/g, '$1$2') .replace(
.replace(/(<!--[^]*?-->)|(<style[^>]*?)\s(?:type|lang)=(["']).*?\3/g, '$1$2'); /(<!--[^]*?-->)|(<script[^>]*?)\s(?:type|lang)=(["']).*?\3/g,
'$1$2'
)
.replace(
/(<!--[^]*?-->)|(<style[^>]*?)\s(?:type|lang)=(["']).*?\3/g,
'$1$2'
);
export default async (file) => { export default async (file) => {
const filename = path.join(LIB, file); const filename = path.join(LIB, file);
let source = fs.readFileSync(filename, 'utf8'); let source = fs.readFileSync(filename, 'utf8');
source = (await svelte(source, sveltePreprocess, { filename })).code source = (await svelte(source, sveltePreprocess, { filename })).code;
const writePath = path.join(DIST, file); const writePath = path.join(DIST, file);
fs.ensureDirSync(path.dirname(writePath)); fs.ensureDirSync(path.dirname(writePath));
fs.writeFileSync(writePath, stripLangTags(source)); fs.writeFileSync(writePath, stripLangTags(source));
} };

View file

@ -6,10 +6,10 @@ import ts from 'typescript';
async function transpileTypeScript(filename, source) { async function transpileTypeScript(filename, source) {
const { compilerOptions } = fs.readJSONSync(path.join(ROOT, 'tsconfig.json')); const { compilerOptions } = fs.readJSONSync(path.join(ROOT, 'tsconfig.json'));
return ts.transpileModule(source, { return ts.transpileModule(source, {
compilerOptions, compilerOptions,
fileName: filename fileName: filename,
}).outputText; }).outputText;
} }
export default async (file) => { export default async (file) => {
@ -19,4 +19,4 @@ export default async (file) => {
const writePath = path.join(DIST, file).replace(/\.ts$/, '.js'); const writePath = path.join(DIST, file).replace(/\.ts$/, '.js');
fs.ensureDirSync(path.dirname(writePath)); fs.ensureDirSync(path.dirname(writePath));
fs.writeFileSync(writePath, source); fs.writeFileSync(writePath, source);
} };

View file

@ -36,7 +36,7 @@ function ownKeys(object, enumerableOnly) {
if (Object.getOwnPropertySymbols) { if (Object.getOwnPropertySymbols) {
let symbols = Object.getOwnPropertySymbols(object); let symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) { if (enumerableOnly) {
symbols = symbols.filter(function(sym) { symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable; return Object.getOwnPropertyDescriptor(object, sym).enumerable;
}); });
} }
@ -51,14 +51,18 @@ function _objectSpread2(target) {
var source = arguments[i] != null ? arguments[i] : {}; var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) { if (i % 2) {
ownKeys(Object(source), true).forEach(function(key) { ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]); _defineProperty(target, key, source[key]);
}); });
} else if (Object.getOwnPropertyDescriptors) { } else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else { } else {
ownKeys(Object(source)).forEach(function(key) { ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); Object.defineProperty(
target,
key,
Object.getOwnPropertyDescriptor(source, key)
);
}); });
} }
} }
@ -67,7 +71,9 @@ function _objectSpread2(target) {
} }
function _slicedToArray(arr, i) { function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); return (
_arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest()
);
} }
function _arrayWithHoles(arr) { function _arrayWithHoles(arr) {
@ -75,7 +81,12 @@ function _arrayWithHoles(arr) {
} }
function _iterableToArrayLimit(arr, i) { function _iterableToArrayLimit(arr, i) {
if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === '[object Arguments]')) { if (
!(
Symbol.iterator in Object(arr) ||
Object.prototype.toString.call(arr) === '[object Arguments]'
)
) {
return; return;
} }
@ -85,7 +96,11 @@ function _iterableToArrayLimit(arr, i) {
let _e; let _e;
try { try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { for (
var _i = arr[Symbol.iterator](), _s;
!(_n = (_s = _i.next()).done);
_n = true
) {
_arr.push(_s.value); _arr.push(_s.value);
if (i && _arr.length === i) break; if (i && _arr.length === i) break;
@ -121,7 +136,7 @@ const addProperty = function addProperty(obj, key, value) {
}; };
const camelize = function camelize(str) { const camelize = function camelize(str) {
return str.replace(/-([a-z])/g, function(g) { return str.replace(/-([a-z])/g, function (g) {
return g[1].toUpperCase(); return g[1].toUpperCase();
}); });
}; };
@ -148,7 +163,7 @@ const fontface = function fontface(rule, result) {
let name = ''; let name = '';
let obj = {}; let obj = {};
const fontObj = {}; const fontObj = {};
rule.declarations.forEach(function(declaration) { rule.declarations.forEach(function (declaration) {
const cssProperty = camelize(declaration.property); const cssProperty = camelize(declaration.property);
fontObj[cssProperty] = declaration.value; fontObj[cssProperty] = declaration.value;
name = capitalize(camelize(fontObj.fontFamily).replace(/"/g, '')); name = capitalize(camelize(fontObj.fontFamily).replace(/"/g, ''));
@ -157,7 +172,7 @@ const fontface = function fontface(rule, result) {
}; };
}); });
let dupeFlag = false; let dupeFlag = false;
Object.keys(result).forEach(function(key) { Object.keys(result).forEach(function (key) {
if (key.split('_')[0] === name) { if (key.split('_')[0] === name) {
if (JSON.stringify(result[key]) === JSON.stringify(obj)) { if (JSON.stringify(result[key]) === JSON.stringify(obj)) {
dupeFlag = true; dupeFlag = true;
@ -166,7 +181,7 @@ const fontface = function fontface(rule, result) {
}); });
if (!dupeFlag) { if (!dupeFlag) {
const numVar = Object.entries(result).filter(function(resObj) { const numVar = Object.entries(result).filter(function (resObj) {
return resObj[0].split('_')[0] === name; return resObj[0].split('_')[0] === name;
}).length; }).length;
@ -183,9 +198,13 @@ const fontface = function fontface(rule, result) {
const keyframes = function keyframes(rule) { const keyframes = function keyframes(rule) {
const keyFrameObj = {}; const keyFrameObj = {};
rule.keyframes.forEach(function(keyframe) { rule.keyframes.forEach(function (keyframe) {
keyframe.declarations.forEach(function(decl) { keyframe.declarations.forEach(function (decl) {
keyFrameObj[keyframe.values[0]] = _objectSpread2({}, keyFrameObj[keyframe.values[0]], _defineProperty({}, decl.property, decl.value)); keyFrameObj[keyframe.values[0]] = _objectSpread2(
{},
keyFrameObj[keyframe.values[0]],
_defineProperty({}, decl.property, decl.value)
);
}); });
}); });
let name = camelize('keyframes-'.concat(rule.name)); let name = camelize('keyframes-'.concat(rule.name));
@ -198,11 +217,11 @@ const keyframes = function keyframes(rule) {
const standard = function standard(rule, result) { const standard = function standard(rule, result) {
const obj = {}; const obj = {};
let retObj = {}; let retObj = {};
rule.declarations.forEach(function(declaration) { rule.declarations.forEach(function (declaration) {
const cssProperty = camelize(declaration.property); const cssProperty = camelize(declaration.property);
obj[cssProperty] = declaration.value; obj[cssProperty] = declaration.value;
}); });
rule.selectors.forEach(function(selector) { rule.selectors.forEach(function (selector) {
let name; // Check if selector contains a pseudo selector let name; // Check if selector contains a pseudo selector
const pseudoSelectorIndex = selector.indexOf(':'); const pseudoSelectorIndex = selector.indexOf(':');
@ -210,11 +229,18 @@ const standard = function standard(rule, result) {
if (pseudoSelectorIndex !== -1) { if (pseudoSelectorIndex !== -1) {
// Find end of pseudo selector // Find end of pseudo selector
let endPseudoSelectorIndex = selector.indexOf(' ', pseudoSelectorIndex); let endPseudoSelectorIndex = selector.indexOf(' ', pseudoSelectorIndex);
if (endPseudoSelectorIndex === -1) endPseudoSelectorIndex = selector.length; // Split selector if (endPseudoSelectorIndex === -1)
endPseudoSelectorIndex = selector.length; // Split selector
const primarySelector = selector.slice(0, pseudoSelectorIndex); const primarySelector = selector.slice(0, pseudoSelectorIndex);
const pseudoSelector = selector.slice(pseudoSelectorIndex, endPseudoSelectorIndex); const pseudoSelector = selector.slice(
const secondarySelector = selector.slice(endPseudoSelectorIndex, selector.length); pseudoSelectorIndex,
endPseudoSelectorIndex
);
const secondarySelector = selector.slice(
endPseudoSelectorIndex,
selector.length
);
const pseudoObj = {}; const pseudoObj = {};
pseudoObj['&'.concat(pseudoSelector).concat(secondarySelector)] = obj; pseudoObj['&'.concat(pseudoSelector).concat(secondarySelector)] = obj;
name = sanitize(primarySelector.trim()); name = sanitize(primarySelector.trim());
@ -228,9 +254,10 @@ const standard = function standard(rule, result) {
}; };
const convertRules = function convertRules(rules) { const convertRules = function convertRules(rules) {
const res = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const res =
arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
let result = res; let result = res;
rules.forEach(function(rule) { rules.forEach(function (rule) {
if (rule.type === 'media') { if (rule.type === 'media') {
// Convert @media rules // Convert @media rules
const name = '@media '.concat(rule.media); const name = '@media '.concat(rule.media);
@ -248,7 +275,7 @@ const convertRules = function convertRules(rules) {
} else if (rule.type === 'rule') { } else if (rule.type === 'rule') {
// Convert standard CSS rules // Convert standard CSS rules
const standardProp = standard(rule, result); const standardProp = standard(rule, result);
Object.entries(standardProp).forEach(function(_ref) { Object.entries(standardProp).forEach(function (_ref) {
const _ref2 = _slicedToArray(_ref, 2); const _ref2 = _slicedToArray(_ref, 2);
const key = _ref2[0]; const key = _ref2[0];
const value = _ref2[1]; const value = _ref2[1];
@ -275,8 +302,9 @@ const reverseMediaQueries = function reverseMediaQueries(inputData) {
const exportObject = {}; const exportObject = {};
const moveMediaInsideClass = function moveMediaInsideClass(object) { const moveMediaInsideClass = function moveMediaInsideClass(object) {
const media = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; const media =
Object.entries(object).forEach(function(_ref) { arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
Object.entries(object).forEach(function (_ref) {
const _ref2 = _slicedToArray(_ref, 2); const _ref2 = _slicedToArray(_ref, 2);
const key = _ref2[0]; const key = _ref2[0];
const value = _ref2[1]; const value = _ref2[1];
@ -288,7 +316,12 @@ const reverseMediaQueries = function reverseMediaQueries(inputData) {
tempObj[media] = value; tempObj[media] = value;
if (exportObject[key]) { if (exportObject[key]) {
exportObject[key] = _objectSpread2({}, exportObject[key], {}, tempObj); exportObject[key] = _objectSpread2(
{},
exportObject[key],
{},
tempObj
);
} else { } else {
exportObject[key] = tempObj; exportObject[key] = tempObj;
} }
@ -317,7 +350,8 @@ const convertStringToJson = function convertStringToJson(input, mediaReverse) {
}; };
const convert = function convert(input) { const convert = function convert(input) {
const config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const config =
arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const outputType = config.outputType; const outputType = config.outputType;
const outputPath = config.outputPath; const outputPath = config.outputPath;
let mediaReverse = config.mediaReverse; let mediaReverse = config.mediaReverse;
@ -331,7 +365,7 @@ const convert = function convert(input) {
if (!outputType) { if (!outputType) {
if (Array.isArray(convertedCss)) { if (Array.isArray(convertedCss)) {
return convertedCss.map(function(obj) { return convertedCss.map(function (obj) {
return obj.contents; return obj.contents;
}); });
} else { } else {
@ -340,7 +374,7 @@ const convert = function convert(input) {
} else { } else {
const writeRecur = function writeRecur(input) { const writeRecur = function writeRecur(input) {
if (Array.isArray(input)) { if (Array.isArray(input)) {
input.forEach(function(obj) { input.forEach(function (obj) {
writeRecur(obj); writeRecur(obj);
}); });
} }

View file

@ -9,8 +9,8 @@ const ROOT = path.resolve(__dirname, '../../');
const LIB = path.join(ROOT, 'src/components'); const LIB = path.join(ROOT, 'src/components');
const TEMPLATE = path.join(__dirname, 'template'); const TEMPLATE = path.join(__dirname, 'template');
const makeNewComponent = async() => { const makeNewComponent = async () => {
const{ name } = await prompts({ const { name } = await prompts({
type: 'text', type: 'text',
name: 'name', name: 'name',
message: 'What should we call your new component, e.g., ImagePack?', message: 'What should we call your new component, e.g., ImagePack?',
@ -21,24 +21,29 @@ const makeNewComponent = async() => {
const componentName = pascalCase(name); const componentName = pascalCase(name);
const componentDir = path.join(LIB, componentName); const componentDir = path.join(LIB, componentName);
if(fs.existsSync(componentDir)) { if (fs.existsSync(componentDir)) {
console.log('Oops! That component already exists. Try another name?'); console.log('Oops! That component already exists. Try another name?');
return; return;
} }
fs.mkdirSync(componentDir); fs.mkdirSync(componentDir);
const files = await glob('**/*', { cwd: TEMPLATE, filesOnly: true }); const files = await glob('**/*', { cwd: TEMPLATE, filesOnly: true });
for (const file of files) { for (const file of files) {
const content = fs.readFileSync(path.join(TEMPLATE, file), 'utf8'); const content = fs.readFileSync(path.join(TEMPLATE, file), 'utf8');
const writeContent = content.replace(/YourComponent/g, componentName); const writeContent = content.replace(/YourComponent/g, componentName);
const writePath = path.join(LIB, file.replace(/YourComponent/g, componentName)); const writePath = path.join(
LIB,
file.replace(/YourComponent/g, componentName)
);
fs.ensureDirSync(path.dirname(writePath)); fs.ensureDirSync(path.dirname(writePath));
fs.writeFileSync(writePath, writeContent); fs.writeFileSync(writePath, writeContent);
} }
console.log(`${green('✔')} ${bold('Your component is ready at:')}\n📁 ${cyan(`src/component/${bold(componentName)}/${componentName}.svelte`)}`); console.log(
`${green('✔')} ${bold('Your component is ready at:')}\n📁 ${cyan(`src/component/${bold(componentName)}/${componentName}.svelte`)}`
);
}; };
makeNewComponent(); makeNewComponent();

View file

@ -19,7 +19,7 @@
// You can declare custom types to help users implement your component. // You can declare custom types to help users implement your component.
type ContainerWidth = 'normal' | 'wide' | 'wider' | 'widest' | 'fluid'; type ContainerWidth = 'normal' | 'wide' | 'wider' | 'widest' | 'fluid';
/** Width of the component within the text well. */ /** Width of the component within the text well. */
export let width: ContainerWidth = 'normal'; export let width: ContainerWidth = 'normal';
@ -34,9 +34,9 @@
<Block {width} {id} cls="photo {cls}"> <Block {width} {id} cls="photo {cls}">
<div <div
style:background-image={`url(${src})`} style:background-image="{`url(${src})`}"
style:height={`${height}px`} style:height="{`${height}px`}"
/> ></div>
<p class="visually-hidden">{altText}</p> <p class="visually-hidden">{altText}</p>
</Block> </Block>

View file

@ -5,9 +5,13 @@ export const scss = {
includePaths: ['src/', 'node_modules/bootstrap/scss/'], includePaths: ['src/', 'node_modules/bootstrap/scss/'],
importer: [ importer: [
(url) => { (url) => {
if (/^\$lib/.test(url)) { return { file: `src/${url.replace('$lib', '')}` }; } if (/^\$lib/.test(url)) {
return { file: `src/${url.replace('$lib', '')}` };
}
// Redirect tilde-prefixed imports to node_modules // Redirect tilde-prefixed imports to node_modules
if (/^~/.test(url)) { return { file: `node_modules/${url.replace('~', '')}` }; } if (/^~/.test(url)) {
return { file: `node_modules/${url.replace('~', '')}` };
}
return null; return null;
}, },
], ],

24
eslint.config.js Normal file
View file

@ -0,0 +1,24 @@
import { svelte } from '@reuters-graphics/yaks-eslint';
import reactPlugin from 'eslint-plugin-react';
/**
* @type {import("eslint").Linter.Config[]}
*/
export default [
{
files: ['src/**/*.{js,ts,svelte,jsx,tsx}', '.storybook/**/*'],
ignores: ['node_modules', 'docs/**/*'],
},
...svelte,
reactPlugin.configs.flat.recommended,
{
rules: {
'react/prop-types': [
'error',
{
skipUndeclared: true,
},
],
},
},
];

13820
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -28,16 +28,13 @@
"dist" "dist"
], ],
"engines": { "engines": {
"node": ">=16.19" "node": ">=18.20"
}, },
"devDependencies": { "devDependencies": {
"@americanexpress/css-to-js": "^1.0.1", "@americanexpress/css-to-js": "^1.0.1",
"@babel/core": "^7.21.4", "@evilmartians/lefthook": "^1.7.14",
"@babel/eslint-parser": "^7.21.3", "@reuters-graphics/yaks-eslint": "^0.0.6",
"@babel/eslint-plugin": "^7.19.1", "@reuters-graphics/yaks-prettier": "^0.0.4",
"@babel/preset-env": "^7.21.4",
"@evilmartians/lefthook": "^1.3.10",
"@reuters-graphics/eslint-config": "^0.0.2",
"@storybook/addon-actions": "^7.4.2", "@storybook/addon-actions": "^7.4.2",
"@storybook/addon-docs": "^7.4.2", "@storybook/addon-docs": "^7.4.2",
"@storybook/addon-essentials": "^7.4.2", "@storybook/addon-essentials": "^7.4.2",
@ -54,41 +51,29 @@
"@storybook/testing-library": "^0.1.0", "@storybook/testing-library": "^0.1.0",
"@storybook/theming": "^7.4.2", "@storybook/theming": "^7.4.2",
"@sveltejs/vite-plugin-svelte": "^2.4.1", "@sveltejs/vite-plugin-svelte": "^2.4.1",
"@tsconfig/svelte": "^4.0.1", "@types/eslint": "^9.6.0",
"@types/google-publisher-tag": "^1.20240219.0", "@types/google-publisher-tag": "^1.20240219.0",
"@types/gtag.js": "^0.0.12", "@types/gtag.js": "^0.0.12",
"@types/lodash-es": "^4.17.12",
"@types/mdx": "^2.0.5", "@types/mdx": "^2.0.5",
"@types/proper-url-join": "^2.1.1", "@types/proper-url-join": "^2.1.1",
"@types/react-syntax-highlighter": "^15.5.7", "@types/react-syntax-highlighter": "^15.5.7",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"auto": "^11.0.0", "auto": "^11.0.0",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"babel-loader": "^9.1.2",
"change-case": "^4.1.2", "change-case": "^4.1.2",
"chromatic": "^7.1.0", "chromatic": "^7.1.0",
"colord": "^2.9.3", "colord": "^2.9.3",
"css-color-converter": "^2.0.0", "css-color-converter": "^2.0.0",
"deep-object-diff": "^1.1.9", "deep-object-diff": "^1.1.9",
"eslint": "^8.56.0", "eslint": "^9.9.0",
"eslint-config-prettier": "^9.1.0", "eslint-plugin-react": "^7.35.0",
"eslint-config-standard-jsx": "^11.0.0",
"eslint-config-standard-react": "^13.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^16.0.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-storybook": "^0.6.12",
"eslint-plugin-svelte": "^2.35.1",
"fs-extra": "^11.1.1", "fs-extra": "^11.1.1",
"kleur": "^4.1.5", "kleur": "^4.1.5",
"mermaid": "^10.3.0", "mermaid": "^10.3.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"postcss": "^8.4.24", "postcss": "^8.4.24",
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.1",
"prettier": "^2.8.8", "prettier": "^3.3.3",
"prettier-plugin-svelte": "^2.10.1",
"prompts": "^2.4.2", "prompts": "^2.4.2",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^18.2.0", "react": "^18.2.0",
@ -104,7 +89,7 @@
"svelte-preprocess": "^5.1.3", "svelte-preprocess": "^5.1.3",
"svelte2tsx": "^0.6.27", "svelte2tsx": "^0.6.27",
"tiny-glob": "^0.2.9", "tiny-glob": "^0.2.9",
"typescript": "^5.3.3", "typescript": "^5.5.4",
"vite": "^4.4.9" "vite": "^4.4.9"
}, },
"dependencies": { "dependencies": {
@ -115,14 +100,13 @@
"@sveltejs/svelte-scroller": "^2.0.7", "@sveltejs/svelte-scroller": "^2.0.7",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"dayjs": "^1.11.3", "dayjs": "^1.11.3",
"journalize": "^2.5.1", "journalize": "^2.6.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"lottie-web": "^5.7.13",
"marked": "^4.0.8", "marked": "^4.0.8",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"proper-url-join": "^2.1.1", "proper-url-join": "^2.1.1",
"pym.js": "^1.3.2", "pym.js": "^1.3.2",
"slugify": "^1.6.5", "slugify": "^1.6.6",
"standard": "^17.1.0", "standard": "^17.1.0",
"svelte-fa": "^2.4.0", "svelte-fa": "^2.4.0",
"svelte-intersection-observer": "^0.10.0", "svelte-intersection-observer": "^0.10.0",

View file

@ -11,7 +11,14 @@ export type Option = {
/** /**
* Used for any props that restrict width of a container to one of pre-fab widths. * Used for any props that restrict width of a container to one of pre-fab widths.
*/ */
export type ContainerWidth = 'narrower' | 'narrow' | 'normal' | 'wide' | 'wider' | 'widest' | 'fluid'; export type ContainerWidth =
| 'narrower'
| 'narrow'
| 'normal'
| 'wide'
| 'wider'
| 'widest'
| 'fluid';
/** /**
* Used to set headline class fluid size from text-2xl to text-6xl * Used to set headline class fluid size from text-2xl to text-6xl
@ -30,7 +37,7 @@ export interface ScrollerStep {
/** /**
* Optional props for background component * Optional props for background component
*/ */
backgroundProps?: Object; backgroundProps?: object;
/** /**
* Foreground can either be a component or markdown-formatted string. * Foreground can either be a component or markdown-formatted string.
* @required * @required
@ -39,5 +46,5 @@ export interface ScrollerStep {
/** /**
* Optional props for foreground component * Optional props for foreground component
*/ */
foregroundProps?: Object; foregroundProps?: object;
} }

View file

@ -23,30 +23,36 @@ export type SponsorshipAd = {
export type InlineAd = { export type InlineAd = {
mobile: { mobile: {
adType: 'mpu' | 'native' | 'mpu2'; adType: 'mpu' | 'native' | 'mpu2';
placementName: 'reuters_mobile_mpu_1' | 'reuters_mobile_mpu_2' | 'reuters_mobile_mpu_3'; placementName:
| 'reuters_mobile_mpu_1'
| 'reuters_mobile_mpu_2'
| 'reuters_mobile_mpu_3';
}; };
desktop: { desktop: {
adType: 'native' | 'canvas' | 'flex'; adType: 'native' | 'canvas' | 'flex';
placementName: 'reuters_desktop_native_1' | 'reuters_desktop_native_2' | 'reuters_desktop_native_3'; placementName:
| 'reuters_desktop_native_1'
| 'reuters_desktop_native_2'
| 'reuters_desktop_native_3';
}; };
}; };
export type DesktopPlacementName = | export type DesktopPlacementName =
LeaderboardAd['desktop']['placementName'] | | LeaderboardAd['desktop']['placementName']
SponsorshipAd['desktop']['placementName'] | | SponsorshipAd['desktop']['placementName']
InlineAd['desktop']['placementName']; | InlineAd['desktop']['placementName'];
export type MobilePlacementName = | export type MobilePlacementName =
LeaderboardAd['mobile']['placementName'] | | LeaderboardAd['mobile']['placementName']
SponsorshipAd['mobile']['placementName'] | | SponsorshipAd['mobile']['placementName']
InlineAd['mobile']['placementName']; | InlineAd['mobile']['placementName'];
export type DesktopAdType = | export type DesktopAdType =
LeaderboardAd['desktop']['adType'] | | LeaderboardAd['desktop']['adType']
SponsorshipAd['desktop']['adType'] | | SponsorshipAd['desktop']['adType']
InlineAd['desktop']['adType']; | InlineAd['desktop']['adType'];
export type MobileAdType = | export type MobileAdType =
LeaderboardAd['mobile']['adType'] | | LeaderboardAd['mobile']['adType']
SponsorshipAd['mobile']['adType'] | | SponsorshipAd['mobile']['adType']
InlineAd['mobile']['adType']; | InlineAd['mobile']['adType'];

View file

@ -27,7 +27,7 @@
type: adType, type: adType,
}, },
}; };
// @ts-ignore // @ts-ignore window global
const freestar = window?.freestar; const freestar = window?.freestar;
// Add adSlot to freestar queue directly if already initialised // Add adSlot to freestar queue directly if already initialised
if (freestar) { if (freestar) {
@ -41,7 +41,7 @@
} }
return () => { return () => {
// @ts-ignore // @ts-ignore window global
const freestar = window?.freestar; const freestar = window?.freestar;
if (freestar) { if (freestar) {
freestar.queue.push(function () { freestar.queue.push(function () {

View file

@ -17,13 +17,13 @@
const desktopPlacementName: InlineAd['desktop']['placementName'] = `reuters_desktop_native_${n}`; const desktopPlacementName: InlineAd['desktop']['placementName'] = `reuters_desktop_native_${n}`;
</script> </script>
<Block id="{id}" class="freestar-adslot {cls}"> <Block {id} class="freestar-adslot {cls}">
<div class="ad-block"> <div class="ad-block">
<div class="ad-label">Advertisement · Scroll to continue</div> <div class="ad-label">Advertisement · Scroll to continue</div>
<div class="ad-container"> <div class="ad-container">
<div class="ad-slot__inner"> <div class="ad-slot__inner">
<div> <div>
<ResponsiveAd desktopPlacementName="{desktopPlacementName}" /> <ResponsiveAd {desktopPlacementName} />
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,7 +1,7 @@
<script> <script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf'; import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore // @ts-ignore raw
import adDocs from './stories/docs/leaderboard.md?raw'; import adDocs from './stories/docs/leaderboard.md?raw';
import AdScripts from './AdScripts.svelte'; import AdScripts from './AdScripts.svelte';
@ -18,7 +18,7 @@
<Meta title="Components/LeaderboardAd" {...meta} /> <Meta title="Components/LeaderboardAd" {...meta} />
<Template let:args> <Template>
<div> <div>
<AdScripts /> <AdScripts />
<LeaderboardAd /> <LeaderboardAd />

View file

@ -48,16 +48,16 @@
<div <div
class="leaderboard__sticky {cls}" class="leaderboard__sticky {cls}"
class:sticky="{sticky}" class:sticky
class:unstick="{unstick}" class:unstick
id="{id}" {id}
style="--height: {adSize}px;" style="--height: {adSize}px;"
> >
<div class="ad-block"> <div class="ad-block">
<div class="ad-slot__container"> <div class="ad-slot__container">
<div class="ad-slot__inner"> <div class="ad-slot__inner">
<div> <div>
<ResponsiveAd desktopPlacementName="{desktopPlacementName}" /> <ResponsiveAd {desktopPlacementName} />
</div> </div>
</div> </div>
</div> </div>

View file

@ -58,9 +58,9 @@
}; };
$: placementName = $: placementName =
windowWidth && windowWidth < mobileBreakpoint windowWidth && windowWidth < mobileBreakpoint ?
? getMobilePlacementName(desktopPlacementName) getMobilePlacementName(desktopPlacementName)
: desktopPlacementName; : desktopPlacementName;
$: adType = getAdType(placementName); $: adType = getAdType(placementName);
</script> </script>
@ -68,6 +68,6 @@
{#if windowWidth} {#if windowWidth}
{#key placementName} {#key placementName}
<AdSlot placementName="{placementName}" adType="{adType}" /> <AdSlot {placementName} {adType} />
{/key} {/key}
{/if} {/if}

View file

@ -1,7 +1,7 @@
<script> <script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf'; import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore // @ts-ignore raw
import adDocs from './stories/docs/sponsorship.md?raw'; import adDocs from './stories/docs/sponsorship.md?raw';
import AdScripts from './AdScripts.svelte'; import AdScripts from './AdScripts.svelte';
@ -18,7 +18,7 @@
<Meta title="Components/SponsorshipAd" {...meta} /> <Meta title="Components/SponsorshipAd" {...meta} />
<Template let:args> <Template>
<div> <div>
<AdScripts /> <AdScripts />
<SponsorshipAd /> <SponsorshipAd />

View file

@ -20,7 +20,7 @@
'reuters_sponsorlogo'; 'reuters_sponsorlogo';
</script> </script>
<Block id="{id}" class="freestar-adslot {cls}"> <Block {id} class="freestar-adslot {cls}">
<div class="ad-block"> <div class="ad-block">
{#if adLabel} {#if adLabel}
<div class="ad-label"> <div class="ad-label">
@ -30,7 +30,7 @@
<div class="ad-container"> <div class="ad-container">
<div class="ad-slot__inner"> <div class="ad-slot__inner">
<div> <div>
<ResponsiveAd desktopPlacementName="{desktopPlacementName}" /> <ResponsiveAd {desktopPlacementName} />
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import getParameterByName from './getParameterByName'; import getParameterByName from './getParameterByName';
import Ias from './ias'; import Ias from './ias';
@ -12,10 +13,12 @@ export const loadBootstrap = () => {
freestar.queue = freestar.queue || []; freestar.queue = freestar.queue || [];
freestar.config = freestar.config || {}; freestar.config = freestar.config || {};
freestar.config.enabled_slots = []; freestar.config.enabled_slots = [];
freestar.initCallback = function() { freestar.initCallback = function () {
freestar.config.enabled_slots.length === 0 ? if (freestar.config.enabled_slots.length === 0) {
(freestar.initCallbackCalled = false) : freestar.initCallbackCalled = false;
} else {
freestar.newAdSlots(freestar.config.enabled_slots); freestar.newAdSlots(freestar.config.enabled_slots);
}
}; };
freestar.config.channel = '/4735792/reuters.com/graphics'; freestar.config.channel = '/4735792/reuters.com/graphics';
@ -40,7 +43,7 @@ export const loadBootstrap = () => {
} }
); );
(<any>window).bootstrap.getResults((result) => { (<any>window).bootstrap.getResults(() => {
// Set GAM // Set GAM
window.googletag = (<any>window).googletag || { cmd: [] }; window.googletag = (<any>window).googletag || { cmd: [] };
window.googletag.cmd.push(() => { window.googletag.cmd.push(() => {
@ -48,24 +51,29 @@ export const loadBootstrap = () => {
/** /**
* @TODO Property 'enableAsyncRendering' does not exist on type 'PubAdsService'. * @TODO Property 'enableAsyncRendering' does not exist on type 'PubAdsService'.
*/ */
// @ts-ignore // @ts-ignore window global
window.googletag.pubads().enableAsyncRendering(); window.googletag.pubads().enableAsyncRendering();
window.googletag.pubads().collapseEmptyDivs(true); window.googletag.pubads().collapseEmptyDivs(true);
}); });
// Set page-level key-values // Set page-level key-values
// cf: https://help.freestar.com/help/using-key-values // cf: https://help.freestar.com/help/using-key-values
freestar.queue.push(function() { freestar.queue.push(function () {
// Global Ads test targeting // Global Ads test targeting
const adstest = new URL(document.location.href).searchParams.get('adstest'); const adstest = new URL(document.location.href).searchParams.get(
'adstest'
);
if (adstest) { if (adstest) {
window.googletag.pubads().setTargeting('adstest', adstest); window.googletag.pubads().setTargeting('adstest', adstest);
} }
// Use the URL path to create a unique ID for the page. // Use the URL path to create a unique ID for the page.
const graphicId = window.location.pathname.split('/') const graphicId =
// Get the first lowercase slug in the pathname, which is the graphic UID. window.location.pathname
.filter(d => d.match(/[a-z0-9]+/) && d !== 'graphics')[0] || 'unknown-graphic'; .split('/')
// Get the first lowercase slug in the pathname, which is the graphic UID.
.filter((d) => d.match(/[a-z0-9]+/) && d !== 'graphics')[0] ||
'unknown-graphic';
window.googletag.pubads().setTargeting('template', 'graphics'); window.googletag.pubads().setTargeting('template', 'graphics');
window.googletag.pubads().setTargeting('graphicId', graphicId); window.googletag.pubads().setTargeting('graphicId', graphicId);
}); });
@ -74,8 +82,11 @@ export const loadBootstrap = () => {
console.error('Ad queue not initialized!'); console.error('Ad queue not initialized!');
} }
freestar.queue.push(function() { freestar.queue.push(function () {
freestar.newAdSlots((<any>window).graphicsAdQueue || [], freestar.config.channel); freestar.newAdSlots(
(<any>window).graphicsAdQueue || [],
freestar.config.channel
);
}); });
}); });
}; };

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
const IAS_REQUEST_TIMEOUT = 600; const IAS_REQUEST_TIMEOUT = 600;
export default () => { export default () => {

View file

@ -1,6 +1,6 @@
interface attributesInterface { interface attributesInterface {
onload?: () => void, onload?: () => void;
async?: boolean async?: boolean;
} }
export const loadScript = (src: string, attributes?: attributesInterface) => { export const loadScript = (src: string, attributes?: attributesInterface) => {

View file

@ -1,12 +1,11 @@
<script> <script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf'; import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// Don't lose the "?raw" in markdown imports! // @ts-ignore raw
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw'; import componentDocs from './stories/docs/component.md?raw';
// @ts-ignore // @ts-ignore raw
import environmentsDocs from './stories/docs/environments.md?raw'; import environmentsDocs from './stories/docs/environments.md?raw';
// @ts-ignore // @ts-ignore raw
import multipageDocs from './stories/docs/multipage.md?raw'; import multipageDocs from './stories/docs/multipage.md?raw';
import Analytics from './Analytics.svelte'; import Analytics from './Analytics.svelte';

View file

@ -1,10 +1,9 @@
<script lang="ts"> <script lang="ts">
import { Meta, Template, Story } from '@storybook/addon-svelte-csf'; import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// Don't lose the "?raw" in markdown imports! // @ts-ignore raw
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw'; import componentDocs from './stories/docs/component.md?raw';
// @ts-ignore // @ts-ignore raw
import customWellWidthsDocs from './stories/docs/customWellWidths.md?raw'; import customWellWidthsDocs from './stories/docs/customWellWidths.md?raw';
import Article from './Article.svelte'; import Article from './Article.svelte';

View file

@ -43,12 +43,7 @@
</script> </script>
<main> <main>
<article <article {id} class:embedded {role} use:cssVariables="{columnWidthVars}">
id="{id}"
class:embedded
role="{role}"
use:cssVariables="{columnWidthVars}"
>
<!-- Article content --> <!-- Article content -->
<slot /> <slot />
</article> </article>

View file

@ -1,18 +1,18 @@
<script> <script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf'; import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore // @ts-ignore raw
import componentDocs from './stories/docs/component.md?raw'; import componentDocs from './stories/docs/component.md?raw';
// @ts-ignore // @ts-ignore raw
import withOverlaysDocs from './stories/docs/withOverlays.md?raw'; import withOverlaysDocs from './stories/docs/withOverlays.md?raw';
// @ts-ignore // @ts-ignore raw
import ariaDescriptionsDocs from './stories/docs/ariaDescriptions.md?raw'; import ariaDescriptionsDocs from './stories/docs/ariaDescriptions.md?raw';
import BeforeAfter from './BeforeAfter.svelte'; import BeforeAfter from './BeforeAfter.svelte';
// @ts-ignore // @ts-ignore raw
import beforeImg from './stories/myrne-before.jpg'; import beforeImg from './stories/myrne-before.jpg';
// @ts-ignore // @ts-ignore raw
import afterImg from './stories/myrne-after.jpg'; import afterImg from './stories/myrne-after.jpg';
import { import {
@ -90,13 +90,13 @@
afterAlt="Satellite image of Russian base at Myrne taken on Oct. 20, 2020." afterAlt="Satellite image of Russian base at Myrne taken on Oct. 20, 2020."
> >
<div let:description="{id}" slot="beforeOverlay" class="overlay p-3"> <div let:description="{id}" slot="beforeOverlay" class="overlay p-3">
<p class="body-caption" id="{id}"> <p class="body-caption" {id}>
On July 7, 2020, the base contained only a few transport vehicles. On July 7, 2020, the base contained only a few transport vehicles.
</p> </p>
</div> </div>
<div let:description="{id}" slot="afterOverlay" class="overlay p-3"> <div let:description="{id}" slot="afterOverlay" class="overlay p-3">
<!-- 👇 id can also be used on an element containing multiple text elements --> <!-- 👇 id can also be used on an element containing multiple text elements -->
<div id="{id}"> <div {id}>
<p class="body-caption"> <p class="body-caption">
But by October, tanks and artillery could be seen. But by October, tanks and artillery could be seen.
</p> </p>

View file

@ -116,16 +116,15 @@
const move = (e) => { const move = (e) => {
if (sliding && imgOffset) { if (sliding && imgOffset) {
const el = e.touches ? e.touches[0] : e; const el = e.touches ? e.touches[0] : e;
const figureOffset = figure const figureOffset =
? parseInt(window.getComputedStyle(figure).marginLeft.slice(0, -2)) figure ?
parseInt(window.getComputedStyle(figure).marginLeft.slice(0, -2))
: 0; : 0;
let x = el.pageX - figureOffset - imgOffset.left; let x = el.pageX - figureOffset - imgOffset.left;
x = x =
x < handleMargin x < handleMargin ? handleMargin
? handleMargin : x > w - handleMargin ? w - handleMargin
: x > w - handleMargin : x;
? w - handleMargin
: x;
offset = x / w; offset = x / w;
} }
}; };
@ -160,7 +159,7 @@
/> />
{#if beforeSrc && beforeAlt && afterSrc && afterAlt} {#if beforeSrc && beforeAlt && afterSrc && afterAlt}
<Block width="{width}" id="{id}" class="photo before-after fmy-6 {cls}"> <Block {width} {id} class="photo before-after fmy-6 {cls}">
<div <div
style="height: {containerHeight}px;" style="height: {containerHeight}px;"
bind:clientWidth="{containerWidth}" bind:clientWidth="{containerWidth}"

View file

@ -1,11 +1,11 @@
<script> <script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf'; import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore // @ts-ignore raw
import componentDocs from './stories/docs/component.md?raw'; import componentDocs from './stories/docs/component.md?raw';
// @ts-ignore // @ts-ignore raw
import customLayoutsDocs from './stories/docs/customLayouts.md?raw'; import customLayoutsDocs from './stories/docs/customLayouts.md?raw';
// @ts-ignore // @ts-ignore raw
import snapWidthsDocs from './stories/docs/snapWidths.md?raw'; import snapWidthsDocs from './stories/docs/snapWidths.md?raw';
import Block from './Block.svelte'; import Block from './Block.svelte';

View file

@ -23,10 +23,10 @@
</script> </script>
<div <div
id="{id}" {id}
class="article-block fmx-auto {width} {cls}" class="article-block fmx-auto {width} {cls}"
class:snap="{snap && width !== 'fluid' && width !== 'widest'}" class:snap="{snap && width !== 'fluid' && width !== 'widest'}"
role="{role}" {role}
aria-label="{ariaLabel}" aria-label="{ariaLabel}"
> >
<!-- block content --> <!-- block content -->

View file

@ -1,7 +1,7 @@
<script> <script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf'; import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore // @ts-ignore raw
import componentDocs from './stories/docs/component.md?raw'; import componentDocs from './stories/docs/component.md?raw';
import BodyText from './BodyText.svelte'; import BodyText from './BodyText.svelte';

View file

@ -18,6 +18,6 @@
import Block from '../Block/Block.svelte'; import Block from '../Block/Block.svelte';
</script> </script>
<Block id="{id}" class="fmy-6 {cls}"> <Block {id} class="fmy-6 {cls}">
<Markdown source="{text}" /> <Markdown source="{text}" />
</Block> </Block>

View file

@ -1,7 +1,7 @@
<script> <script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf'; import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore // @ts-ignore raw
import componentDocs from './stories/docs/component.md?raw'; import componentDocs from './stories/docs/component.md?raw';
import Byline from './Byline.svelte'; import Byline from './Byline.svelte';

View file

@ -64,7 +64,7 @@
first.getDate() === second.getDate(); first.getDate() === second.getDate();
</script> </script>
<Block id="{id}" class="byline-container {alignmentClass} {cls}" width="normal"> <Block {id} class="byline-container {alignmentClass} {cls}" width="normal">
<aside class="article-metadata font-subhed"> <aside class="article-metadata font-subhed">
<div class="byline body-caption fmb-1"> <div class="byline body-caption fmb-1">
{#if $$slots.byline} {#if $$slots.byline}

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light'; import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light';
// @ts-ignore // @ts-ignore scss
import classes from './styles.module.scss'; import classes from './styles.module.scss';
import prism from 'react-syntax-highlighter/dist/esm/styles/prism/prism'; import prism from 'react-syntax-highlighter/dist/esm/styles/prism/prism';
import scss from 'react-syntax-highlighter/dist/esm/languages/prism/scss'; import scss from 'react-syntax-highlighter/dist/esm/languages/prism/scss';
@ -18,15 +18,17 @@ const Copyable = (props) => {
let timeout; let timeout;
useEffect(() => { useEffect(() => {
if(timeout) clearTimeout(timeout); if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => { setCopied(false); }, 1500); timeout = setTimeout(() => {
setCopied(false);
}, 1500);
}, [copied]); }, [copied]);
const handleClick = async({ partial }) => { const handleClick = async ({ partial }) => {
const copyText = `@import "@reuters-graphics/graphics-components/scss/colours/${formatPartial(partial)}";` const copyText = `@import "@reuters-graphics/graphics-components/scss/colours/${formatPartial(partial)}";`;
await navigator.clipboard.writeText(copyText); await navigator.clipboard.writeText(copyText);
setCopied(true); setCopied(true);
} };
return ( return (
<button className="copy-btn" onClick={() => handleClick(props)}> <button className="copy-btn" onClick={() => handleClick(props)}>
@ -34,21 +36,19 @@ const Copyable = (props) => {
{copied && <span className="copy-tag">Copied</span>} {copied && <span className="copy-tag">Copied</span>}
</button> </button>
); );
}
const ImportSnippet = ({ included = false, partial = 'thematic/_tr.scss' }) => {
return included ? (
<div className={classes.importsnippet}>
<p>Included</p>
</div>
) : (
<div className={classes.importsnippet}>
<SyntaxHighlighter language="scss" style={prism}>
{`// global.scss \n@import "@reuters-graphics/graphics-components/scss/colours/${formatPartial(partial)}";`}
</SyntaxHighlighter>
<Copyable partial={partial} />
</div>
)
}; };
export default ImportSnippet; const ImportSnippet = ({ included = false, partial = 'thematic/_tr.scss' }) => {
return included ?
<div className={classes.importsnippet}>
<p>Included</p>
</div>
: <div className={classes.importsnippet}>
<SyntaxHighlighter language="scss" style={prism}>
{`// global.scss \n@import "@reuters-graphics/graphics-components/scss/colours/${formatPartial(partial)}";`}
</SyntaxHighlighter>
<Copyable partial={partial} />
</div>;
};
export default ImportSnippet;

View file

@ -2,23 +2,24 @@ import React, { useEffect, useState } from 'react';
import ImportSnippet from './ImportSnippet'; import ImportSnippet from './ImportSnippet';
import { Unstyled } from '@storybook/blocks'; import { Unstyled } from '@storybook/blocks';
// @ts-ignore // @ts-ignore scss
import classes from './styles.module.scss'; import classes from './styles.module.scss';
const Copyable = (props) => { const Copyable = (props) => {
const handleClick = async(props) => { const handleClick = async (props) => {
const copyText = props.wrap ? `var(${props.children})` : props.children; const copyText = props.wrap ? `var(${props.children})` : props.children;
await navigator.clipboard.writeText(copyText); await navigator.clipboard.writeText(copyText);
props.setCopied(true); props.setCopied(true);
} };
return ( return (
<button className="copy-btn" onClick={() => handleClick(props)}> <button className="copy-btn" onClick={() => handleClick(props)}>
<span className="material-symbols-outlined">content_copy</span>{props.children} <span className="material-symbols-outlined">content_copy</span>
{props.children}
{props.copied && <div className="copy-tag">Copied</div>} {props.copied && <div className="copy-tag">Copied</div>}
</button> </button>
); );
} };
const Cell = (props) => { const Cell = (props) => {
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
@ -26,50 +27,80 @@ const Cell = (props) => {
let timeout; let timeout;
useEffect(() => { useEffect(() => {
if(timeout) clearTimeout(timeout); if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => { setCopied(false); }, 1000); timeout = setTimeout(() => {
setCopied(false);
}, 1000);
}, [copied]); }, [copied]);
const copyProps = {...props, ...{ copied, setCopied } }; const copyProps = { ...props, ...{ copied, setCopied } };
return props.column === 0 ? return props.column === 0 ?
<div className="swatch-container"> <div className="swatch-container">
<div className="swatch" style={{ backgroundColor: props.children }}></div> <div
<span> className="swatch"
<Copyable {...copyProps}>{props.children}</Copyable> style={{ backgroundColor: props.children }}
</span> ></div>
</div> <span>
: props.children.map(css => ( <Copyable {...copyProps}>{props.children}</Copyable>
<div key={css}> </span>
<Copyable {...copyProps} wrap>{css}</Copyable> </div>
</div> : props.children.map((css) => (
)); <div key={css}>
} <Copyable {...copyProps} wrap>
{css}
</Copyable>
</div>
));
};
const TD = (props) => <td><Cell {...props}>{props.children}</Cell></td> const TD = (props) => (
const TR = (props) => <tr>{props.children.map((c, i) => (<TD {...props} column={i} key={i}>{c}</TD>))}</tr> <td>
<Cell {...props}>{props.children}</Cell>
</td>
);
const TR = (props) => (
<tr>
{props.children.map((c, i) => (
<TD {...props} column={i} key={i}>
{c}
</TD>
))}
</tr>
);
const TH = (props) => <th>{props.children}</th>; const TH = (props) => <th>{props.children}</th>;
const CopyTable = ({ title = null, body, copyable, mdnLink = null, included = false, partial }) => { const CopyTable = ({
const header=['Colour', 'CSS variable']; title = null,
body,
copyable,
mdnLink = null,
included = false,
partial,
}) => {
const header = ['Colour', 'CSS variable'];
return ( return (
<Unstyled> <Unstyled>
<div className={classes.title}> <div className={classes.title}>{title}</div>
{title}
</div>
<ImportSnippet included={included} partial={partial} /> <ImportSnippet included={included} partial={partial} />
<table className={classes.table}> <table className={classes.table}>
<thead> <thead>
<tr> <tr>
{header.map(h => (<TH key={h}>{h}</TH>))} {header.map((h) => (
<TH key={h}>{h}</TH>
))}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{body.map((b, i) => (<TR {...{ title, header, body, copyable, mdnLink}} key={i}>{b}</TR>))} {body.map((b, i) => (
<TR {...{ title, header, body, copyable, mdnLink }} key={i}>
{b}
</TR>
))}
</tbody> </tbody>
</table> </table>
</Unstyled> </Unstyled>
) );
}; };
export default CopyTable; export default CopyTable;

View file

@ -1,66 +1,99 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Unstyled } from '@storybook/blocks'; import { Unstyled } from '@storybook/blocks';
// @ts-ignore // @ts-ignore scss
import classes from './styles.module.scss'; import classes from './styles.module.scss';
const MultiLine = (props) => props.children.split('\n').map(t => (<div key={t}>{t}</div>)) const MultiLine = (props) =>
props.children.split('\n').map((t) => <div key={t}>{t}</div>);
const Copyable = (props) => { const Copyable = (props) => {
const handleClick = async(props) => { const handleClick = async (props) => {
const copyText = typeof props.copyable[props.column] === 'function' ? const copyText =
props.copyable[props.column](`${props.children}`) : `${props.children}`; typeof props.copyable[props.column] === 'function' ?
props.copyable[props.column](`${props.children}`)
: `${props.children}`;
await navigator.clipboard.writeText(copyText); await navigator.clipboard.writeText(copyText);
setCopied(true); setCopied(true);
} };
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
let timeout; let timeout;
useEffect(() => { useEffect(() => {
if(timeout) clearTimeout(timeout); if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => { setCopied(false); }, 1000); timeout = setTimeout(() => {
setCopied(false);
}, 1000);
}, [copied]); }, [copied]);
return props.copyable && props.copyable[props.column] ? return props.copyable && props.copyable[props.column] ?
<button className="copy-btn" onClick={() => handleClick(props)}> <button className="copy-btn" onClick={() => handleClick(props)}>
<span className="material-symbols-outlined">content_copy</span>{props.children} <span className="material-symbols-outlined">content_copy</span>
{copied && <div className="copy-tag">Copied</div>} {props.children}
</button> : {copied && <div className="copy-tag">Copied</div>}
<MultiLine>{props.children}</MultiLine>; </button>
} : <MultiLine>{props.children}</MultiLine>;
};
const TD = (props) => <td><Copyable {...props}>{props.children}</Copyable></td> const TD = (props) => (
const TR = (props) => <tr>{props.children.map((c, i) => (<TD {...props} column={i} key={i}>{c}</TD>))}</tr> <td>
<Copyable {...props}>{props.children}</Copyable>
</td>
);
const TR = (props) => (
<tr>
{props.children.map((c, i) => (
<TD {...props} column={i} key={i}>
{c}
</TD>
))}
</tr>
);
const TH = (props) => <th>{props.children}</th>; const TH = (props) => <th>{props.children}</th>;
const CopyTable = ({ title = null, note = null, header, body, copyable, mdnLink = null }) => { const CopyTable = ({
title = null,
note = null,
header,
body,
copyable,
mdnLink = null,
}) => {
return ( return (
<Unstyled> <Unstyled>
<div className={classes.title}> <div className={classes.title}>
{title} {title}
{(title && mdnLink) && ( {title && mdnLink && (
<a href={`https://developer.mozilla.org/en-US/docs/Web/CSS/${mdnLink}`} target="_blank"> <a
href={`https://developer.mozilla.org/en-US/docs/Web/CSS/${mdnLink}`}
target="_blank"
rel="noreferrer"
>
<span className="material-symbols-outlined">link</span> <span className="material-symbols-outlined">link</span>
</a> </a>
)} )}
{(note) && ( {note && <p>{note}</p>}
<p>{note}</p>
)}
</div> </div>
<table className={classes.table}> <table className={classes.table}>
<thead> <thead>
<tr> <tr>
{header.map(h => (<TH key={h}>{h}</TH>))} {header.map((h) => (
<TH key={h}>{h}</TH>
))}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{body.map((b, i) => (<TR {...{ title, header, body, copyable, mdnLink}} key={i}>{b}</TR>))} {body.map((b, i) => (
<TR {...{ title, header, body, copyable, mdnLink }} key={i}>
{b}
</TR>
))}
</tbody> </tbody>
</table> </table>
</Unstyled> </Unstyled>
) );
}; };
export default CopyTable; export default CopyTable;

View file

@ -12,7 +12,7 @@ import lightTheme from '../../../components/Theme/themes/light';
const ThemeWrapper = (props) => { const ThemeWrapper = (props) => {
const theme = flatten(lightTheme); const theme = flatten(lightTheme);
const styleObj = {}; const styleObj = {};
Object.keys(theme).forEach(key => { Object.keys(theme).forEach((key) => {
styleObj[`--theme-${key}`] = theme[key]; styleObj[`--theme-${key}`] = theme[key];
}); });
return ( return (

View file

@ -16,10 +16,10 @@ export default function Mermaid(props) {
setGraphSvg(svg); setGraphSvg(svg);
} catch (err) { } catch (err) {
setGraphSvg(''); setGraphSvg('');
/* eslint-disable-next-line no-console */
console.error('Invalid mermaid syntax: %o', err); console.error('Invalid mermaid syntax: %o', err);
} }
} };
parseMermaid(code); parseMermaid(code);
}, [code]); }, [code]);
@ -29,4 +29,4 @@ export default function Mermaid(props) {
dangerouslySetInnerHTML={{ __html: graphSvg || '' }} dangerouslySetInnerHTML={{ __html: graphSvg || '' }}
/> />
); );
}; }

View file

@ -16,6 +16,6 @@ const VariableTable = (props) => {
copyable={[(v) => `var(${v})`]} copyable={[(v) => `var(${v})`]}
/> />
); );
} };
export default VariableTable; export default VariableTable;

View file

@ -1,16 +1,25 @@
import { HexAlphaColorPicker, HexColorInput } from 'react-colorful'; import { HexAlphaColorPicker, HexColorInput } from 'react-colorful';
import React from 'react'; import React from 'react';
// @ts-ignore scss
import classes from './styles.module.scss'; import classes from './styles.module.scss';
import { fromString } from 'css-color-converter'; import { fromString } from 'css-color-converter';
const ColourPicker = ({ colour, onChange }) => { const ColourPicker = ({ colour, onChange }) => {
return ( return (
<div className={classes.colourpicker}> <div className={classes.colourpicker}>
<HexColorInput color={fromString(colour.trim()).toHexString()} onChange={onChange} alpha prefixed /> <HexColorInput
<HexAlphaColorPicker color={fromString(colour.trim()).toHexString()} onChange={onChange} /> color={fromString(colour.trim()).toHexString()}
onChange={onChange}
alpha
prefixed
/>
<HexAlphaColorPicker
color={fromString(colour.trim()).toHexString()}
onChange={onChange}
/>
</div> </div>
) );
} };
export default ColourPicker; export default ColourPicker;

View file

@ -1,8 +1,7 @@
import React, { useEffect, useState } from 'react'; import React from 'react';
import Key from './Key.jsx'; import Key from './Key.jsx';
import { Unstyled } from '@storybook/blocks'; // @ts-ignore scss
import Value from './Value.jsx';
import classes from './styles.module.scss'; import classes from './styles.module.scss';
const Customiser = ({ theme, themeName, setTheme }) => { const Customiser = ({ theme, themeName, setTheme }) => {
@ -19,10 +18,10 @@ const Customiser = ({ theme, themeName, setTheme }) => {
value, value,
key: themeName + key, key: themeName + key,
}; };
return <Key {...props} />; return <Key {...props} key={props.key} />;
})} })}
</div> </div>
); );
} };
export default Customiser; export default Customiser;

View file

@ -6,12 +6,18 @@ const Key = ({ value, name, map, themeName, setTheme, theme }) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
return ( return (
<div className="key"> <div className="key">
<button className={isOpen ? 'open' : ''} onClick={() => setIsOpen(o => !o)}> <button
className={isOpen ? 'open' : ''}
onClick={() => setIsOpen((o) => !o)}
>
<div> <div>
<span className="material-symbols-outlined">{isOpen ? 'expand_less' : 'expand_more'}</span> <span className="material-symbols-outlined">
</div> {name} {isOpen ? 'expand_less' : 'expand_more'}
</span>
</div>{' '}
{name}
</button> </button>
{Object.entries(value).map(([key, value]) => { {Object.entries(value).map(([key, value]) => {
const props = { const props = {
theme, theme,
@ -23,11 +29,12 @@ const Key = ({ value, name, map, themeName, setTheme, theme }) => {
key: themeName + map + key, key: themeName + map + key,
}; };
if (!isOpen) return null; if (!isOpen) return null;
if (typeof value === 'object') return <Key {...props} />; if (typeof value === 'object')
return <Value {...props} />; return <Key {...props} key={props.key} />;
return <Value {...props} key={props.key} />;
})} })}
</div> </div>
); );
} };
export default Key; export default Key;

View file

@ -8,12 +8,17 @@ const Input = ({ value, onChange }) => {
// this is buggy... // this is buggy...
// if ((value || value === 0) && !isNaN(value)) return <input type="number" value={value} onChange={(e) => onChange(Number(e.target.value))}/>; // if ((value || value === 0) && !isNaN(value)) return <input type="number" value={value} onChange={(e) => onChange(Number(e.target.value))}/>;
// Colour type // Colour type
if (!/var\(.*\)/i.test(value) && CSS.supports('color', value)) return ( if (!/var\(.*\)/i.test(value) && CSS.supports('color', value))
<ColourPicker colour={value} onChange={onChange} /> return <ColourPicker colour={value} onChange={onChange} />;
);
// Text for the rest... // Text for the rest...
return <input type="text" value={value} onChange={(e) => onChange(e.target.value)} />; return (
} <input
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
);
};
const Value = ({ value, name, map, themeName, theme, setTheme }) => { const Value = ({ value, name, map, themeName, theme, setTheme }) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@ -30,20 +35,26 @@ const Value = ({ value, name, map, themeName, theme, setTheme }) => {
<div className="value"> <div className="value">
<label> <label>
<div> <div>
<button className={isOpen ? 'open' : ''} onClick={() => setIsOpen(o => !o)}> <button
className={isOpen ? 'open' : ''}
onClick={() => setIsOpen((o) => !o)}
>
<div> <div>
<span className="material-symbols-outlined">{isOpen ? 'expand_less' : 'expand_more'}</span> <span className="material-symbols-outlined">
</div> {isColour && (<div style={{ background: value }}></div>)} {name} {isOpen ? 'expand_less' : 'expand_more'}
</span>
</div>{' '}
{isColour && <div style={{ background: value }}></div>} {name}
</button> </button>
</div> </div>
</label> </label>
{isOpen && ( {isOpen && (
<div className="input-container"> <div className="input-container">
<Input value={value} key={themeName+map} onChange={onChange}/> <Input value={value} key={themeName + map} onChange={onChange} />
</div> </div>
)} )}
</div> </div>
); );
} };
export default Value; export default Value;

View file

@ -1,11 +1,10 @@
import React, { useEffect, useState } from 'react'; import React from 'react';
import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter'; import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter';
import { Unstyled } from '@storybook/blocks';
import VariableTable from '../CSSVariables/VariableTable'; import VariableTable from '../CSSVariables/VariableTable';
// @ts-ignore scss
import classes from './styles.module.scss'; import classes from './styles.module.scss';
import darkTheme from '../../../../components/Theme/themes/dark'; import darkTheme from '../../../../components/Theme/themes/dark';
import { docco } from 'react-syntax-highlighter/dist/esm/styles/hljs';
import lightTheme from '../../../../components/Theme/themes/light'; import lightTheme from '../../../../components/Theme/themes/light';
import prism from 'react-syntax-highlighter/dist/esm/styles/prism/prism'; import prism from 'react-syntax-highlighter/dist/esm/styles/prism/prism';
import scss from 'react-syntax-highlighter/dist/esm/languages/prism/scss'; import scss from 'react-syntax-highlighter/dist/esm/languages/prism/scss';
@ -23,27 +22,34 @@ const NewTheme = ({ theme, themeName }) => {
return ( return (
<div className={classes.newtheme}> <div className={classes.newtheme}>
<p>Use the code below to adapt the <code>Theme</code> component for your new design:</p> <p>
<SyntaxHighlighter language="svelte" style={prism}> Use the code below to adapt the <code>Theme</code> component for your
{`<Theme new design:
</p>
<SyntaxHighlighter language="svelte" style={prism}>
{`<Theme
base="${themeName}" base="${themeName}"
theme={${JSON.stringify(updates, null, 2).replaceAll('"', '\'')}} theme={${JSON.stringify(updates, null, 2).replaceAll('"', "'")}}
> >
<!-- ... --> <!-- ... -->
</Theme> </Theme>
`} `}
</SyntaxHighlighter> </SyntaxHighlighter>
{bgChanged && ( {bgChanged && (
<SyntaxHighlighter language="scss" style={prism} customStyle={{ maxHeight: '140px' }}> <SyntaxHighlighter
{`// global.scss language="scss"
style={prism}
customStyle={{ maxHeight: '140px' }}
>
{`// global.scss
body { body {
background-color: ${theme.colour.background}; background-color: ${theme.colour.background};
}`} }`}
</SyntaxHighlighter> </SyntaxHighlighter>
)} )}
<VariableTable theme={theme} /> <VariableTable theme={theme} />
</div> </div>
); );
} };
export default NewTheme; export default NewTheme;

View file

@ -4,12 +4,13 @@ import Customiser from './Customiser/Customiser';
import NewTheme from './NewTheme/NewTheme.jsx'; import NewTheme from './NewTheme/NewTheme.jsx';
import ThemeSwitch from './ThemeSwitch/Switch'; import ThemeSwitch from './ThemeSwitch/Switch';
import { Unstyled } from '@storybook/blocks'; import { Unstyled } from '@storybook/blocks';
// @ts-ignore scss
import classes from './styles.module.scss'; import classes from './styles.module.scss';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import darkTheme from '../../../components/Theme/themes/dark'; import darkTheme from '../../../components/Theme/themes/dark';
import lightTheme from '../../../components/Theme/themes/light'; import lightTheme from '../../../components/Theme/themes/light';
const ThemeBuilder = (props) => { const ThemeBuilder = (_props) => {
const [themeName, setThemeName] = useState('light'); const [themeName, setThemeName] = useState('light');
const [theme, setTheme] = useState(cloneDeep(lightTheme)); const [theme, setTheme] = useState(cloneDeep(lightTheme));
@ -23,7 +24,12 @@ const ThemeBuilder = (props) => {
<div className={classes.themebuilder}> <div className={classes.themebuilder}>
<div className="column"> <div className="column">
<ThemeSwitch setThemeName={setThemeName} themeName={themeName} /> <ThemeSwitch setThemeName={setThemeName} themeName={themeName} />
<Customiser theme={theme} setTheme={setTheme} themeName={themeName} key={themeName} /> <Customiser
theme={theme}
setTheme={setTheme}
themeName={themeName}
key={themeName}
/>
</div> </div>
<div className="column"> <div className="column">
<NewTheme theme={theme} themeName={themeName} /> <NewTheme theme={theme} themeName={themeName} />
@ -31,6 +37,6 @@ const ThemeBuilder = (props) => {
</div> </div>
</Unstyled> </Unstyled>
); );
} };
export default ThemeBuilder; export default ThemeBuilder;

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'; import React from 'react';
import { Unstyled } from '@storybook/blocks'; // @ts-ignore scss
import classes from './styles.module.scss'; import classes from './styles.module.scss';
const ThemeSwitch = ({ themeName, setThemeName }) => { const ThemeSwitch = ({ themeName, setThemeName }) => {
@ -11,14 +11,18 @@ const ThemeSwitch = ({ themeName, setThemeName }) => {
<button <button
className={themeName === 'light' ? 'active' : ''} className={themeName === 'light' ? 'active' : ''}
onClick={() => setThemeName('light')} onClick={() => setThemeName('light')}
><span className="material-symbols-outlined">light_mode</span></button> >
<span className="material-symbols-outlined">light_mode</span>
</button>
<button <button
className={themeName === 'dark' ? 'active' : ''} className={themeName === 'dark' ? 'active' : ''}
onClick={() => setThemeName('dark')} onClick={() => setThemeName('dark')}
><span className="material-symbols-outlined">dark_mode</span></button> >
<span className="material-symbols-outlined">dark_mode</span>
</button>
</div> </div>
</div> </div>
); );
} };
export default ThemeSwitch; export default ThemeSwitch;

View file

@ -16,8 +16,8 @@ export const cssStringToTableArray = (cssString, withInclude = false) => {
)};`; )};`;
}) })
.join('\n'); .join('\n');
return withInclude return withInclude ?
? [className, className, properties] [className, className, properties]
: [className, properties]; : [className, properties];
}); });
}; };
@ -33,7 +33,7 @@ export const scssVariablesToTableArray = (scssString) => {
export const extractCssColourVariables = (cssString) => { export const extractCssColourVariables = (cssString) => {
const variableRegexp = /(--[a-zA-Z][a-zA-Z0-9-]+):\s*(.+);/g; const variableRegexp = /(--[a-zA-Z][a-zA-Z0-9-]+):\s*(.+);/g;
const cssVariables = [...cssString.matchAll(variableRegexp)].map( const cssVariables = [...cssString.matchAll(variableRegexp)].map(
([all, g1, g2]) => [g2, g1] ([_, g1, g2]) => [g2, g1]
); );
const colours = {}; const colours = {};
for (const variable of cssVariables) { for (const variable of cssVariables) {

12
src/globals.d.ts vendored
View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
interface ChartbeatConfig { interface ChartbeatConfig {
uid?: number; uid?: number;
domain?: string; domain?: string;
@ -9,18 +10,17 @@ interface ChartbeatConfig {
} }
declare global { declare global {
// eslint-disable-next-line no-unused-vars
interface Window { interface Window {
/** Google analytics dataLayer */ /** Google analytics dataLayer */
dataLayer: Record<string, any>, dataLayer: Record<string, any>;
/** Chartbeat config */ /** Chartbeat config */
_sf_async_config: ChartbeatConfig, _sf_async_config: ChartbeatConfig;
/** Chartbeat method */ /** Chartbeat method */
pSUPERFLY: { pSUPERFLY: {
virtualPage: (config: { path: string, title: string }) => void, virtualPage: (config: { path: string; title: string }) => void;
}, };
/** Graphics ads */ /** Graphics ads */
graphicsAdQueue: any[], graphicsAdQueue: any[];
} }
} }

View file

@ -5,7 +5,7 @@ export { default as resizeObserver } from './actions/resizeObserver/index.js';
// Components // Components
export { export {
default as Analytics, default as Analytics,
registerPageview registerPageview,
} from './components/Analytics/Analytics.svelte'; } from './components/Analytics/Analytics.svelte';
export { default as Article } from './components/Article/Article.svelte'; export { default as Article } from './components/Article/Article.svelte';
export { default as AdScripts } from './components/AdSlot/AdScripts.svelte'; export { default as AdScripts } from './components/AdSlot/AdScripts.svelte';
@ -45,11 +45,7 @@ export { default as SiteHeadline } from './components/SiteHeadline/SiteHeadline.
export { default as Spinner } from './components/Spinner/Spinner.svelte'; export { default as Spinner } from './components/Spinner/Spinner.svelte';
export { default as SponsorshipAd } from './components/AdSlot/SponsorshipAd.svelte'; export { default as SponsorshipAd } from './components/AdSlot/SponsorshipAd.svelte';
export { default as Table } from './components/Table/Table.svelte'; export { default as Table } from './components/Table/Table.svelte';
export { export { default as Theme, themes } from './components/Theme/Theme.svelte';
default as Theme,
// @ts-ignore
themes
} from './components/Theme/Theme.svelte';
export { default as ToolsHeader } from './components/ToolsHeader/ToolsHeader.svelte'; export { default as ToolsHeader } from './components/ToolsHeader/ToolsHeader.svelte';
export { default as Video } from './components/Video/Video.svelte'; export { default as Video } from './components/Video/Video.svelte';
export { default as Visible } from './components/Visible/Visible.svelte'; export { default as Visible } from './components/Visible/Visible.svelte';

View file

@ -5,7 +5,7 @@
"declaration": true, "declaration": true,
"isolatedModules": true, "isolatedModules": true,
"esModuleInterop": true, "esModuleInterop": true,
"moduleResolution": "node", "moduleResolution": "Bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,

View file

@ -15,9 +15,7 @@ const config = defineConfig({
$docs: './src/docs', $docs: './src/docs',
}, },
}, },
plugins: [ plugins: [svelte({})],
svelte({}),
],
}); });
export default config; export default config;

10519
yarn.lock

File diff suppressed because it is too large Load diff