reinit new repo

This commit is contained in:
Jon McClure 2022-08-11 15:43:38 +01:00
commit fc28e8bae8
280 changed files with 484202 additions and 0 deletions

52
.eslintrc.js Normal file
View file

@ -0,0 +1,52 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
ignorePatterns: ['node_modules', 'docs/**'],
extends: ['standard'],
plugins: ['svelte3', '@typescript-eslint'],
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
extraFileExtensions: ['.svelte'],
},
env: {
browser: true,
es2022: true,
},
settings: {
'svelte3/ignore-styles': () => true,
'svelte3/typescript': require('typescript'),
},
rules: {
indent: ['error', 2],
semi: ['error', 'always'],
'comma-dangle': [
'error',
{
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'never',
exports: 'never',
functions: 'never',
},
],
'operator-linebreak': ['error', 'after'],
'space-before-function-paren': ['error', 'never'],
},
overrides: [
{
files: ['*.svelte'],
processor: 'svelte3/svelte3',
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',
indent: ['error', 2],
},
},
],
};

1
.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1 @@
* @hobbes7878 @fcage @MinamiFunakoshiTR

View file

@ -0,0 +1,7 @@
---
title: Build error on commit
assignees: hobbes7878, MinamiFunakoshiTR
labels: bug
---
A commit caused docs to fail to build: {{ sha }}

17
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,17 @@
### What's in this pull request
- [ ] Bug fix
- [ ] New component/feature
- [ ] Documentation update
- [ ] Other
### Description
Tell us what this PR does or link to any related issues that describe the goal here.
### Before submitting, please check that you've
- [ ] Read our [contributing guide](https://github.com/reuters-graphics/graphics-svelte-components/blob/master/CONTRIBUTING.md) at some point
- [ ] Formatted you code correctly (i.e., prettier cleaned it up)
- [ ] Documented any new components or features
- [ ] Tagged an editor to review

41
.github/docs.yaml vendored Normal file
View file

@ -0,0 +1,41 @@
name: Build docs site
permissions:
contents: write
issues: write
on:
push:
branches:
- master
jobs:
build-app:
name: Build site
runs-on: ubuntu-latest
env:
TESTING: true
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16.7.0'
cache: 'yarn'
- name: Install dependencies
run: yarn install
- name: Config git
run: |
git config user.name github-actions
git config user.email github-actions@github.com
- name: Build docs
run: yarn build:docs
- name: Create issue on fail
uses: JasonEtco/create-an-issue@v2
if: ${{ failure() }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
filename: .github/COMMIT_ERROR_ISSUE_TEMPLATE.md
- name: Commit docs
if: ${{ success() }}
run: |
git add .
git commit -m "build docs"
git push origin

182
.gitignore vendored Normal file
View file

@ -0,0 +1,182 @@
# Created by https://www.toptal.com/developers/gitignore/api/node,macos,linux
# Edit at https://www.toptal.com/developers/gitignore?templates=node,macos,linux
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env*.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Storybook build outputs
.out
.storybook-out
storybook-static
# rollup.js default build output
dist/
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# Temporary folders
tmp/
temp/
# End of https://www.toptal.com/developers/gitignore/api/node,macos,linux
.svelte/
.svelte-kit/
dist/
graphics-pack/
!dist/package.json
project-files

5
.markdownlint.jsonc Normal file
View file

@ -0,0 +1,5 @@
{
"MD013": false,
"MD033": false,
"MD041": false
}

1
.npmrc Normal file
View file

@ -0,0 +1 @@
engine-strict=true

15
.prettierrc Normal file
View file

@ -0,0 +1,15 @@
{
"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
}

View file

@ -0,0 +1,4 @@
<!-- Wrapper around all stories -->
<article class="container-fluid">
<slot />
</article>

9
.storybook/Theme.js Normal file
View file

@ -0,0 +1,9 @@
import { create } from '@storybook/theming';
export default create({
base: 'light',
brandTitle: 'Graphics svelte components',
brandUrl: 'https://reuters-graphics.github.io/graphics-svelte-components/',
brandImage: 'https://graphics.thomsonreuters.com/style-assets/images/logos/reuters-graphics-logo/svg/graphics-logo-color-dark.svg',
brandTarget: '_self',
});

41
.storybook/main.cjs Normal file
View file

@ -0,0 +1,41 @@
const { mergeConfig } = require('vite');
const preprocess = require('../bin/preprocess/index.cjs');
module.exports = {
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.svelte"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
],
"framework": "@storybook/svelte",
"core": {
"builder": "@storybook/builder-vite"
},
"features": {
"storyStoreV7": false,
"previewMdx2": true,
},
async viteFinal(config, { configType }) {
return mergeConfig(config, {
base: configType === 'PRODUCTION' ? 'https://reuters-graphics.github.io/graphics-svelte-components/' : '/',
css: {
preprocessorOptions: { scss: preprocess.scss },
},
resolve: {
alias: {
'@reuters-graphics/svelte-charts': './src',
'$lib': './src',
'$docs': './src/docs',
},
},
});
},
svelteOptions: {
preprocess: preprocess.sveltePreprocess,
},
}

26
.storybook/manager.js Normal file
View file

@ -0,0 +1,26 @@
import { addons } from '@storybook/addons';
import theme from './Theme';
addons.setConfig({
isFullscreen: false,
showNav: true,
showPanel: true,
panelPosition: 'bottom',
enableShortcuts: true,
showToolbar: true,
theme: undefined,
selectedPanel: undefined,
initialActive: 'sidebar',
sidebar: {
showRoots: false,
collapsedRoots: ['other'],
},
toolbar: {
title: { hidden: false },
zoom: { hidden: false },
eject: { hidden: false },
copy: { hidden: false },
fullscreen: { hidden: false },
},
theme,
});

View file

@ -0,0 +1,3 @@
<script>
window.global = window;
</script>

58
.storybook/preview.js Normal file
View file

@ -0,0 +1,58 @@
import '../src/scss/main.scss';
import './preview.scss';
import Article from '../src/components/Article/Article.svelte';
import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter';
import scss from 'react-syntax-highlighter/dist/esm/languages/prism/scss';
import svelte from './svelte-highlighting.js';
SyntaxHighlighter.registerLanguage('scss', scss);
SyntaxHighlighter.registerLanguage('svelte', svelte);
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
viewMode: 'docs',
previewTabs: { 'storybook/docs/panel': { index: -1 } },
controls: {
expanded: true,
sort: 'requiredFirst',
matchers: {
color: /(background|colour|Colour)$/i,
date: /Date$/,
},
},
layout: 'fullscreen',
options: {
// https://storybook.js.org/docs/svelte/writing-stories/naming-components-and-hierarchy#sorting-stories
storySort: {
includeNames: true,
order: [
'Intro',
'Guides',
[
'Using these docs',
'Using with the Graphics Kit',
'Using with Google docs',
'Customising components with SCSS',
'*',
],
'*',
'Actions',
['*'],
'Utilities',
['*'],
'Contributing',
[
'Quickstart', 'Component Basics', '*', 'Writing Stories',
'Recipes: Basic story',
'Recipes: Story with custom docs',
'Recipes: Story with custom controls',
'Recipes: Story with media',
'Recipes: Story for a component with slots'
],
],
},
}
};
export const decorators = [() => ({ Component: Article })];

29
.storybook/preview.scss Normal file
View file

@ -0,0 +1,29 @@
table.docblock-argstable {
p {
font-family: inherit;
font-size: inherit;
}
}
div.sbdocs-content {
& > h2, & > div > div > h2 {
margin-top: 4rem;
margin-bottom: 2rem;
&:first-of-type {
margin-top: 4rem;
margin-bottom: 2rem;
}
}
.docblock-source {
margin: 1rem 0;
}
& > blockquote, & > div > div > blockquote, blockquote.sbdocs {
background-color: #ededed;
padding: 15px 20px;
border: 1px solid #ccc;
border-radius: 4px;
}
}

View file

@ -0,0 +1,147 @@
svelte.displayName = 'svelte'
svelte.aliases = []
export default function svelte(Prism) {
const blocks = '(if|else if|await|then|catch|each|html|debug)';
Prism.languages.svelte = Prism.languages.extend('markup', {
each: {
pattern: new RegExp(
'{[#/]each' +
'(?:(?:\\{(?:(?:\\{(?:[^{}])*\\})|(?:[^{}]))*\\})|(?:[^{}]))*}'
),
inside: {
'language-javascript': [
{
pattern: /(as[\s\S]*)\([\s\S]*\)(?=\s*\})/,
lookbehind: true,
inside: Prism.languages['javascript'],
},
{
pattern: /(as[\s]*)[\s\S]*(?=\s*)/,
lookbehind: true,
inside: Prism.languages['javascript'],
},
{
pattern: /(#each[\s]*)[\s\S]*(?=as)/,
lookbehind: true,
inside: Prism.languages['javascript'],
},
],
keyword: /[#/]each|as/,
punctuation: /{|}/,
},
},
block: {
pattern: new RegExp(
'{[#:/@]/s' +
blocks +
'(?:(?:\\{(?:(?:\\{(?:[^{}])*\\})|(?:[^{}]))*\\})|(?:[^{}]))*}'
),
inside: {
punctuation: /^{|}$/,
keyword: [new RegExp('[#:/@]' + blocks + '( )*'), /as/, /then/],
'language-javascript': {
pattern: /[\s\S]*/,
inside: Prism.languages['javascript'],
},
},
},
tag: {
pattern: /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?:"[^"]*"|'[^']*'|{[\s\S]+?}(?=[\s/>])))|(?=[\s/>])))+)?\s*\/?>/i,
greedy: true,
inside: {
tag: {
pattern: /^<\/?[^\s>\/]+/i,
inside: {
punctuation: /^<\/?/,
namespace: /^[^\s>\/:]+:/,
},
},
'language-javascript': {
pattern: /\{(?:(?:\{(?:(?:\{(?:[^{}])*\})|(?:[^{}]))*\})|(?:[^{}]))*\}/,
inside: Prism.languages['javascript'],
},
'attr-value': {
pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/i,
inside: {
punctuation: [
/^=/,
{
pattern: /^(\s*)["']|["']$/,
lookbehind: true,
},
],
'language-javascript': {
pattern: /{[\s\S]+}/,
inside: Prism.languages['javascript'],
},
},
},
punctuation: /\/?>/,
'attr-name': {
pattern: /[^\s>\/]+/,
inside: {
namespace: /^[^\s>\/:]+:/,
},
},
},
},
'language-javascript': {
pattern: /\{(?:(?:\{(?:(?:\{(?:[^{}])*\})|(?:[^{}]))*\})|(?:[^{}]))*\}/,
lookbehind: true,
inside: Prism.languages['javascript'],
},
});
Prism.languages.svelte['tag'].inside['attr-value'].inside['entity'] =
Prism.languages.svelte['entity'];
Prism.hooks.add('wrap', env => {
if (env.type === 'entity') {
env.attributes['title'] = env.content.replace(/&amp;/, '&');
}
});
Object.defineProperty(Prism.languages.svelte.tag, 'addInlined', {
value: function addInlined(tagName, lang) {
const includedCdataInside = {};
includedCdataInside['language-' + lang] = {
pattern: /(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,
lookbehind: true,
inside: Prism.languages[lang],
};
includedCdataInside['cdata'] = /^<!\[CDATA\[|\]\]>$/i;
const inside = {
'included-cdata': {
pattern: /<!\[CDATA\[[\s\S]*?\]\]>/i,
inside: includedCdataInside,
},
};
inside['language-' + lang] = {
pattern: /[\s\S]+/,
inside: Prism.languages[lang],
};
const def = {};
def[tagName] = {
pattern: RegExp(
/(<__[\s\S]*?>)(?:<!\[CDATA\[[\s\S]*?\]\]>\s*|[\s\S])*?(?=<\/__>)/.source.replace(
/__/g,
tagName
),
'i'
),
lookbehind: true,
greedy: true,
inside,
};
Prism.languages.insertBefore('svelte', 'cdata', def);
},
});
Prism.languages.svelte.tag.addInlined('style', 'css');
Prism.languages.svelte.tag.addInlined('script', 'javascript');
}

3
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"recommendations": ["silvenon.mdx"]
}

20
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,20 @@
{
"i18n-ally.localesPaths": [
"locales"
],
"i18n-ally.keystyle": "nested",
"eslint.validate": [
"javascript",
"svelte"
],
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"files.associations": {
"*.svx": "mdx"
},
"[mdx]": {
"editor.wordWrap": "on"
}
}

43
.vscode/svelte.scripts.code-snippets vendored Normal file
View file

@ -0,0 +1,43 @@
{
"Static assets path": {
"scope": "javascript",
"prefix": "static",
"body": [
"import { getPath } from '$utils/statics';",
"",
"getPath('${1:filepath}')"
],
"description": "Add a static assets file path"
},
"SvelteKit $app/env": {
"scope": "javascript",
"prefix": "env",
"body": [
"import { browser, dev, prerendering } from '$app/env';"
],
"description": "SvelteKit $app/env stores"
},
"Fetch script module": {
"scope": "svelte",
"prefix": "fetch-module",
"body": [
"<script context=\"module\">",
" export async function load({ page, fetch, session, context }) {",
" const url = 'https://$1';",
" const res = await fetch(url);",
" if (res.ok) {",
" return {",
" props: {",
" data: await res.json(),",
" },",
" };",
" }",
" return {",
" status: res.status,",
" error: new Error('Could not fetch!'),",
" };",
" }",
"</script>"
]
}
}

49
.vscode/svelte.styles.code-snippets vendored Normal file
View file

@ -0,0 +1,49 @@
{
"Svelte SCSS style": {
"scope": "svelte",
"prefix": "scss",
"body": [
"<style lang=\"scss\">",
"$1",
"</style>"
],
"description": "Add a Svelte SCSS style tag"
},
"Reuters Graphics styles theme": {
"scope": "svelte",
"prefix": "theme",
"body": [
"<style lang=\"scss\">",
" @import '~@reuters-graphics/style-main/scss/fonts/font-faces';",
" :global {",
" @import '@reuters-graphics/style-theme-eisbaer/scss/main';",
" }",
"</style>"
],
"description": "Add Reuters Graphics theme to page"
},
"Reuters Graphics fonts mixins": {
"scope": "scss",
"prefix": "fonts-mixins",
"body": [
"@import \"~@reuters-graphics/style-main/scss/fonts/mixins\";",
"",
"$1 {",
" @include font-sans;",
"}"
],
"description": "Add Reuters Graphics fonts mixins"
},
"Reuters Graphics fonts variables": {
"scope": "scss",
"prefix": "fonts-variables",
"body": [
"@import \"~@reuters-graphics/style-main/scss/fonts/variables\";",
"",
"$1 {",
" font-family: $font-family-sans-serif;",
"}"
],
"description": "Add Reuters Graphics fonts variables"
}
}

50
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,50 @@
![](https://graphics.thomsonreuters.com/style-assets/images/logos/reuters-graphics-logo/svg/graphics-logo-color-dark.svg)
# Contributing Guide
## Why this?
Most Reuters Graphics repos don't include (or need) contributing guidelines. The ones that do represent critical infrastructure. They also are the ones we most want wider contributions from the team to improve and maintain.
This doc provides for a few simple guidelines to make sure changes are well considered and represent the best ideas for how to move our tools forward while opening up the opportunity for others to ship their next great idea.
## Who can contribute?
Contributions are always welcome from members of the Reuters Graphics team.
Anyone outside our team using these components is welcome to submit PRs or issues, **BUT** if they are designed solely to benefit a use case that isn't ours, they likely won't be merged.
## How should Reuters Graphics staff contribute?
### 🏷️ Make an issue
We recommend your first step is to create an issue on this repo describing what is missing, broken or could be added or improved. (We'll close that issue when we merge your PR.)
- It's helpful if that issue describes what changes you propose to make at a high level so we can agree on a general direction before you write code. That's especially true if code you're writing will change how others need to write theirs.
- If needed, provide any links to best practice guidelines that support the change you want to make.
- Tag others on the team who may have expertise or would contribute to any needed discussion.
- **Always tag an editor.**
### 🧹 Follow code standards
Once you're ready to submit code, be sure it's properly formatted _before_ you ask for a review. The easiest way is to ensure the built-in code formatter (prettier) is working. (It should.)
Be sure to add comments around any tricky bits of logic you're adding. Even better, document your code using [JSDoc](https://devhints.io/jsdoc) comments. (Check out [JSDoc shortcuts](https://code.visualstudio.com/docs/languages/javascript#_jsdoc-support) in VS Code for a leg-up.)
### 📝 Write docs
All new components and component features should be reflected in a [docs page](https://reuters-graphics.github.io/graphics-svelte-components/) included with your PR. See the [README](https://github.com/reuters-graphics/graphics-svelte-components#developing-new-components) for instructions on how to add those docs.
### 🍺 Submit code
All code contributions should be made through the normal [GitHub Flow](https://www.w3schools.com/git/git_github_flow.asp#:~:text=The%20GitHub%20flow%20is%20a,Make%20changes%20and%20add%20Commits). Basically, make a branch and submit a pull request.
Generally, it's better to avoid bundling several new features or components in a single PR. Breaking them apart into smaller, individual contributions makes them easier to review and manage.
Once you've submitted your PR, tag an editor to review it.
An editor will approve your PR after addressing any issues they see. Once an editor approves and there are no code conflicts, you can merge your PR into master.
### ✉️ Publishing to the team
For now, only editors should publish new versions of the library to npm. We'll follow [semantic versioning](https://semver.org/) conventions. Most MINOR and all MAJOR version changes should be identified ahead of time during PR review.

5
README.md Normal file
View file

@ -0,0 +1,5 @@
![](https://graphics.thomsonreuters.com/style-assets/images/logos/reuters-graphics-logo/svg/graphics-logo-color-dark.svg)
# ⚙️ graphics-components
Home of the next generation.

3
assetinfo.json Normal file
View file

@ -0,0 +1,3 @@
{
"assetid": ["205245"]
}

79
bin/buildPackage/index.js Normal file
View file

@ -0,0 +1,79 @@
import { DIST, LIB, PACKAGE, TYPES } from './locations.js';
import { createRequire } from 'module';
import { emitDts } from 'svelte2tsx';
import fs from 'fs-extra';
import glob from 'tiny-glob';
import path from 'path';
import picomatch from 'picomatch';
import processOther from './process/other.js';
import processSvelte from './process/svelte.js';
import processTypescript from './process/typescript.js';
import rimraf from 'rimraf';
const require = createRequire(import.meta.url);
const excludePatterns = [
'**/stories/**/*',
'**/docs/**/*',
'**/statics/**/*',
'**/*.exclude.*',
'**/*.stories.svelte',
'**/*.stories.svelte.d.ts',
];
const excludedTypeDefs = [
'**/stories/**/*',
'**/docs/**/*',
'**/*.stories.svelte.d.ts',
];
/**
* This is a basic port of sveltekit's own packaging method:
* https://github.com/sveltejs/kit/tree/master/packages/kit/src/packaging
*/
const build = async () => {
console.log('📦 Building your package');
if (fs.existsSync(DIST)) rimraf.sync(DIST);
// Extract types
await emitDts({
libRoot: LIB,
svelteShimsPath: require.resolve('svelte2tsx/svelte-shims.d.ts'),
declarationDir: TYPES,
});
// Cleanup unwanted types
fs.rmSync(path.join(TYPES, 'docs'), { recursive: true, force: true });
const types = await glob('**/*', { cwd: TYPES, filesOnly: true });
for (const t of types) {
if(picomatch.isMatch(t, excludedTypeDefs)) fs.unlinkSync(path.join(TYPES, t));
}
const pkgExports = {
'./package.json': './package.json'
};
const files = await glob('**/*.{js,json,ts,svelte,css,scss}', { cwd: LIB, filesOnly: true });
for (const file of files) {
if(picomatch.isMatch(file, excludePatterns)) continue;
if (file.endsWith('.svelte')) {
await processSvelte(file);
} else if(file.endsWith('.ts') && !file.endsWith('.d.ts')) {
await processTypescript(file);
} else {
await processOther(file);
}
if (file === 'index.js') continue; // Always add root index last to exports...
pkgExports[`./${file.replace(/\/index\.js$|(\/[^/]+)\.js$/, '$1')}`] = `./dist/${file}`;
}
pkgExports['.'] = './dist/index.js';
const pkg = fs.readJSONSync(PACKAGE);
pkg.type = 'module';
pkg.files = ['dist'];
pkg.private = false;
pkg.exports = pkgExports;
fs.writeFileSync(PACKAGE, JSON.stringify(pkg, null, 2));
}
build();

View file

@ -0,0 +1,8 @@
import path from 'path';
const __dirname = new URL('.', import.meta.url).pathname;
export const ROOT = path.resolve(__dirname, '../../');
export const PACKAGE = path.join(ROOT, 'package.json');
export const LIB = path.join(ROOT, 'src');
export const DIST = path.join(ROOT, 'dist');
export const TYPES = path.join(DIST, '@types');

View file

@ -0,0 +1,11 @@
import { DIST, LIB } from './../locations.js';
import fs from 'fs-extra';
import path from 'path';
export default async (file) => {
const filename = path.join(LIB, file);
const writePath = path.join(DIST, file);
fs.ensureDirSync(path.dirname(writePath));
fs.copyFileSync(filename, writePath);
}

View file

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

View file

@ -0,0 +1,22 @@
import { DIST, LIB, ROOT } from './../locations.js';
import fs from 'fs-extra';
import path from 'path';
import ts from 'typescript';
async function transpileTypeScript(filename, source) {
const { compilerOptions } = fs.readJSONSync(path.join(ROOT, 'tsconfig.json'));
return ts.transpileModule(source, {
compilerOptions,
fileName: filename
}).outputText;
}
export default async (file) => {
const filename = path.join(LIB, file);
let source = fs.readFileSync(filename, 'utf8');
source = await transpileTypeScript(filename, source);
const writePath = path.join(DIST, file).replace(/\.ts$/, '.js');
fs.ensureDirSync(path.dirname(writePath));
fs.writeFileSync(writePath, source);
}

View file

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

View file

@ -0,0 +1,43 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// Don't lose the "?raw" in markdown imports!
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
import YourComponent from './YourComponent.svelte';
import { withComponentDocs } from '$docs/utils/withParams.js';
// 🖼️ You can import images you need in stories directly in code!
// @ts-ignore
import SharkImg from './stories/shark.jpg';
const meta = {
title: 'Components/YourComponent',
component: YourComponent,
...withComponentDocs(componentDocs),
// https://storybook.js.org/docs/svelte/essentials/controls
argTypes: {
width: {
control: 'select',
options: ['normal', 'wide', 'wider', 'widest', 'fluid'],
},
},
};
</script>
<Meta {...meta} />
<Template let:args>
<YourComponent {...args} />
</Template>
<Story
name="Basic"
args="{{
width: 'normal',
src: SharkImg,
altText: 'Duh dum! It\'s a shark!!',
}}"
/>

View file

@ -0,0 +1,51 @@
<script lang="ts">
/** ✏️ DOCUMENT your chart's props using TypeScript and JSDoc comments like below! */
/**
* A source for the image.
* @required
*/
export let src: string;
/**
* AltText for the image.
* @required
*/
export let altText: string;
/** Height of the image. */
export let height: number = 500;
// You can declare custom types to help users implement your component.
type ContainerWidth = 'normal' | 'wide' | 'wider' | 'widest' | 'fluid';
/** Width of the component within the text well. */
export let width: ContainerWidth = 'normal';
</script>
<section class="photo {width}">
<div
style:background-image={`url(${src})`}
style:height={`${height}px`}
/>
<p class="visually-hidden">{altText}</p>
</section>
<style lang="scss">
section.photo {
div {
width: 100%;
background-repeat: no-repeat;
background-size: cover;
}
}
.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
</style>

View file

@ -0,0 +1,23 @@
> **Welcome to your new component!** Use this template to build your component and customise its storybook.
- Build your component in `YourComponent/YourComponent.svelte`.
- Write your component's storybook in `YourComponent/YourComponent.stories.svelte`.
- Don't forget to add your component to `src/index.js`:
```javascript
// ...
export { default as YourComponent } from './components/YourComponent/YourComponent.svelte';
```
- Commit your component to a new branch and push it to GitHub! 🏁
---
```svelte
<script>
import { YourComponent } from '@reuters-graphics/graphics-svelte-components';
</script>
<YourComponent />
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

29
bin/preprocess/index.cjs Normal file
View file

@ -0,0 +1,29 @@
const autoprefixer = require('autoprefixer');
const preprocess = require('svelte-preprocess');
const scss = {
includePaths: ['src/', 'node_modules/bootstrap/scss/'],
importer: [
(url) => {
if (/^\$lib/.test(url)){ return { file: `src/${url.replace('$lib', '')}` }; }
// Redirect tilde-prefixed imports to node_modules
if (/^~/.test(url)) { return { file: `node_modules/${url.replace('~', '')}` }; }
return null;
},
],
quietDeps: true,
};
const sveltePreprocess = preprocess({
preserve: ['ld+json'],
typescript: true,
scss,
postcss: {
plugins: [autoprefixer],
},
});
module.exports = {
scss,
sveltePreprocess
}

8
lefthook.yml Normal file
View file

@ -0,0 +1,8 @@
pre-commit:
commands:
eslint:
glob: 'src/**/*.js'
run: npx eslint --cache --fix {staged_files}
prettier:
glob: 'src/**/*.{js,scss,md,svelte}'
run: npx prettier --write {staged_files}

162
package.json Normal file
View file

@ -0,0 +1,162 @@
{
"name": "@reuters-graphics/graphics-svelte-components",
"version": "0.3.32",
"type": "module",
"private": false,
"homepage": "https://reuters-graphics.github.io/graphics-svelte-components",
"repository": "https://github.com/reuters-graphics/graphics-svelte-components",
"scripts": {
"start": "start-storybook -p 3000",
"new": "node ./bin/newComponent/index.cjs",
"build:package": "node ./bin/buildPackage/index.js",
"build:docs": "build-storybook -o docs"
},
"license": "MIT",
"types": "dist/@types/index.d.ts",
"files": [
"dist"
],
"engines": {
"node": ">=16.7"
},
"devDependencies": {
"@babel/core": "^7.18.9",
"@babel/eslint-parser": "^7.15.4",
"@babel/eslint-plugin": "^7.14.5",
"@babel/preset-env": "^7.15.6",
"@evilmartians/lefthook": "^1.0.1",
"@reuters-graphics/eslint-config": "^0.0.2",
"@storybook/addon-actions": "6.5.9",
"@storybook/addon-essentials": "6.5.9",
"@storybook/addon-interactions": "6.5.9",
"@storybook/addon-links": "6.5.9",
"@storybook/addon-svelte-csf": "^2.0.6",
"@storybook/builder-vite": "^0.2.1",
"@storybook/mdx2-csf": "^0.0.3",
"@storybook/svelte": "6.5.9",
"@storybook/testing-library": "^0.0.13",
"@storybook/theming": "6.5.9",
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"@tsconfig/svelte": "^3.0.0",
"@types/react-syntax-highlighter": "^15.5.4",
"@typescript-eslint/parser": "^5.32.0",
"autoprefixer": "^10.2.5",
"babel-loader": "^8.2.5",
"change-case": "^4.1.2",
"eslint": "^8.16.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-storybook": "^0.6.1",
"eslint-plugin-svelte3": "^4.0.0",
"fs-extra": "^10.1.0",
"kleur": "^4.1.5",
"postcss": "^8.4.14",
"postcss-load-config": "^4.0.1",
"prettier": "^2.3.2",
"prettier-plugin-svelte": "^2.4.0",
"prompts": "^2.4.2",
"react-syntax-highlighter": "^15.5.0",
"rimraf": "^3.0.2",
"sass": "^1.54.0",
"svelte": "^3.49.0",
"svelte-loader": "^3.1.3",
"svelte-preprocess": "^4.10.7",
"svelte2tsx": "^0.5.13",
"tiny-glob": "^0.2.9",
"typescript": "^4.7.4",
"vite": "^3.0.4"
},
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-regular-svg-icons": "^5.15.3",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@reuters-graphics/style-theme-eisbaer": "^0.3.5",
"@sveltejs/svelte-scroller": "^2.0.7",
"bootstrap": "^5.2.0",
"classnames": "^2.3.1",
"dayjs": "^1.11.3",
"lodash-es": "^4.17.21",
"lottie-web": "^5.7.13",
"marked": "^4.0.8",
"proper-url-join": "^2.1.1",
"pym.js": "^1.3.2",
"svelte-fa": "^2.4.0",
"svelte-intersection-observer": "^0.10.0",
"ua-parser-js": "^0.7.27"
},
"exports": {
"./package.json": "./package.json",
"./components/Ai2svelte/index.svelte": "./dist/components/Ai2svelte/index.svelte",
"./components/BeforeAfter/BeforeAfter.svelte": "./dist/components/BeforeAfter/BeforeAfter.svelte",
"./components/BodyText/BodyText.svelte": "./dist/components/BodyText/BodyText.svelte",
"./components/DatawrapperChart/DatawrapperChart.svelte": "./dist/components/DatawrapperChart/DatawrapperChart.svelte",
"./components/EmbedPreviewerLink/EmbedPreviewerLink.svelte": "./dist/components/EmbedPreviewerLink/EmbedPreviewerLink.svelte",
"./components/EndNotes/EndNotes.svelte": "./dist/components/EndNotes/EndNotes.svelte",
"./components/FeaturePhoto/FeaturePhoto.svelte": "./dist/components/FeaturePhoto/FeaturePhoto.svelte",
"./components/Framer/Framer.svelte": "./dist/components/Framer/Framer.svelte",
"./components/Framer/Resizer/index.svelte": "./dist/components/Framer/Resizer/index.svelte",
"./components/Framer/stores": "./dist/components/Framer/stores.js",
"./components/Framer/uniqNames": "./dist/components/Framer/uniqNames.js",
"./components/Headline/Headline.svelte": "./dist/components/Headline/Headline.svelte",
"./components/Hero/Hero.svelte": "./dist/components/Hero/Hero.svelte",
"./components/PymChild/PymChild.svelte": "./dist/components/PymChild/PymChild.svelte",
"./components/ReutersLogo/ReutersLogo.svelte": "./dist/components/ReutersLogo/ReutersLogo.svelte",
"./components/SEO/analytics": "./dist/components/SEO/analytics.js",
"./components/SEO/index.svelte": "./dist/components/SEO/index.svelte",
"./components/SEO/publisherTags": "./dist/components/SEO/publisherTags.js",
"./components/Scroller/Background.svelte": "./dist/components/Scroller/Background.svelte",
"./components/Scroller/Embedded/Background.svelte": "./dist/components/Scroller/Embedded/Background.svelte",
"./components/Scroller/Embedded/Foreground.svelte": "./dist/components/Scroller/Embedded/Foreground.svelte",
"./components/Scroller/Embedded/index.svelte": "./dist/components/Scroller/Embedded/index.svelte",
"./components/Scroller/Foreground.svelte": "./dist/components/Scroller/Foreground.svelte",
"./components/Scroller/demos/basic/_Clicker.svelte": "./dist/components/Scroller/demos/basic/_Clicker.svelte",
"./components/Scroller/demos/basic/_Stacked.svelte": "./dist/components/Scroller/demos/basic/_Stacked.svelte",
"./components/Scroller/demos/basic/_Step.svelte": "./dist/components/Scroller/demos/basic/_Step.svelte",
"./components/Scroller/index.svelte": "./dist/components/Scroller/index.svelte",
"./components/Sharer/Sharer.svelte": "./dist/components/Sharer/Sharer.svelte",
"./components/Sharer/utils/copyToClipboard": "./dist/components/Sharer/utils/copyToClipboard.js",
"./components/Sharer/utils/facebook": "./dist/components/Sharer/utils/facebook.js",
"./components/Sharer/utils/meta": "./dist/components/Sharer/utils/meta.js",
"./components/Sharer/utils/twitter": "./dist/components/Sharer/utils/twitter.js",
"./components/SiteFooter/CompanyLinks.svelte": "./dist/components/SiteFooter/CompanyLinks.svelte",
"./components/SiteFooter/LegalLinks.svelte": "./dist/components/SiteFooter/LegalLinks.svelte",
"./components/SiteFooter/QuickLinks.svelte": "./dist/components/SiteFooter/QuickLinks.svelte",
"./components/SiteFooter/Referrals/IntersectionObserver.svelte": "./dist/components/SiteFooter/Referrals/IntersectionObserver.svelte",
"./components/SiteFooter/Referrals/Link.svelte": "./dist/components/SiteFooter/Referrals/Link.svelte",
"./components/SiteFooter/Referrals/Referrals.svelte": "./dist/components/SiteFooter/Referrals/Referrals.svelte",
"./components/SiteFooter/Referrals/index.svelte": "./dist/components/SiteFooter/Referrals/index.svelte",
"./components/SiteFooter/SiteFooter.svelte": "./dist/components/SiteFooter/SiteFooter.svelte",
"./components/SiteFooter/data.json": "./dist/components/SiteFooter/data.json",
"./components/SiteFooter/svgs/Facebook.svelte": "./dist/components/SiteFooter/svgs/Facebook.svelte",
"./components/SiteFooter/svgs/Graphics.svelte": "./dist/components/SiteFooter/svgs/Graphics.svelte",
"./components/SiteFooter/svgs/Instagram.svelte": "./dist/components/SiteFooter/svgs/Instagram.svelte",
"./components/SiteFooter/svgs/LinkedIn.svelte": "./dist/components/SiteFooter/svgs/LinkedIn.svelte",
"./components/SiteFooter/svgs/Pictures.svelte": "./dist/components/SiteFooter/svgs/Pictures.svelte",
"./components/SiteFooter/svgs/Twitter.svelte": "./dist/components/SiteFooter/svgs/Twitter.svelte",
"./components/SiteFooter/svgs/Videos.svelte": "./dist/components/SiteFooter/svgs/Videos.svelte",
"./components/SiteFooter/svgs/YouTube.svelte": "./dist/components/SiteFooter/svgs/YouTube.svelte",
"./components/SiteHeader/MobileMenu/index.svelte": "./dist/components/SiteHeader/MobileMenu/index.svelte",
"./components/SiteHeader/NavBar/DownArrow.svelte": "./dist/components/SiteHeader/NavBar/DownArrow.svelte",
"./components/SiteHeader/NavBar/NavDropdown/MoreDropdown.svelte": "./dist/components/SiteHeader/NavBar/NavDropdown/MoreDropdown.svelte",
"./components/SiteHeader/NavBar/NavDropdown/SectionDrowdown.svelte": "./dist/components/SiteHeader/NavBar/NavDropdown/SectionDrowdown.svelte",
"./components/SiteHeader/NavBar/NavDropdown/Spinner/index.svelte": "./dist/components/SiteHeader/NavBar/NavDropdown/Spinner/index.svelte",
"./components/SiteHeader/NavBar/NavDropdown/StoryCard/index.svelte": "./dist/components/SiteHeader/NavBar/NavDropdown/StoryCard/index.svelte",
"./components/SiteHeader/NavBar/NavDropdown/StoryCard/time": "./dist/components/SiteHeader/NavBar/NavDropdown/StoryCard/time.js",
"./components/SiteHeader/NavBar/NavDropdown/index.svelte": "./dist/components/SiteHeader/NavBar/NavDropdown/index.svelte",
"./components/SiteHeader/NavBar/index.svelte": "./dist/components/SiteHeader/NavBar/index.svelte",
"./components/SiteHeader/NavBar/utils": "./dist/components/SiteHeader/NavBar/utils/index.js",
"./components/SiteHeader/SiteHeader.svelte": "./dist/components/SiteHeader/SiteHeader.svelte",
"./components/SiteHeader/data.json": "./dist/components/SiteHeader/data.json",
"./components/SiteHeader/scss/_breakpoints.scss": "./dist/components/SiteHeader/scss/_breakpoints.scss",
"./components/SiteHeader/scss/_colors.scss": "./dist/components/SiteHeader/scss/_colors.scss",
"./components/SiteHeader/scss/_eases.scss": "./dist/components/SiteHeader/scss/_eases.scss",
"./components/SiteHeader/scss/_grids.scss": "./dist/components/SiteHeader/scss/_grids.scss",
"./components/SiteHeader/scss/_z-indexes.scss": "./dist/components/SiteHeader/scss/_z-indexes.scss",
"./components/SiteHeader/svgs/Close.svelte": "./dist/components/SiteHeader/svgs/Close.svelte",
"./components/SiteHeader/svgs/Menu.svelte": "./dist/components/SiteHeader/svgs/Menu.svelte",
"./components/Spinner/Spinner.svelte": "./dist/components/Spinner/Spinner.svelte",
"./components/Video/Controls.svelte": "./dist/components/Video/Controls.svelte",
"./components/Video/index.svelte": "./dist/components/Video/index.svelte",
"./components/Visible/Visible.svelte": "./dist/components/Visible/Visible.svelte",
".": "./dist/index.js"
}
}

View file

@ -0,0 +1,42 @@
import { Meta } from '@storybook/addon-docs';
import { parameters } from '$docs/utils/docsPage.js';
<Meta title="Actions/cssVariables" parameters={{ ...parameters }} />
![](https://graphics.thomsonreuters.com/style-assets/images/logos/reuters-graphics-logo/svg/graphics-logo-color-dark.svg)
# `cssVariables`
A Svelte [action](https://svelte.dev/tutorial/actions) you can use to easily set [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) on HTML elements. Useful for passing JavaScript values to your component SCSS like this:
```svelte
<script>
import { cssVariables } from '@reuters-graphics/graphics-svelte-components';
export let height = 300;
export let textColour = 'red';
// Create an object of variable names and CSS values...
$: variables = {
height: height + 'px',
textColour: textColour,
};
</script>
<!-- Attach it to a parent element with the action -->
<div use:cssVariables="{variables}">
<p>My text...</p>
</div>
<style lang="scss">
/**
* Now use your variables in your SCSS!
*/
div {
height: var(--height);
p {
color: var(--textColour);
}
}
</style>
```

View file

@ -0,0 +1,20 @@
// Shamelessly stolen from: https://github.com/kaisermann/svelte-css-vars
export default (node, props) => {
Object.entries(props).forEach(([key, value]) => {
node.style.setProperty(`--${key}`, value);
});
return {
update(new_props) {
Object.entries(new_props).forEach(([key, value]) => {
node.style.setProperty(`--${key}`, value);
delete props[key];
});
Object.keys(props).forEach(name =>
node.style.removeProperty(`--${name}`),
);
props = new_props;
},
};
};

View file

@ -0,0 +1,25 @@
// Shamelessly stolen from https://github.com/sveltejs/svelte/issues/7583#issue-1260717165
let observer;
let callbacks;
export default (element, onResize) => {
if (!observer) {
callbacks = new WeakMap();
observer = new ResizeObserver(entries => {
for (const entry of entries) {
const onResize = callbacks.get(entry.target);
if (onResize) onResize(entry.target);
}
});
}
callbacks.set(element, onResize);
observer.observe(element);
return {
destroy: () => {
callbacks.delete(element);
observer.unobserve(element);
},
};
}

View file

@ -0,0 +1,601 @@
<script>
import { assets } from '$app/paths';
let width = null;
</script>
<!-- Generated by ai2html v0.100.0 - 2021-09-29 12:37 -->
<div id="g-_ai-chart-box" bind:clientWidth="{width}">
<!-- Artboard: xs -->
{#if width && width >= 0 && width < 510}
<div id="g-_ai-chart-xs" class="g-artboard" style="">
<div style="padding: 0 0 91.7004% 0;"></div>
<div
id="g-_ai-chart-xs-img"
class="g-aiImg"
alt=""
style="{`background-image: url(${assets}/images/graphics/ai-chart-xs.png);`}"
></div>
<div
id="g-ai0-1"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:3.216%;margin-top:-7.7px;left:0.5952%;width:99px;"
>
<p class="g-pstyle0">Shake intensity</p>
</div>
<div
id="g-ai0-2"
class="g-legend g-aiAbs g-aiPointText"
style="top:9.8251%;margin-top:-7.7px;left:4.9821%;width:47px;"
>
<p class="g-pstyle0">Light</p>
</div>
<div
id="g-ai0-3"
class="g-legend g-aiAbs g-aiPointText"
style="top:15.7733%;margin-top:-7.7px;left:4.9821%;width:69px;"
>
<p class="g-pstyle0">Moderate</p>
</div>
<div
id="g-ai0-4"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:16.4343%;margin-top:-7.7px;left:79.0675%;width:82px;"
>
<p class="g-pstyle0">Cap-Haitien</p>
</div>
<div
id="g-ai0-5"
class="g-legend g-aiAbs g-aiPointText"
style="top:21.7216%;margin-top:-7.7px;left:4.9821%;width:55px;"
>
<p class="g-pstyle0">Strong</p>
</div>
<div
id="g-ai0-6"
class="g-legend g-aiAbs g-aiPointText"
style="top:28.0002%;margin-top:-7.7px;left:4.9821%;width:78px;"
>
<p class="g-pstyle0">Very strong</p>
</div>
<div
id="g-ai0-7"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:28.9916%;margin-top:-7.7px;left:62.2348%;width:68px;"
>
<p class="g-pstyle0">Gonaïves</p>
</div>
<div
id="g-ai0-8"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:39.9449%;margin-top:-14.9px;left:28.714%;margin-left:-36.5px;width:73px;"
>
<p class="g-pstyle1">Caribbean</p>
<p class="g-pstyle1">Sea</p>
</div>
<div
id="g-ai0-9"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:42.6579%;margin-top:-10.1px;left:68.5061%;width:77px;"
>
<p class="g-pstyle2">HAITI</p>
</div>
<div
id="g-ai0-10"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:59.0632%;margin-top:-7.7px;left:11.2526%;width:63px;"
>
<p class="g-pstyle0">Jeremie</p>
</div>
<div
id="g-ai0-11"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:61.1155%;margin-top:-8.9px;left:70.5455%;width:106px;"
>
<p class="g-pstyle3">Port-au-Prince</p>
</div>
<div
id="g-ai0-12"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:62.1069%;margin-top:-8.9px;left:32.6015%;width:77px;"
>
<p class="g-pstyle3">Epicenter</p>
</div>
<div
id="g-ai0-13"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:78.8906%;margin-top:-7.7px;left:63.9138%;width:58px;"
>
<p class="g-pstyle0">Jacmel</p>
</div>
<div
id="g-ai0-14"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:80.2124%;margin-top:-7.7px;left:22.5649%;width:71px;"
>
<p class="g-pstyle0">Les Cayes</p>
</div>
<div
id="g-ai0-15"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:87.8129%;margin-top:-7.7px;left:0.6179%;width:49px;"
>
<p class="g-pstyle0">50 mi</p>
</div>
<div
id="g-ai0-16"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:91.0202%;margin-top:-11.4px;right:10.4418%;width:70px;"
>
<p class="g-pstyle4">Dominican</p>
<p class="g-pstyle4">Republic</p>
</div>
<div
id="g-ai0-17"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:93.7611%;margin-top:-7.7px;left:0.6179%;width:52px;"
>
<p class="g-pstyle0">50 km</p>
</div>
</div>
{/if}
<!-- Artboard: sm -->
{#if width && width >= 510 && width < 660}
<div id="g-_ai-chart-sm" class="g-artboard" style="">
<div style="padding: 0 0 82.703% 0;"></div>
<div
id="g-_ai-chart-sm-img"
class="g-aiImg"
alt=""
style="{`background-image: url(${assets}/images/graphics/ai-chart-sm.png);`}"
></div>
<div
id="g-ai1-1"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:3.8773%;margin-top:-9.4px;left:0.3278%;width:111px;"
>
<p class="g-pstyle0">Shake intensity</p>
</div>
<div
id="g-ai1-2"
class="g-legend g-aiAbs g-aiPointText"
style="top:9.0933%;margin-top:-9.4px;left:3.0258%;width:52px;"
>
<p class="g-pstyle0">Light</p>
</div>
<div
id="g-ai1-3"
class="g-legend g-aiAbs g-aiPointText"
style="top:13.5979%;margin-top:-9.4px;left:3.0259%;width:77px;"
>
<p class="g-pstyle0">Moderate</p>
</div>
<div
id="g-ai1-4"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:16.6801%;margin-top:-9.4px;left:70.3255%;width:92px;"
>
<p class="g-pstyle0">Cap-Haitien</p>
</div>
<div
id="g-ai1-5"
class="g-legend g-aiAbs g-aiPointText"
style="top:18.3397%;margin-top:-9.4px;left:3.0258%;width:61px;"
>
<p class="g-pstyle0">Strong</p>
</div>
<div
id="g-ai1-6"
class="g-legend g-aiAbs g-aiPointText"
style="top:22.6073%;margin-top:-9.4px;left:3.0258%;width:88px;"
>
<p class="g-pstyle0">Very strong</p>
</div>
<div
id="g-ai1-7"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:28.5344%;margin-top:-9.4px;left:55.9181%;width:76px;"
>
<p class="g-pstyle0">Gonaïves</p>
</div>
<div
id="g-ai1-8"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:38.8091%;margin-top:-17.7px;left:27.2818%;margin-left:-41px;width:82px;"
>
<p class="g-pstyle1">Caribbean</p>
<p class="g-pstyle1">Sea</p>
</div>
<div
id="g-ai1-9"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:39.9724%;margin-top:-8.6px;left:61.2858%;width:67px;"
>
<p class="g-pstyle2">HAITI</p>
</div>
<div
id="g-ai1-10"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:56.985%;margin-top:-9.4px;left:12.2815%;width:69px;"
>
<p class="g-pstyle0">Jeremie</p>
</div>
<div
id="g-ai1-11"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:59.1569%;margin-top:-9.5px;left:63.0314%;width:112px;"
>
<p class="g-pstyle3">Port-au-Prince</p>
</div>
<div
id="g-ai1-12"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:60.1053%;margin-top:-9.5px;left:30.5543%;width:81px;"
>
<p class="g-pstyle3">Epicenter</p>
</div>
<div
id="g-ai1-13"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:62.7194%;margin-top:-16.5px;left:91.2282%;margin-left:-57px;width:114px;"
>
<p class="g-pstyle4">Dominican</p>
<p class="g-pstyle4">Republic</p>
</div>
<div
id="g-ai1-14"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:75.4778%;margin-top:-9.4px;left:57.3552%;width:64px;"
>
<p class="g-pstyle0">Jacmel</p>
</div>
<div
id="g-ai1-15"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:76.6632%;margin-top:-9.4px;left:21.9639%;width:79px;"
>
<p class="g-pstyle0">Les Cayes</p>
</div>
<div
id="g-ai1-16"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:85.5251%;margin-top:-7.7px;left:0.1344%;width:49px;"
>
<p class="g-pstyle5">50 mi</p>
</div>
<div
id="g-ai1-17"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:90.0297%;margin-top:-7.7px;left:0.1344%;width:52px;"
>
<p class="g-pstyle5">50 km</p>
</div>
</div>
{/if}
<!-- Artboard: md -->
{#if width && width >= 660}
<div id="g-_ai-chart-md" class="g-artboard" style="">
<div style="padding: 0 0 79.6009% 0;"></div>
<div
id="g-_ai-chart-md-img"
class="g-aiImg"
alt=""
style="{`background-image: url(${assets}/images/graphics/ai-chart-md.png);`}"
></div>
<div
id="g-ai2-1"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:2.3515%;margin-top:-9.4px;left:0.3608%;width:111px;"
>
<p class="g-pstyle0">Shake intensity</p>
</div>
<div
id="g-ai2-2"
class="g-legend g-aiAbs g-aiPointText"
style="top:7.6811%;margin-top:-9.4px;left:2.6603%;width:52px;"
>
<p class="g-pstyle0">Light</p>
</div>
<div
id="g-ai2-3"
class="g-legend g-aiAbs g-aiPointText"
style="top:12.2494%;margin-top:-9.4px;left:2.6604%;width:77px;"
>
<p class="g-pstyle0">Moderate</p>
</div>
<div
id="g-ai2-4"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:15.4852%;margin-top:-9.4px;left:70.3606%;width:92px;"
>
<p class="g-pstyle0">Cap-Haitien</p>
</div>
<div
id="g-ai2-5"
class="g-legend g-aiAbs g-aiPointText"
style="top:17.1983%;margin-top:-9.4px;left:2.6603%;width:61px;"
>
<p class="g-pstyle0">Strong</p>
</div>
<div
id="g-ai2-6"
class="g-legend g-aiAbs g-aiPointText"
style="top:21.7666%;margin-top:-9.4px;left:2.6603%;width:88px;"
>
<p class="g-pstyle0">Very strong</p>
</div>
<div
id="g-ai2-7"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:27.6672%;margin-top:-9.4px;left:55.993%;width:76px;"
>
<p class="g-pstyle0">Gonaïves</p>
</div>
<div
id="g-ai2-8"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:38.0099%;margin-top:-17.7px;left:27.2388%;margin-left:-41px;width:82px;"
>
<p class="g-pstyle1">Caribbean</p>
<p class="g-pstyle1">Sea</p>
</div>
<div
id="g-ai2-9"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:42.7626%;margin-top:-10.7px;left:62.8914%;width:80px;"
>
<p class="g-pstyle2">HAITI</p>
</div>
<div
id="g-ai2-10"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:50.0029%;margin-top:-17.7px;left:92.295%;margin-left:-60.5px;width:121px;"
>
<p class="g-pstyle3">Dominican</p>
<p class="g-pstyle3">Republic</p>
</div>
<div
id="g-ai2-11"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:57.3608%;margin-top:-9.4px;left:12.2815%;width:69px;"
>
<p class="g-pstyle0">Jeremie</p>
</div>
<div
id="g-ai2-12"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:60.2742%;margin-top:-10.7px;left:30.6995%;width:89px;"
>
<p class="g-pstyle4">Epicenter</p>
</div>
<div
id="g-ai2-13"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:62.5583%;margin-top:-10.7px;left:66.3403%;width:125px;"
>
<p class="g-pstyle4">Port-au-Prince</p>
</div>
<div
id="g-ai2-14"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:75.6338%;margin-top:-9.4px;left:57.8174%;width:64px;"
>
<p class="g-pstyle0">Jacmel</p>
</div>
<div
id="g-ai2-15"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:77.3469%;margin-top:-9.4px;left:22.5239%;width:79px;"
>
<p class="g-pstyle0">Les Cayes</p>
</div>
<div
id="g-ai2-16"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:86.936%;margin-top:-7.7px;left:0.1678%;width:49px;"
>
<p class="g-pstyle5">50 mi</p>
</div>
<div
id="g-ai2-17"
class="g-map-labels g-aiAbs g-aiPointText"
style="top:91.5043%;margin-top:-7.7px;left:0.1678%;width:52px;"
>
<p class="g-pstyle5">50 km</p>
</div>
</div>
{/if}
</div>
<!-- End ai2html - 2021-09-29 12:37 -->
<!-- ai file: _ai-chart.ai -->
<style lang="scss">
#g-_ai-chart-box,
#g-_ai-chart-box .g-artboard {
margin: 0 auto;
}
#g-_ai-chart-box p {
margin: 0;
}
#g-_ai-chart-box .g-aiAbs {
position: absolute;
}
#g-_ai-chart-box .g-aiImg {
position: absolute;
top: 0;
display: block;
width: 100% !important;
height: 100%;
background-size: contain;
background-repeat: no-repeat;
}
#g-_ai-chart-box .g-aiPointText p {
white-space: nowrap;
}
#g-_ai-chart-xs {
position: relative;
overflow: hidden;
}
#g-_ai-chart-xs p {
font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 400;
line-height: 14px;
height: auto;
opacity: 1;
letter-spacing: 0em;
font-size: 12px;
text-align: left;
color: rgb(51, 51, 51);
text-transform: none;
padding-bottom: 0;
padding-top: 0;
mix-blend-mode: normal;
font-style: normal;
position: static;
}
#g-_ai-chart-xs .g-pstyle0 {
height: 14px;
}
#g-_ai-chart-xs .g-pstyle1 {
font-style: italic;
height: 14px;
text-align: center;
color: rgb(113, 115, 117);
}
#g-_ai-chart-xs .g-pstyle2 {
font-weight: 700;
line-height: 18px;
height: 18px;
letter-spacing: 0.25em;
font-size: 15px;
}
#g-_ai-chart-xs .g-pstyle3 {
font-weight: 700;
line-height: 16px;
height: 16px;
font-size: 13px;
}
#g-_ai-chart-xs .g-pstyle4 {
line-height: 11px;
height: 11px;
font-size: 11px;
text-align: right;
}
#g-_ai-chart-sm {
position: relative;
overflow: hidden;
}
#g-_ai-chart-sm p {
font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 400;
line-height: 17px;
height: auto;
opacity: 1;
letter-spacing: 0em;
font-size: 14px;
text-align: left;
color: rgb(51, 51, 51);
text-transform: none;
padding-bottom: 0;
padding-top: 0;
mix-blend-mode: normal;
font-style: normal;
position: static;
}
#g-_ai-chart-sm .g-pstyle0 {
height: 17px;
}
#g-_ai-chart-sm .g-pstyle1 {
font-style: italic;
height: 17px;
text-align: center;
color: rgb(113, 115, 117);
}
#g-_ai-chart-sm .g-pstyle2 {
font-weight: 700;
line-height: 15px;
height: 15px;
letter-spacing: 0.25em;
font-size: 12px;
}
#g-_ai-chart-sm .g-pstyle3 {
font-weight: 700;
height: 17px;
}
#g-_ai-chart-sm .g-pstyle4 {
font-weight: 300;
line-height: 16px;
height: 16px;
letter-spacing: 0.25em;
font-size: 13px;
text-align: center;
text-transform: uppercase;
color: rgb(134, 136, 139);
}
#g-_ai-chart-sm .g-pstyle5 {
line-height: 14px;
height: 14px;
font-size: 12px;
}
#g-_ai-chart-md {
position: relative;
overflow: hidden;
}
#g-_ai-chart-md p {
font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 400;
line-height: 17px;
height: auto;
opacity: 1;
letter-spacing: 0em;
font-size: 14px;
text-align: left;
color: rgb(51, 51, 51);
text-transform: none;
padding-bottom: 0;
padding-top: 0;
mix-blend-mode: normal;
font-style: normal;
position: static;
}
#g-_ai-chart-md .g-pstyle0 {
height: 17px;
}
#g-_ai-chart-md .g-pstyle1 {
font-style: italic;
height: 17px;
text-align: center;
color: rgb(113, 115, 117);
}
#g-_ai-chart-md .g-pstyle2 {
font-weight: 700;
line-height: 19px;
height: 19px;
letter-spacing: 0.25em;
font-size: 16px;
}
#g-_ai-chart-md .g-pstyle3 {
font-weight: 300;
height: 17px;
letter-spacing: 0.25em;
text-align: center;
text-transform: uppercase;
color: rgb(134, 136, 139);
}
#g-_ai-chart-md .g-pstyle4 {
font-weight: 700;
line-height: 19px;
height: 19px;
font-size: 16px;
}
#g-_ai-chart-md .g-pstyle5 {
line-height: 14px;
height: 14px;
font-size: 12px;
}
/* Custom CSS */
</style>

View file

@ -0,0 +1,230 @@
<script>
import { assets } from '$app/paths';
let width = null;
</script>
<!-- Generated by ai2html v0.100.0 - 2022-02-14 14:47 -->
<div id="g-ai-linechart-box" bind:clientWidth="{width}">
<!-- Artboard: xs -->
{#if width && width >= 0 && width < 510}
<div id="g-ai-linechart-xs" class="g-artboard" style="">
<div style="padding: 0 0 187.8788% 0;"></div>
<div
id="g-ai-linechart-xs-img"
class="g-aiImg"
alt=""
style="background-image: url({assets}/images/graphics/ai-linechart-xs.png);"
></div>
</div>
{/if}
<!-- Artboard: sm -->
{#if width && width >= 510 && width < 660}
<div id="g-ai-linechart-sm" class="g-artboard" style="">
<div style="padding: 0 0 121.5686% 0;"></div>
<div
id="g-ai-linechart-sm-img"
class="g-aiImg"
alt=""
style="background-image: url({assets}/images/graphics/ai-linechart-sm.png);"
></div>
</div>
{/if}
<!-- Artboard: md -->
{#if width && width >= 660 && width < 930}
<div id="g-ai-linechart-md" class="g-artboard" style="">
<div style="padding: 0 0 52.7353% 0;"></div>
<div
id="g-ai-linechart-md-img"
class="g-aiImg"
alt=""
style="background-image: url({assets}/images/graphics/ai-linechart-md.png);"
></div>
<div
id="g-ai2-1"
class="g-ai2html-settings g-aiAbs g-aiPointText"
style="top:10.8355%;margin-top:-9.7px;left:10.3982%;width:41px;"
>
<p class="g-pstyle0">5%</p>
</div>
<div
id="g-ai2-2"
class="g-ai2html-settings g-aiAbs g-aiPointText"
style="top:29.5108%;margin-top:-9.7px;left:10.3982%;width:29px;"
>
<p class="g-pstyle0">0</p>
</div>
<div
id="g-ai2-3"
class="g-ai2html-settings g-aiAbs g-aiPointText"
style="top:35.0946%;margin-top:-11.1px;left:84.3799%;width:55px;"
>
<p class="g-pstyle1">Dow</p>
</div>
<div
id="g-ai2-4"
class="g-ai2html-settings g-aiAbs g-aiPointText"
style="top:39.9791%;margin-top:-11.1px;left:84.3801%;width:83px;"
>
<p class="g-pstyle2">S&amp;P 500</p>
</div>
<div
id="g-ai2-5"
class="g-ai2html-settings g-aiAbs g-aiPointText"
style="top:48.3111%;margin-top:-11.1px;left:84.3801%;width:78px;"
>
<p class="g-pstyle3">Nasdaq</p>
</div>
<div
id="g-ai2-6"
class="g-ai2html-settings g-aiAbs g-aiPointText"
style="top:48.1861%;margin-top:-9.7px;left:10.3982%;width:36px;"
>
<p class="g-pstyle0">&minus;5</p>
</div>
<div
id="g-ai2-7"
class="g-ai2html-settings g-aiAbs g-aiPointText"
style="top:67.1488%;margin-top:-9.7px;left:10.3982%;width:43px;"
>
<p class="g-pstyle0">&minus;10</p>
</div>
<div
id="g-ai2-8"
class="g-ai2html-settings g-aiAbs g-aiPointText"
style="top:85.8241%;margin-top:-9.7px;left:10.3982%;width:43px;"
>
<p class="g-pstyle0">&minus;15</p>
</div>
<div
id="g-ai2-9"
class="g-ai2html-settings g-aiAbs g-aiPointText"
style="top:93.0069%;margin-top:-9.7px;left:28.7755%;width:92px;"
>
<p class="g-pstyle0">Jan. 2, 2022</p>
</div>
<div
id="g-ai2-10"
class="g-ai2html-settings g-aiAbs g-aiPointText"
style="top:93.0069%;margin-top:-9.7px;left:73.7228%;width:57px;"
>
<p class="g-pstyle0">Jan. 9</p>
</div>
</div>
{/if}
<!-- Artboard: lg -->
{#if width && width >= 930 && width < 1200}
<div id="g-ai-linechart-lg" class="g-artboard" style="">
<div style="padding: 0 0 66.6667% 0;"></div>
<div
id="g-ai-linechart-lg-img"
class="g-aiImg"
alt=""
style="background-image: url({assets}/images/graphics/ai-linechart-lg.png);"
></div>
</div>
{/if}
<!-- Artboard: xl -->
{#if width && width >= 1200}
<div id="g-ai-linechart-xl" class="g-artboard" style="">
<div style="padding: 0 0 51.6667% 0;"></div>
<div
id="g-ai-linechart-xl-img"
class="g-aiImg"
alt=""
style="background-image: url({assets}/images/graphics/ai-linechart-xl.png);"
></div>
</div>
{/if}
</div>
<!-- End ai2html - 2022-02-14 14:47 -->
<!-- ai file: ai-linechart.ai -->
<style lang="scss">
#g-ai-linechart-box,
#g-ai-linechart-box .g-artboard {
margin: 0 auto;
}
#g-ai-linechart-box p {
margin: 0;
}
#g-ai-linechart-box .g-aiAbs {
position: absolute;
}
#g-ai-linechart-box .g-aiImg {
position: absolute;
top: 0;
display: block;
width: 100% !important;
height: 100%;
background-size: contain;
background-repeat: no-repeat;
}
#g-ai-linechart-box .g-aiPointText p {
white-space: nowrap;
}
#g-ai-linechart-xs {
position: relative;
overflow: hidden;
}
#g-ai-linechart-sm {
position: relative;
overflow: hidden;
}
#g-ai-linechart-md {
position: relative;
overflow: hidden;
}
#g-ai-linechart-md p {
font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 300;
line-height: 18px;
height: auto;
opacity: 1;
letter-spacing: 0em;
font-size: 15px;
text-align: left;
color: rgb(0, 0, 0);
text-transform: none;
padding-bottom: 0;
padding-top: 0;
mix-blend-mode: normal;
font-style: normal;
position: static;
}
#g-ai-linechart-md .g-pstyle0 {
height: 18px;
}
#g-ai-linechart-md .g-pstyle1 {
font-weight: 700;
line-height: 20px;
height: 20px;
font-size: 17px;
color: rgb(116, 196, 118);
}
#g-ai-linechart-md .g-pstyle2 {
font-weight: 700;
line-height: 20px;
height: 20px;
font-size: 17px;
color: rgb(49, 130, 189);
}
#g-ai-linechart-md .g-pstyle3 {
font-weight: 700;
line-height: 20px;
height: 20px;
font-size: 17px;
color: rgb(244, 193, 0);
}
#g-ai-linechart-lg {
position: relative;
overflow: hidden;
}
#g-ai-linechart-xl {
position: relative;
overflow: hidden;
}
/* Custom CSS */
</style>

View file

@ -0,0 +1,24 @@
<table class="linechart-data">
<thead
><tr>
<th data-column="Date" data-row="-1">Date </th><th
data-column="S&amp;P 500"
data-row="-1"
>S&amp;P 500
</th><th data-column="Dow" data-row="-1">Dow </th><th
data-column="Nasdaq"
data-row="-1"
>Nasdaq
</th>
</tr></thead
>
<tbody
><tr> <td>December 31, 2021</td><td>0%</td><td>0</td><td>0%</td></tr><tr>
<td>January 3, 2022</td><td>1%</td><td>1</td><td>1%</td></tr
><tr> <td>January 4, 2022</td><td>1%</td><td>1</td><td>0%</td></tr><tr>
<td>January 5, 2022</td><td>1%</td><td>0</td><td>3%</td></tr
><tr> <td>January 6, 2022</td><td>1%</td><td>0</td><td>4%</td></tr><tr>
<td>January 7, 2022</td><td>2%</td><td>0</td><td>5%</td></tr
><tr> <td>January 10, 2022</td><td>2%</td><td>1</td><td>4%</td></tr>
</tbody>
</table>

View file

@ -0,0 +1,320 @@
---
title: Ai2svelte
description: A shortcut for ai2svelte graphics.
slug: ai2svelte
---
<script>
import DemoContainer from '../_docs/DemoContainer/index.svelte';
import Ai2svelte from './index.svelte';
import AiChart from './ai2svelte/ai-chart.exclude.svelte';
import DataTable from './ai2svelte/data-table.exclude.svelte'
const fetchComponent = async(componentName) => {
return (await import(`./ai2svelte/${componentName}.exclude.svelte`)).default
};
</script>
<section>
## {title}
Pass a component created by [ai2svelte](https://github.com/reuters-graphics/ai2svelte) to this component, which will wrap it in a graphics section tag.
</section>
```svelte
<script>
import { Ai2svelte } from '@reuters-graphics/graphics-svelte-components';
import MyAiChart from './some-chart.svelte';
</script>
<Ai2svelte
AiGraphic="{MyAiChart}"
ariaDescription="Description of your graphic for screen readers."
/>
```
<DemoContainer>
<Ai2svelte AiGraphic={AiChart} ariaHidden = {true}
ariaDescription="A map of Haiti shows the epicenter of an earthquake in the southwest of the country." />
</DemoContainer>
<section>
## Accessibility props
`ariaHidden`
- Set to `true` by default, which means HTML text content in the
ai2svelte component are visible on the page but not read aloud by screen readers.
(Read more about aria-hidden elements [here](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-hidden).)
- Set it to `false` to make the screen reader read aloud HTML text content in ai2svelte.
`ariaDescription`
- If `ariaHidden` is `true`, make sure you add an `ariaDescription`, which is invisible on the page but is read aloud by screen readers.
- This prop takes a string, like altText for an image.
- It should describe what the chart is showing and why it's important. This is helpful for:
- Blind readers using screen-reading or Braille conversion software
- All readers if the image is unavailable or takes too long to load
- Read about writing aria description, also known as alt text, [here](https://medium.com/nightingale/writing-alt-text-for-data-visualization-2a218ef43f81).
<section class='note'>
🚨 **Important:** If `ariaHidden` is `true` but you are missing `ariaDescription` or some other special alternative for screen readers (more on this later), your graphic will be hidden from the page and you will see a warning in your console.
</section>
Graphics kit users can set your `ariaHidden` prop and write your `ariaDescription` in the google doc like this:
</section>
```bash
# In your Google doc...
[blocks]
# ...
Type: ai2svelte
ComponentName: my-chart
AriaHidden: true // or false
AltText: Add a descriptive line on the graphic for screen readers.
[]
```
<section>
## Graphic width
Adjust the size of the graphic by passing a class name corresponding to one of our well widths: `wide`, `wider`, `widest` or `fluid`.
</section>
```svelte
<script>
import { Ai2svelte } from '@reuters-graphics/graphics-svelte-components';
import MyAiChart from './some-chart.svelte';
</script>
<Ai2svelte
AiGraphic="{MyAiChart}"
size="wide"
ariaDescription="A map of Haiti shows the epicenter of an earthquake in the southwest of the country."
/>
```
<DemoContainer>
<Ai2svelte AiGraphic={AiChart} size='wide'
ariaDescription="A map of Haiti shows the epicenter of an earthquake in the southwest of the country." />
</DemoContainer>
<section>
## Chart title, description, source and note
Add additional chart chatter through slots.
</section>
```svelte
<script>
import { Ai2svelte } from '@reuters-graphics/graphics-svelte-components';
import MyAiChart from './some-chart.svelte';
</script>
<!-- Add an ID and change the default width of your graphic -->
<Ai2svelte
AiGraphic="{MyAiChart}"
id="ai-map"
size="wide"
ariaDescription="A map of Haiti shows the epicenter of an earthquake in the southwest of the country."
>
<!-- Add a title and/or notes with slots -->
<div slot="title" class="title">
<h4>Earthquake in Haiti</h4>
<p>A 7.0 magnitude earthquake struck the island on Tuesday.</p>
</div>
<aside slot="notes">
<p class="note">Note: Data current as of Wednesday.</p>
<p class="source">Source: USGIS</p>
</aside>
</Ai2svelte>
<style lang="scss">
// You can now style those elements!
h4 {
color: darkred;
}
</style>
```
<DemoContainer>
<Ai2svelte AiGraphic="{AiChart}" id="ai-map"
ariaHidden = {true}
size="wide"
ariaDescription="A map of Haiti shows the epicenter of an earthquake in the southwest of the country." >
<!-- Add a title and/or notes with slots -->
<div slot="title" class="title">
<h4>Earthquake in Haiti</h4>
<p>A 7.0 magnitude earthquake struck the island on Tuesday.</p>
</div>
<aside slot="notes">
<p class="note">Note: Data current as of Wednesday.</p>
<p class="source">Source: USGIS</p>
</aside>
</Ai2svelte>
</DemoContainer>
<style>
div.title h4 {
color: darkred;
}
</style>
<section>
## Using ArchieML google doc
You can use this component to layout AI graphics via an [ArchieML](http://archieml.org/)-formatted Google doc by using the following pattern to dynamically import an ai2svelte component:
</section>
```bash
# In your Google doc...
[blocks]
# ...
Type: ai2svelte
ComponentName: my-chart
[]
```
```svelte
<!-- src/lib/Page.svelte -->
<script>
import content from '$locales/en/content.json';
import { Ai2svelte } from '@reuters-graphics/graphics-svelte-components';
import { truthyString } from '$utils/truthyString';
import { fetchComponent } from '$utils/dynamicComponents';
</script>
{#each content.blocks as block}
{#if block.Type === 'ai2svelte'}
{#await fetchComponent(block.ComponentName)}
<div></div>
{:then component}
<Ai2svelte
AiGraphic="{component}"
id="{block.ComponentName}"
size="{block.Size}"
ariaHidden="{truthyString(block.AriaHidden)}"
ariaDescription="{block.AltText}"
>
<!-- Code below is optional. Can delete if you have no dek, source, note, etc. -->
<div slot="title" class="title">
{#if block.Title}<h4>{block.Title}</h4>{/if}
{#if block.Chatter}<p>{block.Chatter}</p>{/if}
</div>
<aside slot="notes">
{#if block.Note}<p class="note">Note: {block.Note}</p>{/if}
{#if block.Source}<p class="source">Source: {block.Source}</p>{/if}
</aside>
<!-- End of optional code -->
</Ai2svelte>
{:catch error}
{console.error(
`Error fetching component: ./ai2svelte/${block.ComponentName}.svelte`,
error
)}
{/await}
{/if}
{/each}
```
<section>
This comes with some restrictions, though. Be sure your `fetchComponent` function follows [the limits on dynamic imports](https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations).
**NOTE**: Make sure you wrap the function `truthyString()` around `block.StackBackground`. `truthyString()` converts the string pulled from Google docs ('true', 'false', etc.)
into a Boolean.
</section>
<section class='pt-5'>
## Customising data for screen readers
Sometimes, in addition to or instead of adding an aria description for graphics,
we want to also provide a data table, a lengthier description with more complex element structure or something else.
You can add custom information for screen readers only by using the `hidden` slot.
For example, for the line chart below, we can add a **data table** that helps screen-reader users navigate the data.
</section>
```svelte
<script>
import SRDataTable from './SRDataTable.svelte';
</script>
<Ai2svelte
AiGraphic="{MyAiChart}"
ariaDescription="A line chart showing daily closing prices for S&P 500, Dow, and Nasdaq from Dec. 31, 2021 to Jan. 10, 2022"
>
<slot slot="hidden">
<SRDataTable/>
</slot>
<Ai2svelte/>
```
<DemoContainer>
{#await fetchComponent('ai-linechart') then component}
<Ai2svelte AiGraphic="{component}"
ariaDescription="A map of Haiti shows the epicenter of an earthquake in the southwest of the country.">
<slot slot="hidden"><DataTable/></slot>
</Ai2svelte>
{/await}
</DemoContainer>
<section>
This is what `<SRDataTable/>` looks like. (You can make an HTML table using DataWrapper.)
</section>
```svelte
<table class="line-chart-data">
<thead
><tr>
<th data-column="Date" data-row="-1">Date </th><th
data-column="S&amp;P 500"
data-row="-1"
>S&amp;P 500
</th><th data-column="Dow" data-row="-1">Dow </th><th
data-column="Nasdaq"
data-row="-1"
>Nasdaq
</th>
</tr></thead
>
<tbody
><tr> <td>December 31, 2021</td><td>0%</td><td>0</td><td>0%</td></tr><tr>
<td>January 3, 2022</td><td>1%</td><td>1</td><td>1%</td></tr
><tr> <td>January 4, 2022</td><td>1%</td><td>1</td><td>0%</td></tr><tr>
<td>January 5, 2022</td><td>1%</td><td>0</td><td>3%</td></tr
><tr> <td>January 6, 2022</td><td>1%</td><td>0</td><td>4%</td></tr><tr>
<td>January 7, 2022</td><td>2%</td><td>0</td><td>5%</td></tr
><tr> <td>January 10, 2022</td><td>2%</td><td>1</td><td>4%</td></tr>
</tbody>
</table>
```
<section>
[Read this](https://accessibility.psu.edu/images/charts/) for more information on screen-reader data tables for charts.
</section>

View file

@ -0,0 +1,63 @@
<script>
/* This component wraps ai2svelte graphics. */
export let AiGraphic;
export let id = '';
export let ariaHidden = true;
export let ariaDescription = null;
// normal, wide, wider, widest or fluid
export let size = 'normal';
export let onAiMounted = () => {};
if (ariaHidden && !ariaDescription) {
console.warn(
'Must provide aria description for ai2svelte components if ariaHidden is true.'
);
}
</script>
<section class="ai2svelte-container graphic {size}" id="{id}">
{#if (ariaHidden && (ariaDescription || $$slots.hidden)) || !ariaHidden}
{#if $$slots.title}
<div class="chatter-container">
<slot name="title" />
</div>
{/if}
{#if ariaDescription}
<p class="visually-hidden">{ariaDescription}</p>
{/if}
{#if $$slots.hidden}
<div class="visually-hidden custom">
<slot name="hidden" />
</div>
{/if}
<div class="ai-wrapper" aria-hidden="{ariaHidden}">
<svelte:component this="{AiGraphic}" onAiMounted="{onAiMounted}" />
</div>
{#if $$slots.notes}
<div class="chatter-container">
<slot name="notes" />
</div>
{/if}
{/if}
</section>
<style lang="scss">
@import '~@reuters-graphics/style-theme-eisbaer/scss/components/containers/widths';
.chatter-container {
@extend .well;
}
.visually-hidden {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important; // Fix for https://github.com/twbs/bootstrap/issues/25686
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
clip-path: polygon(0px 0px, 0px 0px, 0px 0px);
white-space: nowrap !important;
border: 0 !important;
}
</style>

View file

@ -0,0 +1,126 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// Don't lose the "?raw" in markdown imports!
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
// @ts-ignore
import customWellWidthsDocs from './stories/docs/customWellWidths.md?raw';
import Article from './Article.svelte';
import Section from '../Section/Section.svelte';
import { withComponentDocs, withStoryDocs } from '$docs/utils/withParams.js';
const meta = {
title: 'Layout/Article',
component: Article,
...withComponentDocs(componentDocs),
};
</script>
<Meta {...meta} />
<Template let:args>
<Article {...args} />
</Template>
<Story
name="Basic"
args="{{
embedded: false,
id: '',
}}"
>
<Article id="article-story-basic">
<div class="demo-container">
<div class="background-label">Article container</div>
<div class="padding-label"><span></span>15px padding</div>
</div>
</Article>
</Story>
<Story
name="Custom columns"
{...withStoryDocs(customWellWidthsDocs)}
>
<Article id="article-column-widths-demo">
<div class="article-boundaries">
<Section id="section-demo" width="narrower">narrower</Section>
<Section id="section-demo" width="narrow">narrow</Section>
<Section id="section-demo">normal</Section>
<Section id="section-demo" width="wide">wide</Section>
<Section id="section-demo" width="wider">wider</Section>
<Section id="section-demo" width="widest">widest</Section>
<Section id="section-demo" width="fluid">fluid</Section>
</div>
</Article>
<Article id="article-column-widths-demo" columnWidths={{ narrower: 310, narrow: 450, normal: 550, wide: 675, wider: 1400 }}>
<div class="article-boundaries custom">
<Section id="section-demo" width="narrower">narrower*</Section>
<Section id="section-demo" width="narrow">narrow*</Section>
<Section id="section-demo">normal*</Section>
<Section id="section-demo" width="wide">wide*</Section>
<Section id="section-demo" width="wider">wider*</Section>
<Section id="section-demo" width="widest">widest</Section>
<Section id="section-demo" width="fluid">fluid</Section>
</div>
</Article>
</Story>
<style lang="scss">
:global {
#article-story-basic,
#article-column-widths-demo {
width: calc(100% + 30px);
margin-left: -15px;
}
#article-column-widths-demo {
background-color: #ddd;
position: relative;
margin-bottom: 10px;
.article-boundaries {
padding: 0;
width: 100%;
height: 100%;
background-color: #bbb;
&.custom {
section {
background: rgb(211, 132, 123);
}
}
}
section {
height: 300px;
background: #81a1c1;
margin-bottom: 2px;
height: 50px;
padding-left: 3px;
color: white;
font-size: 12px;
}
}
}
div.demo-container {
height: 300px;
background: #ccc;
position: relative;
font-size: 12px;
.background-label {
position: absolute;
bottom: 0;
left: 5px;
}
.padding-label {
position: absolute;
top: 0;
left: -15px;
span {
font-size: 18px;
}
}
}
</style>

View file

@ -0,0 +1,59 @@
<script lang="ts">
/** Set to true for embeddables. */
export let embedded: boolean = false;
/** Add an id to the article tag to target it with custom CSS. */
export let id: string = '';
/** ARIA role of the article, usually ["main"](https://w3c.github.io/aria/#main) if enclosing the story. */
export let role: string = 'main';
interface ColumnWidths {
/** Narrower column width */
narrower: number;
/** Narrow column width */
narrow: number;
/** Normal column width */
normal: number;
/** Wide column width */
wide: number;
/** Wider column width */
wider: number;
}
/** Set custom widths for the normal, wide and wider column dimensions */
export let columnWidths: ColumnWidths = {
narrower: 330,
narrow: 510,
normal: 660,
wide: 930,
wider: 1200,
};
import cssVariables from '$lib/actions/cssVariables/index.js';
$: columnWidthVars = {
'narrower-column-width': columnWidths.narrower + 'px',
'narrow-column-width': columnWidths.narrow + 'px',
'normal-column-width': columnWidths.normal + 'px',
'wide-column-width': columnWidths.wide + 'px',
'wider-column-width': columnWidths.wider + 'px',
};
</script>
<article {id} class:embedded={embedded} role={role} use:cssVariables={columnWidthVars}>
<!-- Article content -->
<slot></slot>
</article>
<style lang="scss">
article {
width: 100%;
display: block;
margin: 0;
padding: 0 15px;
overflow-x: hidden;
&.embedded {
overflow: auto;
}
}
</style>

View file

@ -0,0 +1,13 @@
The `Article` component contains all the content of our story and also establishes the dimensions of our article well.
> 📌 In most cases, you won't need to mess with the `Article` component because it's already included in our rigs for you!
```svelte
<script>
import { Article } from '@reuters-graphics/graphics-svelte-components';
</script>
<Article>
<!-- The story stuff goes in here! -->
</Article>
```

View file

@ -0,0 +1,83 @@
The `Article` component also creates several column dimensions inside our article well. The standard widths of columns follow a basic class scheme:
- `narrower` A bit narrower than narrow...
- `narrow` A bit narrower than the text column
- `normal` **The main width of the body text column**
- `wide` A bit wider than the text column
- `wider` A bit wider than wide...
- `widest` Edge-to-edge, but _excluding_ the left and right padding on `Article`
- `fluid` Fully edge-to-edge
(Check out the below demo in the "Canvas" tab to better see the wider differences.)
When combined with the `Section` component, you can set custom column widths by passing an object to the `columnWidths` prop with pixel values for the `narrower`, `narrow`, `normal`, `wide` and `wider` column widths.
```svelte
<Article
columnWidths={{ narrower: 310, narrow: 450, normal: 550, wide: 675, wider: 1400 }}
>
<Section width='narrower' />
<Section width='narrow' />
<Section width='normal' />
<Section width='wide' />
<Section width='wider' />
<Section width='widest' />
<Section width='fluid' />
</Article>
```
> Keep in mind, other tools, like our AI templates, use our default column widths, so customising those widths here may have downstream consequences for graphics made outside your code.
If you're not using our `Section` component, you can still inherit the column widths from `Article` to create your own custom container using the article well dimensions by using [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) like this:
```svelte
<div class="my-special-container">
<!-- Stuffs... -->
</div>
<style lang="scss">
div.my-special-container {
max-width: var(--wide-column-width);
}
</style>
```
... or you can make your component entirely configurable within the article well doing something like this:
```svelte
<script>
export let width = 'normal';
</script>
<div class="my-special-container {width}">
<!-- Stuffs... -->
</div>
<style lang="scss">
div.my-special-container {
max-width: var(--normal-column-width);
&.narrower {
max-width: var(--narrower-column-width);
}
&.narrow {
max-width: var(--narrow-column-width);
}
&.wide {
max-width: var(--wide-column-width);
}
&.wider {
max-width: var(--wider-column-width);
}
&.widest {
max-width: 100%;
}
&.fluid {
width: calc(100% + 30px);
margin-left: -15px;
max-width: none;
}
}
</style>
```
Here's an example of how custom* `columnWidths` can be used to change the article well columns:

View file

@ -0,0 +1,123 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
// @ts-ignore
import withOverlaysDocs from './stories/docs/withOverlays.md?raw';
// @ts-ignore
import ariaDescriptionsDocs from './stories/docs/ariaDescriptions.md?raw';
import BeforeAfter from './BeforeAfter.svelte';
// @ts-ignore
import beforeImg from './stories/myrne-before.jpg';
// @ts-ignore
import afterImg from './stories/myrne-after.jpg';
import {
withComponentDocs,
withStoryDocs,
} from '$lib/docs/utils/withParams.js';
const meta = {
title: 'Components/BeforeAfter',
component: BeforeAfter,
...withComponentDocs(componentDocs),
argTypes: {
handleColour: { control: 'color' },
width: {
control: 'select',
options: ['normal', 'wide', 'wider', 'widest', 'fluid'],
},
},
};
</script>
<Meta {...meta} />
<Template let:args>
<BeforeAfter {...args} />
</Template>
<Story
name="Default"
args={{
beforeSrc: beforeImg,
beforeAlt: 'Satellite image of Russian base at Myrne taken on July 7, 2020.',
afterSrc: afterImg,
afterAlt: 'Satellite image of Russian base at Myrne taken on Oct. 20, 2020.',
}}
/>
<Story name="With overlays" {...withStoryDocs(withOverlaysDocs)}>
<BeforeAfter
beforeSrc="{beforeImg}"
beforeAlt="Satellite image of Russian base at Myrne taken on July 7, 2020."
afterSrc="{afterImg}"
afterAlt="Satellite image of Russian base at Myrne taken on Oct. 20, 2020."
>
<div slot="beforeOverlay" class="overlay before">
<h6>July 7, 2020</h6>
<p>Initially, this site was far smaller.</p>
</div>
<div slot="afterOverlay" class="overlay after">
<h6>Oct. 20, 2020</h6>
<p>But then forces built up.</p>
</div>
<aside slot="caption">
<p>Photos by MAXAR Technologies, 2021.</p>
</aside>
</BeforeAfter>
<style lang="scss">
.overlay {
color: white;
padding: 15px;
background: rgba(0, 0, 0, 0.2);
&.after {
text-align: right;
}
h6, p {
color: white;
}
}
</style>
</Story>
<Story name="ARIA descriptions" {...withStoryDocs(ariaDescriptionsDocs)}>
<BeforeAfter
beforeSrc="{beforeImg}"
beforeAlt="Satellite image of Russian base at Myrne taken on July 7, 2020."
afterSrc="{afterImg}"
afterAlt="Satellite image of Russian base at Myrne taken on Oct. 20, 2020."
>
<div let:description="{id}" slot="beforeOverlay" class="overlay">
<p id="{id}">
On July 7, 2020, the base contained only a few transport vehicles.
</p>
</div>
<div let:description="{id}" slot="afterOverlay" class="overlay">
<!-- 👇 id can also be used on an element containing multiple text elements -->
<div id="{id}">
<p>But by October, tanks and artillery could be seen.</p>
<p>
In total, over 50 pieces of heavy machinery and 200 personnel are now
based here.
</p>
</div>
</div>
</BeforeAfter>
<style lang="scss">
div.overlay {
color: white;
padding: 15px;
max-width: 250px;
background: rgba(0, 0, 0, 0.2);
}
p {
color: white;
}
</style>
</Story>

View file

@ -0,0 +1,322 @@
<script lang="ts">
import { throttle } from 'lodash-es';
import { onMount } from 'svelte';
type ContainerWidth = 'normal' | 'wide' | 'wider' | 'widest' | 'fluid';
/** Width of the chart within the text well. */
export let width: ContainerWidth = 'normal'; // options: wide, wider, widest, fluid
/** Height of the component */
export let height = 600;
/**
* If set, makes the height a ratio of the component's width.
* @type {number}
*/
export let heightRatio: number | null = null;
/**
* Before image src
* @required
*/
export let beforeSrc: string | null = null;
/**
* Before image altText
* @required
*/
export let beforeAlt: string | null = null;
/**
* After image src
* @required
*/
export let afterSrc: string | null = null;
/**
* After image altText
* @required
*/
export let afterAlt: string | null = null;
/** Drag handle colour */
export let handleColour = 'white';
/** Drag handle opacity */
export let handleInactiveOpacity = 0.4;
/** Margin at the edge of the image to stop dragging */
export let handleMargin = 20;
/** Percentage of the component width the handle will travel ona key press */
export let keyPressStep = 0.05;
/** Initial offset of the handle, between 0 and 1.*/
export let offset = 0.5;
const random4 = () =>
Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
const id = 'before-after-' + random4() + random4();
let img;
let imgOffset = null;
let sliding = false;
let figure;
let beforeOverlayWidth = 0;
let isFocused = false;
let containerWidth;
$: containerHeight = (containerWidth && heightRatio) ? containerWidth * heightRatio : height;
const onFocus = () => (isFocused = true);
const onBlur = () => (isFocused = false);
const handleKeyDown = (e) => {
if (!isFocused) return;
const { keyCode } = e;
const margin = handleMargin / w;
if (keyCode === 39) {
offset = Math.min(1 - margin, offset + keyPressStep);
} else if (keyCode === 37) {
offset = Math.max(0 + margin, offset - keyPressStep);
}
};
const measureImage = () => {
if (img && img.complete) imgOffset = img.getBoundingClientRect();
};
const resize = () => {
measureImage();
};
const measureLoadedImage = (e) => {
if (e.type === 'load') {
imgOffset = e.target.getBoundingClientRect();
}
};
const move = (e) => {
if (sliding && imgOffset) {
const el = e.touches ? e.touches[0] : e;
const figureOffset = figure
? parseInt(window.getComputedStyle(figure).marginLeft.slice(0, -2))
: 0;
let x = el.pageX - figureOffset - imgOffset.left;
x =
x < handleMargin
? handleMargin
: x > w - handleMargin
? w - handleMargin
: x;
offset = x / w;
}
};
const start = (e) => {
sliding = true;
move(e);
};
const end = () => {
sliding = false;
};
$: w = (imgOffset && imgOffset.width) || 0;
$: x = w * offset;
$: figStyle = `width:100%;height:${containerHeight}px;`;
$: imgStyle = 'width:100%;height:100%;';
$: beforeOverlayClip =
x < beforeOverlayWidth ? Math.abs(x - beforeOverlayWidth) : 0;
if (!(beforeSrc && beforeAlt && afterSrc && afterAlt)) {
console.warn('Missing required src or alt props for BeforeAfter component');
}
onMount(() => {
// This is necessary b/c on:load doesn't reliably fire on the image...
const interval = setInterval(() => {
if (imgOffset) clearInterval(interval);
if (img && img.complete && !imgOffset) measureImage();
}, 50);
});
</script>
<svelte:window
on:touchmove="{move}"
on:touchend="{end}"
on:mousemove="{move}"
on:mouseup="{end}"
on:resize="{throttle(resize, 100)}"
on:keydown="{handleKeyDown}"
/>
{#if beforeSrc && beforeAlt && afterSrc && afterAlt}
<section
class="photo before-after {width}"
style="height: {containerHeight}px;"
bind:clientWidth="{containerWidth}"
>
<figure
style="{figStyle}"
class="before-after-container"
on:touchstart="{start}"
on:mousedown="{start}"
bind:this="{figure}"
aria-labelledby="{$$slots.caption && `${id}-caption`}"
>
<img
bind:this="{img}"
src="{afterSrc}"
alt="{afterAlt}"
on:load="{measureLoadedImage}"
on:mousedown|preventDefault
style="{imgStyle}"
class="after"
aria-describedby="{$$slots.beforeOverlay && `${id}-before`}"
/>
<img
src="{beforeSrc}"
alt="{beforeAlt}"
on:mousedown|preventDefault
style="clip: rect(0 {x}px {containerHeight}px 0);{imgStyle}"
class="before"
aria-describedby="{$$slots.afterOverlay && `${id}-after`}"
/>
{#if $$slots.beforeOverlay}
<div
id="image-before-label"
class="overlay-container before"
bind:clientWidth="{beforeOverlayWidth}"
style="clip-path: inset(0 {beforeOverlayClip}px 0 0);"
>
<!-- Overlay for before image -->
<slot
name="beforeOverlay"
description="{`${id}-before-description`}"
/>
</div>
{/if}
{#if $$slots.afterOverlay}
<div id="image-after-label" class="overlay-container after">
<!-- Overlay for after image -->
<slot name="afterOverlay" description="{`${id}-after-description`}" />
</div>
{/if}
<div
tabindex="0"
class="handle"
style="left: calc({offset *
100}% - 20px); --before-after-handle-colour: {handleColour}; --before-after-handle-inactive-opacity: {handleInactiveOpacity};"
on:focus="{onFocus}"
on:blur="{onBlur}"
>
<div class="arrow-left"></div>
<div class="arrow-right"></div>
</div>
</figure>
</section>
{#if $$slots.caption}
<section class="graphic caption {width}" id="{`${id}-caption`}">
<!-- Caption for image credits -->
<slot name="caption" />
</section>
{/if}
{/if}
<style lang="scss">
@import "@reuters-graphics/style-main/scss/fonts/mixins";
figure.before-after-container {
overflow: hidden;
position: relative;
box-sizing: content-box;
margin: 0 auto;
img {
top: 0;
left: 0;
z-index: 20;
&.after {
z-index: 21;
}
&.before {
z-index: 22;
}
display: block;
max-width: 100%;
user-select: none;
object-fit: cover;
position: absolute;
}
.overlay-container {
position: absolute;
:global {
p {
@include font-display;
font-size: 1rem;
line-height: 1.2rem;
&:last-child {
margin-bottom: 0;
}
}
}
&.before {
left: 0;
z-index: 23;
}
&.after {
right: 0;
z-index: 21;
}
}
}
.handle {
z-index: 30;
width: 40px;
height: 40px;
cursor: move;
background: none;
user-select: none;
position: absolute;
border-radius: 50px;
top: calc(50% - 20px);
border: 4px solid var(--before-after-handle-colour);
opacity: var(--before-after-handle-inactive-opacity, 0.6);
&:hover,
&:active,
&:focus {
opacity: 1;
}
&:before,
&:after {
content: '';
height: 9999px;
position: absolute;
left: calc(50% - 2px);
border: 2px solid var(--before-after-handle-colour);
}
&:before {
top: 40px;
}
&:after {
bottom: 40px;
}
.arrow-right,
.arrow-left {
width: 0;
height: 0;
user-select: none;
position: relative;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
}
.arrow-right {
left: 19px;
bottom: 14px;
border-left: 10px solid var(--before-after-handle-colour);
}
.arrow-left {
left: 3px;
top: 6px;
border-right: 10px solid var(--before-after-handle-colour);
}
}
section.graphic.caption {
margin: 0 auto;
}
</style>

View file

@ -0,0 +1,29 @@
Use text elements in your overlays as [ARIA descriptions](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-describedby) for your images by setting an ID on text elements within each overly with the `description` [slot prop](https://svelte.dev/tutorial/slot-props).
> **💡 TIP:** You must always use the `beforeAlt` / `afterAlt` props to label your image for visually impaired readers, but you can use these descriptions to provide more information or context that the reader might also need.
```svelte
<BeforeAfter
beforeSrc="{`${assets}/images/before-after/myrne-before.jpg`}"
beforeAlt="Satellite image of Russian base at Myrne taken on July 7, 2020."
afterSrc="{`${assets}/images/before-after/myrne-after.jpg`}"
afterAlt="Satellite image of Russian base at Myrne taken on Oct. 20, 2020."
>
<!-- 👇 Define the prop on each slot... -->
<div let:description="{id}" slot="beforeOverlay">
<p id="{id}">
On July 7, 2020, the base contained only a few transport vehicles.
</p>
</div>
<div let:description="{id}" slot="afterOverlay">
<!-- 👇 id can also be used on an element containing multiple text elements -->
<div id="{id}">
<p>But by October, tanks and artillery could be seen.</p>
<p>
In total, over 50 pieces of heavy machinery and 200 personnel are now
based here.
</p>
</div>
</div>
</BeforeAfter>
```

View file

@ -0,0 +1,15 @@
A before and after image comparison component.
```svelte
<script>
import { BeforeAfter } from '@reuters-graphics/graphics-svelte-components';
import { assets } from '$app/paths'; // If using in the Graphics Kit
</script>
<BeforeAfter
beforeSrc="{`${assets}/images/before-after/myrne-before.jpg`}"
beforeAlt="Satellite image of Russian base at Myrne taken on July 7, 2020."
afterSrc="{`${assets}/images/before-after/myrne-after.jpg`}"
afterAlt="Satellite image of Russian base at Myrne taken on Oct. 20, 2020."
/>
```

View file

@ -0,0 +1,34 @@
Add overlays with the `beforeOverlay` and `afterOverlay` slots and a caption to the `caption` slot, then style these elements to match your page design as below.
```svelte
<BeforeAfter
beforeSrc="{`${assets}/images/before-after/myrne-before.jpg`}"
beforeAlt="Satellite image of Russian base at Myrne taken on July 7, 2020."
afterSrc="{`${assets}/images/before-after/myrne-after.jpg`}"
afterAlt="Satellite image of Russian base at Myrne taken on Oct. 20, 2020."
>
<div slot="beforeOverlay" class="overlay before">
<h6>July 7, 2020</h6>
<p>Initially, this site was far smaller.</p>
</div>
<div slot="afterOverlay" class="overlay after">
<h6>Oct. 20, 2020</h6>
<p>But then forces built up.</p>
</div>
</BeforeAfter>
<style lang="scss">
.overlay {
color: white;
padding: 15px;
background: rgba(0, 0, 0, 0.2);
h6, p {
color: white;
margin: 0;
}
&.after {
text-align: right;
}
}
</style>
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

View file

@ -0,0 +1,35 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
import BodyText from './BodyText.svelte';
import {
withComponentDocs
} from '$lib/docs/utils/withParams.js';
const meta = {
title: 'Components/BodyText',
component: BodyText,
...withComponentDocs(componentDocs),
};
</script>
<Meta {...meta} />
<Template let:args>
<BodyText {...args} />
</Template>
<Story
name="Default"
args={{
text: `Bacon ipsum **dolor amet** cow tongue tri-tip.
Biltong turducken ground round kevin [hamburger turkey](https://reuters.com) pig.
Venison shoulder *ham hock ham leberkas*. Flank beef ribs fatback, jerky meatball ham hock.`
}}
/>

View file

@ -0,0 +1,17 @@
<script lang="ts">
/**
* A markdown text string.
* @type {string}
* @required
*/
export let text: string;
import { marked } from 'marked';
import Section from '../Section/Section.svelte';
</script>
<Section cls="body-text">
{#if text}
{@html marked.parse(text)}
{/if}
</Section>

View file

@ -0,0 +1,15 @@
Parse mardown-formatted text.
```svelte
<script>
import { BodyText } from '@reuters-graphics/graphics-svelte-components';
const markdownText = `Bacon ipsum **dolor amet** cow tongue tri-tip.
Biltong turducken ground round kevin [hamburger turkey](https://reuters.com) pig.
Venison shoulder *ham hock ham leberkas*. Flank beef ribs fatback, jerky meatball ham hock.`;
</script>
<BodyText text="{markdownText}" />
```

View file

@ -0,0 +1,61 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
// @ts-ignore
import withChatterDocs from './stories/docs/withChatter.md?raw';
import DatawrapperChart from './DatawrapperChart.svelte';
import {
withComponentDocs,
withStoryDocs,
} from '$lib/docs/utils/withParams.js';
const meta = {
title: 'Components/DatawrapperChart',
component: DatawrapperChart,
...withComponentDocs(componentDocs),
argTypes: {
width: {
control: 'select',
options: ['normal', 'wide', 'wider', 'widest', 'fluid'],
},
}
};
</script>
<Meta {...meta} />
<Template let:args>
<DatawrapperChart {...args} />
</Template>
<Story
name="Default"
args={{
src: 'https://graphics.reuters.com/USA-ABORTION/lgpdwggnwvo/media-embed.html',
id: 'abortion-rights-map',
ariaLabel: 'map',
title: 'Global abortion access',
}}
/>
<Story name="With chatter" {...withStoryDocs(withChatterDocs)}>
<DatawrapperChart
title='Global abortion access'
ariaLabel='map'
id='abortion-rights-map'
src='https://graphics.reuters.com/USA-ABORTION/lgvdwemlbpo/media-embed.html'
>
<div slot="title" class="title">
<h3>Global abortion access</h3>
<p>A map of worldwide access to abortion.</p>
</div>
<aside slot="notes">
<p class="note">Note: Different indicators and additional restrictions, including different gestational limits, apply in some countries. Refer to source for full classification. Current as of May 4, 2022.</p>
<p class="source">Source: Center for Reproductive Rights</p>
</aside>
</DatawrapperChart>
</Story>

View file

@ -0,0 +1,83 @@
<script lang="ts">
import { onMount } from 'svelte';
/**
* iFrame title
* @required
*/
export let title: string = '';
/**
* iFrame aria label
* @required
*/
export let ariaLabel: string = '';
/** iFrame id */
export let id: string = '';
/**
* Datawrapper embed URL
* @required
*/
export let src: string;
type ScrollingOption = 'auto' | 'yes' | 'no';
/** iFrame scrolling option */
export let scrolling: ScrollingOption = 'no';
type ContainerWidth = 'normal' | 'wide' | 'wider' | 'widest' | 'fluid';
/** Width of the chart within the text well. */
export let width: ContainerWidth = 'normal'; // options: wide, wider, widest, fluid
onMount(() => {
if (typeof window !== 'undefined') {
window.addEventListener('message', function (e) {
if (void 0 !== e.data['datawrapper-height']) {
const t = document.querySelectorAll('iframe');
for (const a in e.data['datawrapper-height']) {
for (let r = 0; r < t.length; r++) {
if (t[r].contentWindow === e.source) {
t[r].style.height = e.data['datawrapper-height'][a] + 'px';
}
}
}
}
});
}
});
</script>
<section class="graphic {width}">
{#if $$slots.title}
<div class="chatter-container">
<!-- Custom headline and chatter slot -->
<slot name="title" />
</div>
{/if}
<div class="datawrapper-chart">
<iframe
title="{title}"
aria-label="{ariaLabel}"
id="{id}"
src="{src}"
scrolling="{scrolling}"
frameborder="0"
style="width: 0; min-width: 100% !important; border: none;"></iframe>
</div>
{#if $$slots.notes}
<div class="chatter-container">
<!-- Custom notes and source slot -->
<slot name="notes" />
</div>
{/if}
</section>
<style lang="scss">
@import '@reuters-graphics/style-theme-eisbaer/scss/components/containers/widths';
.chatter-container {
@extend .well;
}
.datawrapper-chart {
margin: auto;
}
</style>

View file

@ -0,0 +1,25 @@
Easily add a responsive Datawrapper embed on your page.
```svelte
<script>
import { DatawrapperChart } from '@reuters-graphics/graphics-svelte-components';
</script>
<DatawrapperChart
title='Global abortion access'
ariaLabel='map'
id='abortion-rights-map'
src='https://graphics.reuters.com/USA-ABORTION/lgpdwggnwvo/media-embed.html'
/>
```
##### Getting the chart URL for `src`
Copy the source url for the Datawrapper chart in the `src` prop.
You can get this from the published url on Reuters Graphics.
- Publish the chart on Datawrapper.
- Go to the **Datawrapper charts** Teams channel, wait for the graphic to finish publishing.
- Inside **Embed code (for developers only)**, find and copy the url inside the `src` prop. (It ends in `media-embed.html`.)
**Note:** There is no need to update the url if you update the chart inside Datawrapper. Any changes will be automatically reflected.

View file

@ -0,0 +1,4 @@
By default, Datawrapper will export your chart with the title, chatter and notes. At the moment, these
don't match our styles, can't be made to fit into the well and will necessarily stretch to fit the graph width and
can't be modified. You can do all of these things by removing all the text from your Datawrapper chart before
publishing it and instead adding whatever you need inside the component via slots as below.

View file

@ -0,0 +1,31 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
import EmbedPreviewerLink from './EmbedPreviewerLink.svelte';
import {
withComponentDocs
} from '$lib/docs/utils/withParams.js';
const meta = {
title: 'Utilities/EmbedPreviewerLink',
component: EmbedPreviewerLink,
...withComponentDocs(componentDocs),
};
</script>
<Meta {...meta} />
<Template let:args>
<EmbedPreviewerLink {...args} />
</Template>
<Story
name="Default"
args={{
dev: true
}}
/>

View file

@ -0,0 +1,30 @@
<script lang="ts">
export let dev: boolean = false;
import Fa from 'svelte-fa/src/fa.svelte';
import { faWindowRestore } from '@fortawesome/free-regular-svg-icons';
</script>
{#if dev}
<div>
<a rel="external" href="/embed-previewer">
<Fa icon={faWindowRestore} />
</a>
</div>
{/if}
<style lang="scss">
div {
position: fixed;
bottom: 5px;
left: 10px;
font-size: 18px;
a {
color: #ccc;
&:hover {
color: #666;
}
}
}
</style>

View file

@ -0,0 +1,11 @@
An embed tool for development in graphics kit.
```svelte
<script>
import { EmbedPreviewerLink } from '@reuters-graphics/graphics-svelte-components';
import { dev } from '$app/env';
</script>
<EmbedPreviewerLink dev={dev} />
```

View file

@ -0,0 +1,37 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
import EndNotes from './EndNotes.svelte';
import {
withComponentDocs
} from '$lib/docs/utils/withParams.js';
const meta = {
title: 'Components/EndNotes',
component: EndNotes,
...withComponentDocs(componentDocs),
};
</script>
<Meta {...meta} />
<Template let:args>
<EndNotes {...args} />
</Template>
<Story
name="Default"
args={{
text: `### Source
Reuters research.
### Credits
People.`
}}
/>

View file

@ -0,0 +1,16 @@
<script lang="ts">
/**
* A markdown text string.
* @type {string}
* @required
*/
export let text: string;
import { marked } from 'marked';
</script>
<section class="end-notes">
{#if text}
{@html marked.parse(text)}
{/if}
</section>

View file

@ -0,0 +1,17 @@
End notes section.
```svelte
<script>
import { EndNotes } from '@reuters-graphics/graphics-svelte-components';
const markdownText = `### Source
Reuters research.
### Credits
People.`;
</script>
<EndNotes text="{markdownText}" />
```

View file

@ -0,0 +1,56 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
// @ts-ignore
import missingAltTextDocs from './stories/docs/missingAltText.md?raw';
import FeaturePhoto from './FeaturePhoto.svelte';
// @ts-ignore
import sharkSrc from './stories/shark.jpg';
import {
withComponentDocs,
withStoryDocs,
} from '$lib/docs/utils/withParams.js';
const meta = {
title: 'Components/FeaturePhoto',
component: FeaturePhoto,
...withComponentDocs(componentDocs),
argTypes: {
width: {
control: 'select',
options: ['normal', 'wide', 'wider', 'widest', 'fluid'],
},
}
};
</script>
<Meta {...meta} />
<Template let:args>
<FeaturePhoto {...args} />
</Template>
<Story
name="Default"
args={{
src: sharkSrc,
altText: 'A shark!',
width: 'normal',
caption: 'Carcharodon carcharias - REUTERS'
}}
/>
<Story
name="Missing altText"
args={{
src: sharkSrc,
width: 'normal',
caption: 'Carcharodon carcharias - REUTERS'
}}
{...withStoryDocs(missingAltTextDocs)}
/>

View file

@ -0,0 +1,115 @@
<script lang="ts">
import { onMount } from 'svelte';
/**
* Photo src
* @type {string}
* @required
*/
export let src: string;
/**
* Photo altText
* @type {string}
* @required
*/
export let altText: string;
/**
* Caption below the photo
* @type {string}
*/
export let caption: string;
/**
* Height of the photo placeholder when lazy-loading
*/
export let height: number = 100;
type ContainerWidth = 'normal' | 'wide' | 'wider' | 'widest' | 'fluid';
/**
* Width of the container, one of: normal, wide, wider, widest or fluid
*/
export let width: ContainerWidth = 'normal';
/**
* Whether to lazy load the photo using the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)
*/
export let lazy: boolean = false;
/** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `top` when lazy loading. */
export let top = 0;
/** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `bottom` when lazy loading. */
export let bottom = 0;
/** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `left` when lazy loading. */
export let left = 0;
/** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `right` when lazy loading. */
export let right = 0;
let intersecting = false;
let container;
const intersectable = typeof IntersectionObserver !== 'undefined';
onMount(() => {
if (!lazy) return;
if (intersectable) {
const rootMargin = `${bottom}px ${left}px ${top}px ${right}px`;
const observer = new IntersectionObserver(
(entries) => {
intersecting = entries[0].isIntersecting;
if (intersecting) {
observer.unobserve(container);
}
},
{
rootMargin,
}
);
observer.observe(container);
return () => observer.unobserve(container);
}
});
</script>
<figure
class="photo { width }"
bind:this="{container}"
>
{#if !lazy || (intersectable && intersecting)}
<img src="{src}" alt="{altText}" />
{:else}
<div class="placeholder" height="{`${height}px`}"></div>
{/if}
{#if caption}
<figcaption>{caption}</figcaption>
{/if}
{#if (!altText)}
<div class='alt-warning'>Missing altText</div>
{/if}
</figure>
<style lang="scss">
@import "@reuters-graphics/style-main/scss/fonts/mixins";
figure {
position: relative;
div.alt-warning {
@include font-display;
padding: 5px 10px;
background-color: red;
color: white;
position: absolute;
top: 0;
right: 0;
font-size: 14px;
line-height: 16px;
}
}
.placeholder {
background-color: #ccc;
width: 100%;
}
figcaption {
@include font-display;
font-weight: 400;
}
</style>

View file

@ -0,0 +1,16 @@
A full-width photo inside the text well.
```svelte
<script>
import { FeaturePhoto } from '@reuters-graphics/graphics-svelte-components';
import { assets } from '$app/paths'; // 👈 If using in the Graphics Kit...
</script>
<FeaturePhoto
src="{`${assets}/images/myImage.jpg`}"
alt="Some alt text"
caption="A caption"
lazy="{false}"
width="normal"
/>
```

View file

@ -0,0 +1 @@
If your photo is missing `altText` a small warning will overlay the image.

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View file

@ -0,0 +1,33 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
import Framer from './Framer.svelte';
import {
withComponentDocs
} from '$lib/docs/utils/withParams.js';
const meta = {
title: 'Utilities/Framer',
component: Framer,
...withComponentDocs(componentDocs),
};
</script>
<Meta {...meta} />
<Template let:args>
<Framer {...args} />
</Template>
<Story
name="Default"
args={{
embeds: [
'https://graphics.reuters.com/USA-CONGRESS/FUNDRAISING/zjvqkawjlvx/embeds/en/embed/?zzz',
],
}}
/>

View file

@ -0,0 +1,142 @@
<script>
import Fa from 'svelte-fa/src/fa.svelte';
import { faDesktop, faLink } from '@fortawesome/free-solid-svg-icons';
import { onMount, afterUpdate } from 'svelte';
import pym from 'pym.js';
import urljoin from 'proper-url-join';
import Resizer from './Resizer/index.svelte';
import { width } from './stores.js';
import getUniqNames from './uniqNames.js';
export let embeds;
export let breakpoints = [330, 510, 660, 930, 1200];
export let minFrameWidth = 320;
export let maxFrameWidth = 1200;
let activeEmbed = embeds[0];
$: embedTitles = getUniqNames(embeds);
// @ts-ignore
let pymParent;
const reframe = (embed) => {
pymParent = new pym.Parent(
'frame-parent',
/^http/.test(embed)
? embed
: urljoin(window.location.origin, embed, { trailingSlash: true })
);
};
onMount(() => {
reframe(activeEmbed);
});
afterUpdate(() => {
reframe(activeEmbed);
});
</script>
<div class="container">
<header>
<img
src="https://graphics.thomsonreuters.com/style-assets/images/logos/reuters-graphics-logo/svg/graphics-logo-dark.svg"
alt=""
/>
</header>
<nav>
{#each embeds as embed, i}
<button
on:click="{() => {
activeEmbed = embed;
}}"
class:active="{activeEmbed === embed}"
>
{embedTitles[i]}
<a rel="external" target="_blank" href="{embed}" title="{embed}">
<Fa icon="{faLink}" />
</a>
</button>
{/each}
</nav>
<div id="frame-parent" style="width:{$width}px;"></div>
</div>
<div id="home-link">
<a rel="external" href="./../">
<Fa icon="{faDesktop}" />
</a>
</div>
<Resizer
breakpoints="{breakpoints}"
minFrameWidth="{minFrameWidth}"
maxFrameWidth="{maxFrameWidth}"
/>
<style lang="scss">
@import '@reuters-graphics/style-color/scss/thematic/brand';
@import '@reuters-graphics/style-main/scss/fonts/mixins';
header {
@include font-display;
font-size: 50px;
text-align: center;
text-transform: uppercase;
font-weight: 700;
margin: 20px 0;
}
nav {
text-align: center;
margin: 0 auto 20px;
max-width: 900px;
button {
margin: 0 4px 5px;
background-color: transparent;
border: 0;
color: #999;
padding: 2px 2px;
cursor: pointer;
@include font-display;
font-weight: 400;
&.active {
border-bottom: 2px solid #666;
color: #666;
}
&:focus {
outline: none;
}
a {
color: #bbb;
font-size: 12px;
&:hover {
color: #666;
}
}
}
}
#frame-parent {
border: 1px solid #ddd;
margin: 0 auto;
width: var(--width);
}
div#home-link {
position: fixed;
bottom: 5px;
left: 10px;
font-size: 18px;
a {
color: #ccc;
&:hover {
color: #666;
}
}
}
</style>

View file

@ -0,0 +1,228 @@
<script>
import { faDesktop, faMobileAlt } from '@fortawesome/free-solid-svg-icons';
import Fa from 'svelte-fa/src/fa.svelte';
import { width } from './../stores.js';
export let breakpoints = [330, 510, 660, 930, 1200];
export let maxFrameWidth = 1200;
export let minFrameWidth = 320;
let container;
const sliderWidth = 90;
let windowInnerWidth = 1200;
$: minWidth = minFrameWidth;
$: maxWidth = Math.min(windowInnerWidth - 70, maxFrameWidth);
$: pixelRange = maxWidth - minWidth;
$: if ($width > maxWidth) width.set(maxWidth);
$: offset = ($width - minWidth) / pixelRange;
let sliding = false;
let isFocused = false;
const roundToNearestFive = (d) => Math.ceil(d / 5) * 5;
const getPx = () => Math.round(pixelRange * offset + minWidth);
let pixelLabel = null;
const move = (e) => {
if (!sliding || !container) return;
const { left } = container.getBoundingClientRect();
offset = Math.min(Math.max(0, e.pageX - left), sliderWidth) / sliderWidth;
pixelLabel = roundToNearestFive(getPx());
};
const handleKeyDown = (e) => {
if (!isFocused) return;
const { keyCode } = e;
const pixelWidth = sliderWidth / pixelRange;
// right
if (keyCode === 39) {
offset = Math.min(1, offset + pixelWidth / sliderWidth);
// left
} else if (keyCode === 37) {
offset = Math.max(0, offset - pixelWidth / sliderWidth);
}
width.set(getPx());
};
const start = (e) => {
sliding = true;
move(e);
};
const end = () => {
sliding = false;
pixelLabel = null;
width.set(roundToNearestFive(getPx()));
};
const onFocus = () => {
isFocused = true;
};
const onBlur = () => {
isFocused = false;
};
const increment = () => {
const availableBreakpoints = breakpoints
.filter((b) => b <= maxWidth)
.filter((b) => b > $width);
if (availableBreakpoints.length === 0) {
width.set(maxWidth);
} else {
width.set(availableBreakpoints[0]);
}
};
const decrement = () => {
const availableBreakpoints = breakpoints.filter((b) => b < $width);
if (availableBreakpoints.length === 0) {
width.set(minWidth);
} else {
width.set(availableBreakpoints.slice(-1)[0]);
}
};
</script>
<svelte:window
on:mousemove="{move}"
on:mouseup="{end}"
on:keydown="{handleKeyDown}"
bind:innerWidth="{windowInnerWidth}"
/>
<div id="resizer">
<div class="slider">
<div class="label" style="{`opacity: ${sliding || isFocused ? 1 : 0};`}">
{pixelLabel || $width}px
</div>
<button
class="icon left"
disabled="{$width === minWidth}"
on:click="{decrement}"
on:focus="{onFocus}"
on:mouseover="{onFocus}"
on:mouseleave="{onBlur}"
>
<Fa icon="{faMobileAlt}" fw />
</button>
<div class="slider-container" bind:this="{container}">
<div class="track"></div>
<div
class="handle"
tabindex="0"
style="left: calc({offset * 100}% - 5px);"
on:mousedown="{start}"
on:focus="{onFocus}"
on:blur="{onBlur}"
></div>
</div>
<button
class="icon right"
disabled="{$width === maxWidth}"
on:click="{increment}"
on:focus="{onFocus}"
on:mouseover="{onFocus}"
on:mouseleave="{onBlur}"
>
<Fa icon="{faDesktop}" fw />
</button>
</div>
</div>
<style lang="scss">
#resizer {
width: 250px;
position: fixed;
bottom: 0px;
right: 0px;
padding: 15px;
.slider {
width: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
& > div,
button {
display: inline-block;
}
}
div.label {
font-family: monospace;
font-size: 13px;
line-height: 13px;
text-align: center;
transition: opacity 0.2s;
color: grey;
background-color: rgba(255, 255, 255, 0.8);
padding: 4px 8px;
border-radius: 4px;
margin-right: 5px;
}
button.icon {
font-size: 14px;
line-height: 14px;
color: #bbb;
cursor: pointer;
background-color: transparent;
border: 0;
&:active,
&:focus {
outline: none;
}
&:hover {
color: #999;
}
&:active {
transform: translate(1px, 1px);
}
&[disabled] {
color: #ccc;
cursor: default;
&:hover {
color: #ccc;
}
&:active {
transform: translate(0px, 0px);
}
}
&.left {
text-align: right;
padding-right: 3px;
}
&.right {
padding-left: 6px;
text-align: left;
}
}
div.slider-container {
width: 90px;
height: 20px;
position: relative;
div.track {
height: 4px;
width: 100%;
position: absolute;
border-radius: 2px;
top: calc(50% - 2px);
background-color: lightgrey;
}
}
}
.handle {
z-index: 30;
width: 10px;
height: 20px;
cursor: ew-resize;
background: #bbb;
user-select: none;
position: absolute;
border-radius: 4px;
border: 1px solid grey;
top: calc(50% - 10px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
&:active,
&:focus {
outline: none;
}
}
</style>

View file

@ -0,0 +1,3 @@
import { writable } from 'svelte/store';
export const width = writable(660);

View file

@ -0,0 +1,11 @@
An embed tool for development in the Graphics Kit.
```svelte
<script>
import { Framer } from '@reuters-graphics/graphics-svelte-components';
const embeds = ['/embeds/my-chart/index.html'];
</script>
<Framer embeds={embeds} />
```

View file

@ -0,0 +1,54 @@
export default (embeds) => {
const nakedEmbeds = embeds
.map((e) => e.replace(/\?.+$/, ''))
.map((e) => e.replace(/index\.html$/, ''))
.map((e) => e.replace(/^http[s]*:\/\/[\w.]+\.com/, ''));
// If just one, get the last path part
if (nakedEmbeds.length === 1) {
return [
nakedEmbeds[0]
.split('/')
.filter((d) => d)
.slice(-1)[0],
];
}
// If many, test each path part for unique-ness
const test = nakedEmbeds[0];
let replacementForward = 0;
for (const i in test.split('/')) {
const pathPart = test.split('/')[i];
const notUniq = nakedEmbeds.every((e) => e.split('/')[i] === pathPart);
if (notUniq) {
replacementForward += 1;
} else {
break;
}
}
if (replacementForward === test.split('/').length) return nakedEmbeds;
let replacementBackward = 0;
for (const i in test.split('/').reverse()) {
const pathPart = test.split('/').reverse()[i];
const notUniq = nakedEmbeds.every(
(e) => e.split('/').reverse()[i] === pathPart
);
if (notUniq) {
replacementBackward += 1;
} else {
break;
}
}
return nakedEmbeds.map((e) => {
if (replacementBackward > 0) {
return e
.split('/')
.slice(replacementForward, replacementBackward * -1)
.join('/');
}
return e.split('/').slice(replacementForward).join('/');
});
};

View file

@ -0,0 +1,69 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
// @ts-ignore
import withBylineDocs from './stories/docs/withByline.md?raw';
// @ts-ignore
import withCrownDocs from './stories/docs/withCrown.md?raw';
// @ts-ignore
import crownImgSrc from './stories/crown.png';
import Headline from './Headline.svelte';
import {
withComponentDocs,
withStoryDocs,
} from '$lib/docs/utils/withParams.js';
const meta = {
title: 'Components/Headline',
component: Headline,
...withComponentDocs(componentDocs),
};
</script>
<Meta {...meta} />
<Template let:args>
<Headline {...args} />
</Template>
<Story
name="Default"
args={{
section: 'World News',
hed: 'Reuters Graphics interactive'
}}
/>
<Story
name="With byline"
{...withStoryDocs(withBylineDocs)}
>
<Headline
hed="{'Reuters Graphics Interactive'}"
dek="{'The beginning of a beautiful page'}"
section="{'Global news'}"
sectionColour="{'orange'}"
>
<!-- Use named slots to add a byline... -->
<span slot="byline">By <strong>Peppa Pig</strong></span>
<!-- ...and a dateline. -->
<span slot="dateline">Published Jan. 1, 2020</span>
</Headline>
</Story>
<Story
name="With crown"
{...withStoryDocs(withCrownDocs)}
>
<Headline>
<!-- Add a crown -->
<img slot="crown" src="{crownImgSrc}" alt="Illustration of Europe" />
<!-- Override the hed with a named slot -->
<h2 slot="hed" class="spaced font-serif">Europa</h2>
<span slot="dateline">Published Jan. 1, 2020</span>
</Headline>
</Story>

View file

@ -0,0 +1,61 @@
<script lang="ts">
/**
* Headline
*/
export let hed: string = 'Reuters Graphics Interactive';
/**
* Dek
*/
export let dek: string | null = null;
/**
* Section title
*/
export let section: string | null = null;
/**
* Section colour
*/
export let sectionColour: string = 'red';
</script>
<section class="headline">
<!-- Crown named slot -->
<slot name="crown"></slot>
<div class="title">
{#if section}
<p class={`section-title color-${sectionColour}`}>{section}</p>
{/if}
{#if $$slots.hed}
<!-- Headline override named slot -->
<slot name="hed"></slot>
{:else}
<h2>{hed}</h2>
{/if}
{#if dek}
<p>{dek}</p>
{/if}
</div>
{#if ($$slots.byline || $$slots.dateline)}
<aside class="article-metadata">
{#if $$slots.byline}
<div class="byline-container">
<div class="byline">
<!-- Byline named slot -->
<slot name="byline"></slot>
</div>
</div>
{/if}
{#if $$slots.dateline}
<div class="dateline-container">
<div class="published">
<!-- Dateline named slot -->
<slot name="dateline"></slot>
</div>
</div>
{/if}
</aside>
{/if}
</section>
<style lang="scss">
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,14 @@
Reuters Graphics headline
```svelte
<script>
import { Headline } from '@reuters-graphics/graphics-svelte-components';
</script>
<Headline
hed="{'Reuters Graphics Interactive'}"
dek="{'The beginning of a beautiful page'}"
section="{'Global news'}"
sectionColour="{'orange'}"
/>
```

View file

@ -0,0 +1,19 @@
Add a byline and dateline with `byline` and `dateline` named slots.
```svelte
<script>
import { Headline } from '@reuters-graphics/graphics-svelte-components';
</script>
<Headline
hed="{'Reuters Graphics Interactive'}"
dek="{'The beginning of a beautiful page'}"
section="{'Global news'}"
sectionColour="{'orange'}"
>
<!-- Use named slots to add a byline... -->
<span slot="byline">By <strong>Peppa Pig</strong></span>
<!-- ...and a dateline. -->
<span slot="dateline">Published Jan. 1, 2020</span>
</Headline>
```

View file

@ -0,0 +1,16 @@
Add a crown image in the `crown` named slot and override the headline in the `hed` named slot.
```svelte
<script>
import { Headline } from '@reuters-graphics/graphics-svelte-components';
import { assets } from '$app/paths';
</script>
<Headline>
<!-- Add a crown -->
<img slot="crown" src="{`${assets}/images/crown.png`}" />
<!-- Override the hed with a named slot -->
<h2 slot="hed" class="spaced font-serif">Europa</h2>
<span slot="dateline">Published Jan. 1, 2020</span>
</Headline>
```

View file

@ -0,0 +1,37 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
// @ts-ignore
import polarImgSrc from './stories/polar.jpg';
import Hero from './Hero.svelte';
import {
withComponentDocs,
withStoryDocs,
} from '$lib/docs/utils/withParams.js';
const meta = {
title: 'Components/Hero',
component: Hero,
...withComponentDocs(componentDocs),
};
</script>
<Meta {...meta} />
<Template let:args>
<Hero {...args} />
</Template>
<Story
name="Default"
args={{
section: 'World News',
hed: 'Reuters Graphics interactive',
imgSrc: polarImgSrc,
}}
/>

View file

@ -0,0 +1,84 @@
<script lang="ts">
/**
* Headline
*/
export let hed: string = 'Reuters Graphics Interactive';
/**
* Hedline colour
*/
export let hedColour = 'white';
/**
* Dek
*/
export let dek: string | null = null;
/**
* Section title
*/
export let section: string | null = null;
/**
* Section colour
*/
export let sectionColour: string = 'red';
export let imgSrc: string;
export let imgAltText: string;
export let overlay: boolean | string = true;
export let top = false;
export let bottom = false;
export let left = false;
export let right = false;
</script>
<section class="hero-title">
<figure>
{#if $$slots.image}
<slot name='image'></slot>
{:else}
<img src="{imgSrc}" alt="{imgAltText}" />
{/if}
{#if overlay}
<div
class="overlay"
class:lightest={overlay === 'lightest'}
class:lighter={overlay === 'lighter'}
class:light={overlay === 'light'}
class:dark={overlay === 'dark'}
class:darker={overlay === 'darker'}
class:darkest={overlay === 'darkest'}
></div>
{/if}
</figure>
<div
class="title color-white"
class:top={top}
class:bottom={bottom}
class:left={left}
class:right={right}
>
{#if section}
<p class={`section-title color-${sectionColour} text-shadow`}>{section}</p>
{/if}
{#if $$slots.hed}
<slot name="hed"></slot>
{:else}
<h2
class={`text-shadow-darker color-${hedColour} important`}
>{hed}</h2>
{/if}
{#if $$slots.dek}
<slot name="dek"></slot>
{:else}
{#if dek}
<p class="text-shadow-darkest">{dek}</p>
{/if}
{/if}
</div>
</section>

View file

@ -0,0 +1,113 @@
---
title: Hero
description: A headline over a hero image
slug: hero
---
<script>
import DemoContainer from '../_docs/DemoContainer/index.svelte';
import Hero from './index.svelte';
import { assets } from '$app/paths';
</script>
<section>
## {title}
{description}
</section>
```svelte
<script>
import { Headline } from '@reuters-graphics/graphics-svelte-components';
import { assets } from '$app/paths';
</script>
<Hero hed="Eisbär" section="Climate Change" dek="The last of the white bears">
<img slot="image" src="{`${assets}/images/polar.jpg`}" alt="A polar bear" />
</Hero>
```
<DemoContainer>
<Hero
hed='Eisbär'
section='Climate Change'
dek="The last of the white bears"
>
<img slot='image' src={`${assets}/images/polar.jpg`} alt='A polar bear' />
</Hero>
</DemoContainer>
```svelte
<script>
import { Headline } from '@reuters-graphics/graphics-svelte-components';
import { assets } from '$app/paths';
</script>
<!-- Place the title with top, bottom, left & right props-->
<Hero
hed="Eisbär"
section="Climate Change"
dek="The last of the white bears"
overlay="{'darker'}"
bottom
left
>
<img slot="image" src="{`${assets}/images/polar.jpg`}" alt="A polar bear" />
</Hero>
```
<DemoContainer>
<Hero
hed='Eisbär'
section='Climate Change'
dek="The last of the white bears"
overlay={'darker'}
bottom
left
>
<img slot='image' src={`${assets}/images/polar.jpg`} alt='A polar bear' />
</Hero>
</DemoContainer>
```svelte
<script>
import { Headline } from '@reuters-graphics/graphics-svelte-components';
import { assets } from '$app/paths';
</script>
<Hero section="Climate Change" overlay="{false}">
<img slot="image" src="{`${assets}/images/polar.jpg`}" alt="A polar bear" />
<!-- Override the hed and/or dek with named slots -->
<h2 slot="hed" class="uppercase spaced-more color-blue text-shadow-lighter">
Eisbaer
</h2>
<p slot="dek" class="color-blue text-shadow-lighter">
The last of the white bears
</p>
</Hero>
```
<DemoContainer>
<Hero section="Climate Change" overlay={false}>
<img slot="image" src="{`${assets}/images/polar.jpg`}" alt="A polar bear" />
<h2 slot="hed" class="uppercase spaced-more color-blue text-shadow-lighter">Eisbaer</h2>
<p slot="dek" class="color-blue text-shadow-lighter">The last of the white bears</p>
</Hero>
</DemoContainer>
<style lang="scss">
/*Fudging some styles for the demo...*/
:global {
section.hero-title {
figure {
margin: 0 !important;
width: 100% !important;
}
div.title {
padding: 0 15px !important;
}
}
}
</style>

View file

@ -0,0 +1,17 @@
> 🔨 **Under construction**: We're working on this component to make it better. Pardon our mess.
Reuters Graphics headline
```svelte
<script>
import { Headline } from '@reuters-graphics/graphics-svelte-components';
</script>
<Headline
hed="{'Reuters Graphics Interactive'}"
dek="{'The beginning of a beautiful page'}"
section="{'Global news'}"
sectionColour="{'orange'}"
/>
```

View file

@ -0,0 +1,19 @@
Add a byline and dateline with `byline` and `dateline` named slots.
```svelte
<script>
import { Headline } from '@reuters-graphics/graphics-svelte-components';
</script>
<Headline
hed="{'Reuters Graphics Interactive'}"
dek="{'The beginning of a beautiful page'}"
section="{'Global news'}"
sectionColour="{'orange'}"
>
<!-- Use named slots to add a byline... -->
<span slot="byline">By <strong>Peppa Pig</strong></span>
<!-- ...and a dateline. -->
<span slot="dateline">Published Jan. 1, 2020</span>
</Headline>
```

View file

@ -0,0 +1,16 @@
Add a crown image in the `crown` named slot and override the headline in the `hed` named slot.
```svelte
<script>
import { Headline } from '@reuters-graphics/graphics-svelte-components';
import { assets } from '$app/paths';
</script>
<Headline>
<!-- Add a crown -->
<img slot="crown" src="{`${assets}/images/crown.png`}" />
<!-- Override the hed with a named slot -->
<h2 slot="hed" class="spaced font-serif">Europa</h2>
<span slot="dateline">Published Jan. 1, 2020</span>
</Headline>
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View file

@ -0,0 +1,28 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
import PymChild from './PymChild.svelte';
import {
withComponentDocs
} from '$lib/docs/utils/withParams.js';
const meta = {
title: 'Utilities/PymChild',
component: PymChild,
...withComponentDocs(componentDocs),
};
</script>
<Meta {...meta} />
<Template let:args>
<PymChild {...args} />
</Template>
<Story
name="Default"
/>

View file

@ -0,0 +1,14 @@
<script lang="ts">
/** Pym.js polling interval */
export let polling: number = 500;
import { onMount } from 'svelte';
import pym from 'pym.js';
// @ts-ignore
let pymChild;
onMount(() => {
pymChild = new pym.Child({ polling });
});
</script>

View file

@ -0,0 +1,9 @@
A Pym.js child instance for embeddables.
```svelte
<script>
import { PymChild } from '@reuters-graphics/graphics-svelte-components';
</script>
<PymChild polling="{500}" />
```

View file

@ -0,0 +1,32 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
import ReutersLogo from './ReutersLogo.svelte';
import {
withComponentDocs
} from '$lib/docs/utils/withParams.js';
const meta = {
title: 'Components/ReutersLogo',
component: ReutersLogo,
...withComponentDocs(componentDocs),
argTypes: {
logoColour: { control: 'color' },
textColour: { control: 'color' },
}
};
</script>
<Meta {...meta} />
<Template let:args>
<ReutersLogo {...args} />
</Template>
<Story
name="Default"
/>

View file

@ -0,0 +1,22 @@
<script lang="ts">
/** "Kinesis" colour */
export let logoColour: string = '#FA6400';
/** Text colour */
export let textColour: string = '#404040';
/** CSS width value */
export let width: string = '100%';
</script>
<svg
aria-hidden="true"
focusable="false"
viewBox="0 0 301 72"
style="width: {width};"
>
<path
d="M79.231 36.006a4.171 4.171 0 11-8.342 0 4.171 4.171 0 018.342 0zm-12.81-7.292a2.506 2.506 0 100 5.011 2.506 2.506 0 000-5.01zm-2.959-8.744a3.187 3.187 0 100 6.373 3.187 3.187 0 000-6.373zm-5.165-6.632a2.944 2.944 0 100 5.888 2.944 2.944 0 000-5.888zM51.8 9.387a2.51 2.51 0 100 5.02 2.51 2.51 0 000-5.02zm-6.882-1.626a2.123 2.123 0 100 4.245 2.123 2.123 0 000-4.245zm-6.745.297a1.637 1.637 0 100 3.273 1.637 1.637 0 000-3.273zm-5.868 1.416a1.408 1.408 0 100 2.816 1.408 1.408 0 000-2.816zm-5.015 2.238a1.214 1.214 0 100 2.43 1.214 1.214 0 000-2.43zm-4.426 2.904a1.41 1.41 0 100 2.82 1.41 1.41 0 000-2.82zm-4.182 4.064a1.633 1.633 0 100 3.265 1.633 1.633 0 000-3.265zm-3.501 5.452a1.94 1.94 0 100 3.881 1.94 1.94 0 000-3.88zm-2.023 7.008a2.058 2.058 0 100 4.116 2.058 2.058 0 000-4.116zm.143 7.139a2.508 2.508 0 100 5.016 2.508 2.508 0 000-5.016zm2.956 7.387a3.185 3.185 0 100 6.37 3.185 3.185 0 000-6.37zm5.203 7.099a2.942 2.942 0 100 5.883 2.942 2.942 0 000-5.883zm6.27 4.703a2.505 2.505 0 100 5.01 2.505 2.505 0 000-5.01zm7.08 2.544a2.12 2.12 0 100 4.241 2.12 2.12 0 000-4.24zm6.733.666a1.63 1.63 0 100 3.26 1.63 1.63 0 000-3.26zm5.885-.96a1.41 1.41 0 100 2.821 1.41 1.41 0 000-2.82zm5.01-1.86a1.212 1.212 0 100 2.425 1.212 1.212 0 000-2.424zm4.425-3.29a1.415 1.415 0 100 2.829 1.415 1.415 0 000-2.83zm4.189-4.51a1.637 1.637 0 100 3.274 1.637 1.637 0 000-3.273zm3.492-6.07a1.939 1.939 0 100 3.879 1.939 1.939 0 000-3.878zm2.03-7.231a2.048 2.048 0 100 4.096 2.048 2.048 0 000-4.096zm-8.018-2.354a1.943 1.943 0 100 3.887 1.943 1.943 0 000-3.886zm-7.522-1.729a1.1 1.1 0 100 2.201 1.1 1.1 0 000-2.201zm-2.08-5.232a1.513 1.513 0 100 3.026 1.513 1.513 0 000-3.027zm-4.94-4.396a2.066 2.066 0 100 4.13 2.066 2.066 0 000-4.13zm-6.704-.742a2.33 2.33 0 100 4.66 2.33 2.33 0 000-4.66zm-5.678 3.236a2.216 2.216 0 100 4.432 2.216 2.216 0 000-4.432zm-2.91 6.09a1.492 1.492 0 100 2.984 1.492 1.492 0 000-2.985zm.116 5.686a1.098 1.098 0 100 2.197 1.098 1.098 0 000-2.197zm1.971 4.365a1.514 1.514 0 100 3.028 1.514 1.514 0 000-3.028zm4.97 3.369a2.064 2.064 0 100 4.128 2.064 2.064 0 000-4.128zm6.685.171a2.326 2.326 0 100 4.653 2.326 2.326 0 000-4.653zm5.712-3.023a2.222 2.222 0 100 4.444 2.222 2.222 0 000-4.444zm2.9-4.616a1.494 1.494 0 100 2.988 1.494 1.494 0 000-2.988zM57.58 26.61a2.498 2.498 0 100 4.996 2.498 2.498 0 000-4.996zm-4.232-7.013a2.986 2.986 0 100 5.972 2.986 2.986 0 000-5.972zm-6.385-3.924a2.676 2.676 0 100 5.353 2.676 2.676 0 000-5.353zm-7.088-.78a2.35 2.35 0 100 4.7 2.35 2.35 0 000-4.7zm-7.085 2.27a1.758 1.758 0 100 3.516 1.758 1.758 0 000-3.517zm-5.984 4.16a1.618 1.618 0 100 3.234 1.618 1.618 0 000-3.235zm-4.045 5.94a1.624 1.624 0 100 3.248 1.624 1.624 0 000-3.249zm-1.704 6.503a1.936 1.936 0 100 3.873 1.936 1.936 0 000-3.873zm.991 6.655a2.501 2.501 0 100 5.002 2.501 2.501 0 000-5.002zm4.229 6.055a2.98 2.98 0 100 5.96 2.98 2.98 0 000-5.96zm6.416 4.522a2.686 2.686 0 100 5.372 2.686 2.686 0 000-5.372zm7.066 1.415a2.366 2.366 0 100 4.733 2.366 2.366 0 000-4.733zm7.09-1.05a1.758 1.758 0 100 3.516 1.758 1.758 0 000-3.516zm5.992-3.852a1.62 1.62 0 100 3.238 1.62 1.62 0 000-3.238zm4.042-6a1.62 1.62 0 100 3.239 1.62 1.62 0 000-3.238zm16.53-20.253a3.603 3.603 0 100 7.205 3.603 3.603 0 000-7.205zm-4.581-8.281a3.066 3.066 0 100 6.133 3.066 3.066 0 000-6.133zm-6.8-6.497a2.642 2.642 0 100 5.284 2.642 2.642 0 000-5.284zm-7.626-3.932a2.14 2.14 0 100 4.279 2.14 2.14 0 000-4.28zM46.907.534a1.842 1.842 0 100 3.685 1.842 1.842 0 000-3.685zM39.855 0a1.585 1.585 0 100 3.17 1.585 1.585 0 000-3.17zm-7.03.526a1.847 1.847 0 100 3.694 1.847 1.847 0 000-3.693zm-7.5 2.022a2.141 2.141 0 100 4.282 2.141 2.141 0 000-4.282zm-7.633 3.924a2.644 2.644 0 100 5.288 2.644 2.644 0 000-5.288zm-6.801 6.505a3.064 3.064 0 100 6.127 3.064 3.064 0 000-6.127zM6.315 21.27a3.603 3.603 0 100 7.205 3.603 3.603 0 000-7.205zM4.662 31.835a4.171 4.171 0 100 8.342 4.171 4.171 0 000-8.342zm1.655 11.696a3.606 3.606 0 100 7.21 3.606 3.606 0 000-7.21zm4.578 9.374a3.062 3.062 0 100 6.125 3.062 3.062 0 000-6.125zm6.792 7.34a2.644 2.644 0 100 5.288 2.644 2.644 0 000-5.288zm7.638 4.927a2.142 2.142 0 100 4.284 2.142 2.142 0 000-4.284zm7.505 2.616a1.845 1.845 0 100 3.69 1.845 1.845 0 000-3.69zm7.041 1.036a1.588 1.588 0 100 3.176 1.588 1.588 0 000-3.176zm7.03-1.038a1.845 1.845 0 100 3.69 1.845 1.845 0 000-3.69zm7.505-2.61a2.148 2.148 0 100 4.297 2.148 2.148 0 000-4.297zm7.632-4.938a2.646 2.646 0 100 5.292 2.646 2.646 0 000-5.292zm6.8-7.335a3.059 3.059 0 100 6.118 3.059 3.059 0 000-6.118zm4.578-9.366a3.609 3.609 0 100 7.217 3.609 3.609 0 000-7.217z"
fill="{logoColour}"></path>
<path
d="M121.865 50.29c0 .287-.167.497-.498.497h-5.085c-.455 0-.624-.422-.83-.833l-5.997-10.096h-.922c-1.087 0-4.451-.119-5.41-.168v10.264c0 .456-.336.833-.793.833h-4.165a.837.837 0 01-.83-.833V22.667c0-.833.494-1.125 1.333-1.248 2.166-.331 6.82-.537 9.864-.537 6.41 0 12.331 2.291 12.331 9.407v.378c0 4.407-2.204 6.82-5.616 8.146l6.532 11.141a.543.543 0 01.086.336zm-6.746-20c0-3.123-2.422-4.456-6.586-4.456-.878 0-4.666.084-5.41.168v9.026c.661.043 4.695.127 5.41.127 4.284 0 6.586-.826 6.586-4.488v-.378zm144.946 20c0 .287-.166.497-.498.497h-5.084c-.456 0-.625-.422-.83-.833l-5.998-10.096h-.922c-1.087 0-4.45-.119-5.41-.168v10.264c0 .456-.335.833-.793.833h-4.165a.837.837 0 01-.83-.833V22.667c0-.833.494-1.125 1.333-1.248 2.167-.331 6.82-.537 9.865-.537 6.41 0 12.33 2.291 12.33 9.407v.378c0 4.407-2.204 6.82-5.615 8.146l6.532 11.141a.544.544 0 01.085.336zm-6.745-20c0-3.123-2.423-4.456-6.587-4.456-.878 0-4.666.084-5.41.168v9.026c.661.043 4.695.127 5.41.127 4.284 0 6.587-.826 6.587-4.488v-.378zm-121.253 7.603h13.327a.835.835 0 00.833-.827V34.06a.837.837 0 00-.833-.829h-13.327v-3.818c0-3.482.134-3.578 3.284-3.578h10.54a.842.842 0 00.835-.836v-2.66c0-.585-.21-.796-.835-.876-1.332-.204-4.045-.58-8.995-.58-6.912 0-10.578-.048-10.578 8.53v13.174c0 8.579 3.666 8.531 10.578 8.531 4.95 0 7.663-.372 8.995-.583.625-.08.835-.288.835-.875v-2.66a.837.837 0 00-.835-.83h-10.54c-3.15 0-3.284-.094-3.284-3.583v-4.693zm82.266 0h13.326a.835.835 0 00.834-.827V34.06a.837.837 0 00-.834-.829h-13.326v-3.818c0-3.482.133-3.578 3.283-3.578h10.54a.842.842 0 00.836-.836v-2.66c0-.585-.21-.796-.835-.876-1.333-.204-4.045-.58-8.996-.58-6.912 0-10.578-.048-10.578 8.53v13.174c0 8.579 3.666 8.531 10.578 8.531 4.95 0 7.663-.372 8.996-.583.625-.08.835-.288.835-.875v-2.66a.837.837 0 00-.835-.83h-10.541c-3.15 0-3.284-.094-3.284-3.583v-4.693zM177 39.331V22.004c0-.462-.374-.791-.837-.791h-4.121c-.458 0-.83.329-.83.79v17.328c0 4.749-2.192 7-6.69 7-4.489 0-6.685-2.251-6.685-7V22.004c0-.462-.372-.791-.834-.791h-4.114c-.457 0-.836.329-.836.79v17.328c0 8.873 5.77 11.786 12.47 11.786 6.705 0 12.477-2.913 12.477-11.786zm26.923-13.292h-8.304v23.915a.84.84 0 01-.839.833h-4.117a.834.834 0 01-.83-.833V26.04h-8.315c-.455 0-.834-.288-.834-.745v-3.29c0-.462.379-.791.834-.791h22.404c.458 0 .835.33.835.791v3.29c0 .457-.377.745-.834.745zm81.586 16.38c0-3.994-2.27-6.06-5.788-7.48-2.766-1.112-5.196-2.057-7.046-2.785-1.842-.73-3.41-1.993-3.41-3.364 0-1.87 2.319-2.955 5.392-2.955 3.626 0 6.477.506 9.227.757h.084c.414 0 .703-.338.703-.75V22.76c0-.413-.327-.706-.746-.787-1.411-.339-5.044-1.094-9.015-1.094-7.74 0-11.432 3.33-11.432 7.912 0 2.911 1.283 5.781 4.912 7.279 3.636 1.5 11.08 3.228 11.08 6.35 0 2.414-1.124 3.741-4.833 3.741-3.848 0-7.745-.545-9.412-.754h-.083a.793.793 0 00-.789.793v3c0 .458.375.745.79.828 2.002.46 5.735 1.094 9.41 1.094 8.335 0 10.956-4.5 10.956-8.703zm3.039-15.501c0 3.27 2.412 6.038 6.092 6.038 3.68 0 6.119-2.769 6.119-6.038 0-3.296-2.44-6.006-6.12-6.006-3.679 0-6.091 2.71-6.091 6.006zm.887 0c0-2.908 2.049-5.23 5.205-5.23 3.183 0 5.23 2.322 5.23 5.23 0 2.909-2.047 5.263-5.23 5.263-3.156 0-5.204-2.354-5.204-5.263zm7.97 2.69l-1.321-2.25c.69-.267 1.136-.754 1.136-1.644v-.075c0-1.438-1.196-1.9-2.491-1.9-.612 0-1.553.042-1.99.11-.169.023-.269.082-.269.25v5.51a.17.17 0 00.169.167h.84c.091 0 .158-.076.158-.167v-2.073c.195.01.875.035 1.092.035h.187l1.213 2.038c.04.082.074.167.166.167h1.027c.065 0 .1-.043.1-.1 0-.019 0-.043-.017-.067zm-1.345-3.893c0 .738-.464.905-1.331.905-.14 0-.955-.017-1.092-.026v-1.821a21.3 21.3 0 011.092-.034c.842 0 1.331.269 1.331.9v.076z"
fill="{textColour}"></path>
</svg>

View file

@ -0,0 +1,9 @@
The official home of the Reuters logo.
```svelte
<script>
import { ReutersLogo } from '@reuters-graphics/graphics-svelte-components';
</script>
<ReutersLogo />
```

View file

@ -0,0 +1,331 @@
/* eslint-disable */
const attachScript = function (i, s, o, g, r, a, m) {
i.GoogleAnalyticsObject = r;
(i[r] =
i[r] ||
function () {
(i[r].q = i[r].q || []).push(arguments);
}),
(i[r].l = 1 * new Date());
(a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m);
};
/* eslint-enable */
export default (page, title) => {
attachScript(
window,
document,
'script',
'https://www.google-analytics.com/analytics.js',
'ga'
);
window.ga('create', 'UA-41619329-3', { cookieDomain: 'auto' });
window.ga('require', 'linkid', 'linkid.js');
window.ga('send', 'pageview', {
page,
title,
});
if (!inIframe()) {
//start time on page tracking if not in an iframe
riveted.init({
reportInterval: 30,
});
}
};
// checks if page is in an iframe
function inIframe() {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
/*
* START: Riveted time on page tracking code
* see aditional documentation here: https://riveted.parsnip.io/
*/
var riveted = (function () {
var started = false;
var stopped = false;
var turnedOff = false;
var clockTime = 0;
var startTime = new Date();
var clockTimer = null;
var idleTimer = null;
var sendEvent;
var sendUserTiming;
var reportInterval;
var idleTimeout;
var nonInteraction;
var universalGA;
var classicGA;
var googleTagManager;
function init(options) {
/*
* Determine which version of GA is being used
* "ga", "_gaq", and "dataLayer" are the possible globals
*/
if (typeof ga === 'function') {
universalGA = true;
}
if (typeof _gaq !== 'undefined' && typeof _gaq.push === 'function') {
classicGA = true;
}
if (
typeof dataLayer !== 'undefined' &&
typeof dataLayer.push === 'function'
) {
googleTagManager = true;
}
// Set up options and defaults
options = options || {};
reportInterval = parseInt(options.reportInterval, 10) || 5;
idleTimeout = parseInt(options.idleTimeout, 10) || 30;
if (typeof options.eventHandler === 'function') {
sendEvent = options.eventHandler;
}
if (typeof options.userTimingHandler === 'function') {
sendUserTiming = options.userTimingHandler;
}
if (
'nonInteraction' in options &&
(options.nonInteraction === false || options.nonInteraction === 'false')
) {
nonInteraction = false;
} else {
nonInteraction = true;
}
// Basic activity event listeners
addListener(document, 'keydown', trigger);
addListener(document, 'click', trigger);
addListener(window, 'mousemove', throttle(trigger, 500));
addListener(window, 'scroll', throttle(trigger, 500));
// Page visibility listeners
addListener(document, 'visibilitychange', visibilityChange);
addListener(document, 'webkitvisibilitychange', visibilityChange);
//sends initial zero value
sendEvent(0);
}
/*
* Throttle function borrowed from:
* Underscore.js 1.5.2
* http://underscorejs.org
* (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Underscore may be freely distributed under the MIT license.
*/
function throttle(func, wait) {
var context, args, result;
var timeout = null;
var previous = 0;
var later = function () {
previous = new Date();
timeout = null;
result = func.apply(context, args);
};
return function () {
var now = new Date();
if (!previous) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
return result;
};
}
/*
* Cross-browser event listening
*/
function addListener(element, eventName, handler) {
if (element.addEventListener) {
element.addEventListener(eventName, handler, false);
} else if (element.attachEvent) {
element.attachEvent('on' + eventName, handler);
} else {
element['on' + eventName] = handler;
}
}
/*
* Function for logging User Timing event on initial interaction
*/
sendUserTiming = function (timingValue) {
if (googleTagManager) {
dataLayer.push({
event: 'RivetedTiming',
eventCategory: 'Riveted',
timingVar: 'First Interaction',
timingValue: timingValue,
});
} else {
if (universalGA) {
ga('send', 'timing', 'Riveted', 'First Interaction', timingValue);
}
if (classicGA) {
_gaq.push([
'_trackTiming',
'Riveted',
'First Interaction',
timingValue,
null,
100,
]);
}
}
};
/*
* Function for logging ping events
*/
sendEvent = function (time) {
if (googleTagManager) {
dataLayer.push({
event: 'Riveted',
eventCategory: 'Riveted',
eventAction: 'Time Spent',
eventLabel: time,
eventValue: reportInterval,
eventNonInteraction: nonInteraction,
});
} else {
if (universalGA) {
ga(
'send',
'event',
'Riveted',
'Time Spent',
time.toString(),
reportInterval,
{ nonInteraction: nonInteraction }
);
}
if (classicGA) {
_gaq.push([
'_trackEvent',
'Riveted',
'Time Spent',
time.toString(),
reportInterval,
nonInteraction,
]);
}
}
};
function setIdle() {
clearTimeout(idleTimer);
stopClock();
}
function visibilityChange() {
if (document.hidden || document.webkitHidden) {
setIdle();
}
}
function clock() {
clockTime += 1;
if (clockTime > 0 && clockTime % reportInterval === 0) {
sendEvent(clockTime);
}
}
function stopClock() {
stopped = true;
clearTimeout(clockTimer);
}
function turnOff() {
setIdle();
turnedOff = true;
}
function turnOn() {
turnedOff = false;
}
function restartClock() {
stopped = false;
clearTimeout(clockTimer);
clockTimer = setInterval(clock, 1000);
}
function startRiveted() {
// Calculate seconds from start to first interaction
var currentTime = new Date();
var diff = currentTime - startTime;
// Set global
started = true;
// Send User Timing Event
sendUserTiming(diff);
// Start clock
clockTimer = setInterval(clock, 1000);
}
function trigger() {
if (turnedOff) {
return;
}
if (!started) {
startRiveted();
}
if (stopped) {
restartClock();
}
clearTimeout(idleTimer);
idleTimer = setTimeout(setIdle, idleTimeout * 1000 + 100);
}
return {
init: init,
trigger: trigger,
setIdle: setIdle,
on: turnOn,
off: turnOff,
};
})();
/* END: Riveted time on page tracking code */

Some files were not shown because too many files have changed in this diff Show more