├── .babelrc
├── .editorconfig
├── .github
└── workflows
│ └── publish.yml
├── .gitignore
├── .npmignore
├── .prettierrc
├── .storybook
├── config.js
├── global.css
├── main.js
├── manager-head.html
├── theme.js
└── webpack.config.js
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── images
│ └── logo.png
├── src
├── components
│ ├── grid
│ │ ├── Grid.story.js
│ │ └── Grid.tsx
│ ├── html
│ │ ├── Html.story.js
│ │ └── Html.tsx
│ ├── lazyImage
│ │ ├── LazyImage.story.js
│ │ └── LazyImage.tsx
│ ├── modal
│ │ ├── Modal.story.js
│ │ └── Modal.tsx
│ ├── pagination
│ │ ├── Pagination.story.js
│ │ └── Pagination.tsx
│ ├── snackbar
│ │ ├── Snackbar.story.js
│ │ └── Snackbar.tsx
│ └── stickynav
│ │ ├── Stickynav.story.js
│ │ └── Stickynav.tsx
├── core
│ ├── Core.tsx
│ └── CorePreact.tsx
├── library.ts
├── storybook
│ ├── Code.js
│ ├── Description.js
│ ├── Example.js
│ ├── FullWidth.js
│ ├── Hero.js
│ ├── ImportPath.js
│ ├── Page.js
│ ├── config.js
│ ├── lipsum.js
│ └── styles
│ │ └── prism.css
└── welcome
│ ├── Welcome.js
│ └── welcome.story.js
└── tsconfig.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-typescript", "@babel/preset-env", "@babel/preset-react"],
3 | "plugins": [
4 | [
5 | "prismjs",
6 | {
7 | "languages": ["javascript", "css", "typescript", "jsx"],
8 | "plugins": ["line-numbers"],
9 | "theme": "okaidia",
10 | "css": true
11 | }
12 | ]
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | quote_type = "single"
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies and build the source code
2 | # When the workflow succeeds the package will be published
3 |
4 | name: Publish
5 |
6 | on:
7 | push:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | release:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v2
17 | - uses: actions/setup-node@v1
18 | with:
19 | node-version: 12
20 | registry-url: https://registry.npmjs.org/
21 | - run: npm ci
22 | - run: npm run build-storybook
23 | - name: Upload ftp
24 | uses: sebastianpopp/ftp-action@releases/v2
25 | with:
26 | host: ${{ secrets.FTP_SERVER }}
27 | user: ${{ secrets.FTP_USERNAME }}
28 | password: ${{ secrets.FTP_PASSWORD }}
29 | localDir: ".out"
30 | remoteDir: "storybook"
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /dist
3 | /lib
4 | /types
5 | /build
6 | /components
7 | /core
8 | .out
9 |
10 | .idea
11 | .DS_Store
12 |
13 | # Microbundle directories
14 | .rts2_cache_cjs
15 | .rts2_cache_es
16 | .rts2_cache_umd
17 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | public
3 | src
4 | .out
5 | .storybook
6 | .gitignore
7 | .rts2_cache_cjs
8 | .rts2_cache_es
9 | .rts2_cache_modern
10 | .rts2_cache_umd
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "singleQuote": true,
4 | "printWidth": 100,
5 | "tabWidth": 2
6 | }
7 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { configure, addParameters } from '@storybook/react';
3 | import { setPragma } from 'goober';
4 | import theme from './theme';
5 | import './global.css';
6 |
7 | setPragma(React.createElement);
8 |
9 | addParameters({
10 | options: {
11 | theme,
12 | showPanel: false,
13 | isToolshown: false,
14 | panelPosition: 'right',
15 | },
16 | });
17 |
18 | function loadStories() {
19 | // put welcome screen at the top of the list so it's the first one displayed
20 | require('../src/welcome/welcome.story');
21 |
22 | const req = require.context('../src', true, /\.story\.js$/);
23 | req.keys().forEach(filename => req(filename));
24 | }
25 |
26 | configure(loadStories, module);
27 |
--------------------------------------------------------------------------------
/.storybook/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-size: 16px;
3 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell,
4 | Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
5 | margin: 0;
6 | }
7 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | addons: ['@storybook/preset-typescript'],
3 | };
4 |
--------------------------------------------------------------------------------
/.storybook/manager-head.html:
--------------------------------------------------------------------------------
1 |
Domparty Component Library
2 |
3 |
4 |
5 |
9 |
10 |
11 |
15 |
16 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.storybook/theme.js:
--------------------------------------------------------------------------------
1 | import { create } from '@storybook/theming';
2 |
3 | const light = '#FFFFFF';
4 | const primary = 'black';
5 | const secondary = '#C295D8';
6 |
7 | export default create({
8 | base: 'light',
9 |
10 | colorPrimary: primary,
11 | colorSecondary: secondary,
12 |
13 | // UI
14 | appBg: '#000000',
15 | appContentBg: '#000000',
16 | appBorderColor: light,
17 | appBorderRadius: 0,
18 |
19 | // Text colors
20 | textColor: '#ffffff',
21 | textInverseColor: '#343434',
22 |
23 | // Typography
24 | fontBase:
25 | '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif',
26 |
27 | // Toolbar default and active colors
28 | barTextColor: primary,
29 | barSelectedColor: secondary,
30 | barBg: 'black',
31 |
32 | // Form colors
33 | inputBg: 'white',
34 | inputBorder: 'silver',
35 | inputTextColor: '#ffffff',
36 | inputBorderRadius: 4,
37 |
38 | brandTitle: '@domparty/fe',
39 | brandUrl: 'https://github.com/domparty/component-library',
40 | brandImage: '/images/logo.png',
41 | });
42 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | // Export a function. Accept the base config as the only param.
4 | module.exports = async ({ config, mode }) => {
5 | // Remove the existing css rule
6 | config.module.rules = config.module.rules.filter(
7 | f => f.test.toString() !== '/\\.css$/'
8 | );
9 |
10 | // Make whatever fine-grained changes you need
11 | config.module.rules.push({
12 | test: /\.css$/,
13 | loaders: ['style-loader', 'css-loader'],
14 | include: path.resolve(__dirname, '../'),
15 | });
16 |
17 | // Return the altered config
18 | return config;
19 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Domparty - Component library
2 |
3 | A white label component library, ready for you to style. Unbiased on how styling should look on your
4 | website, while giving you the handles to kick-start you application with some helpful functional components.
5 |
6 | ## Instructions
7 |
8 | Install the component library using `npm i @domparty/fe`. This will make the library available for your project.
9 | Because @domparty/fe uses Goober internally to provide all neat CSS tricks, first implement our provider into your application.
10 |
11 | ### Using Preact?
12 |
13 | If you're more into speedy apps, we have also implemented a provider for you! Instead of importing the provider from `@domparty/fe/core`,
14 | import the provider from `@domparty/fe/core/preact`. This way your application is using the Pragma from Preact, instead of React.
15 |
16 | ### Default React apps
17 |
18 | For default React apps, the following snippet can be used.
19 | _use `import { Provider } from '@domparty/fe/core/preact';` for Preact_
20 |
21 | ```javascript
22 | import React from 'react';
23 | import { Provider } from '@domparty/fe/core';
24 |
25 | export default () => (
26 |
27 |
28 |
29 | );
30 | ```
31 |
32 | ### Next.js apps
33 |
34 | To implement @domparty/fe into Next.js make sure the \_app.js file implements the component.
35 |
36 | ```javascript
37 | import React from 'react';
38 | import { Provider } from '@domparty/fe/core';
39 |
40 | export default ({ Component, pageProps }) => (
41 |
42 |
43 |
44 | );
45 | ```
46 |
47 | ## SSR (server-side rendering)
48 |
49 | To make sure all styles are rendered correctly on the server. The component library exports Goobers' `extractCss` function for you to implement.
50 |
51 | To use this feature in Next.js apps, make sure the `getInitialProps` in your \_document file uses this function.
52 |
53 | ```javascript
54 | import Document, { Head, Main, NextScript } from 'next/document';
55 | import { extractCss } from '@domparty/fe/core';
56 |
57 | export default class MyDocument extends Document {
58 | static getInitialProps({ renderPage }) {
59 | const page = renderPage();
60 |
61 | // Extrach the css for each page render
62 | const css = extractCss();
63 | return { ...page, css };
64 | }
65 |
66 | render() {
67 | return (
68 |
69 |
70 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | );
82 | }
83 | }
84 | ```
85 |
86 | # License
87 |
88 | [MIT](https://oss.ninja/mit/domparty/)
89 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@domparty/fe",
3 | "version": "0.0.75",
4 | "description": "Our white-label front-end component library. Filled with functional components to help you kick-start your project 🚀",
5 | "main": "components/index.js",
6 | "scripts": {
7 | "start": "start-storybook -p 9000 -s public",
8 | "build-storybook": "build-storybook -s public -c .storybook -o .out",
9 | "prepublish": "npm run build",
10 | "build": "rm -rf ./dist && npm run build:components && npm run build:core && npm run build:preactcore",
11 | "build:components": "microbundle ./src/library.ts -o ./components/index.js --name @domparty/fe --no-compress --jsx=React.createElement",
12 | "build:core": "microbundle ./src/core/Core.tsx -o ./core/index.js --name Core --no-compress --jsx=React.createElement",
13 | "build:preactcore": "microbundle ./src/core/CorePreact.tsx -o ./core/preact/index.js --no-compress --name CorePreact --jsx=h"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/domparty/component-library.git"
18 | },
19 | "author": "The Domparty team",
20 | "license": "ISC",
21 | "bugs": {
22 | "url": "https://github.com/domparty/component-library/issues"
23 | },
24 | "homepage": "https://github.com/domparty/component-library#readme",
25 | "devDependencies": {
26 | "@babel/cli": "^7.8.4",
27 | "@babel/core": "^7.9.0",
28 | "@babel/preset-env": "^7.9.0",
29 | "@babel/preset-react": "^7.9.4",
30 | "@babel/preset-typescript": "^7.9.0",
31 | "@storybook/preset-typescript": "^3.0.0",
32 | "@storybook/react": "^5.3.17",
33 | "@types/react": "^16.9.26",
34 | "@types/react-dom": "^16.9.5",
35 | "babel-loader": "^8.1.0",
36 | "babel-plugin-prismjs": "^2.0.1",
37 | "copyclip": "0.0.2",
38 | "fork-ts-checker-webpack-plugin": "^4.1.2",
39 | "microbundle": "^0.12.0-next.8",
40 | "prettier": "^2.0.2",
41 | "prismjs": "^1.20.0",
42 | "ts-loader": "^6.2.2",
43 | "typescript": "^3.8.3"
44 | },
45 | "dependencies": {
46 | "@charlietango/use-native-lazy-loading": "^1.7.0",
47 | "goober": "^1.7.1",
48 | "preact": "^10.3.4",
49 | "react": "^16.13.1",
50 | "react-dom": "^16.13.1",
51 | "react-intersection-observer": "^8.26.1"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/domparty/component-library/d00a87a7f3194ca74d7748b3f10c1eaa7c431b2b/public/favicon.ico
--------------------------------------------------------------------------------
/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/domparty/component-library/d00a87a7f3194ca74d7748b3f10c1eaa7c431b2b/public/images/logo.png
--------------------------------------------------------------------------------
/src/components/grid/Grid.story.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import React from 'react';
3 | import Grid from './Grid.tsx';
4 | import Hero from '../../storybook/Hero';
5 | import Example from '../../storybook/Example';
6 | import Description from '../../storybook/Description';
7 | import Page from '../../storybook/Page';
8 | import lipsum from '../../storybook/lipsum';
9 |
10 | const div = (columns = 1, color = '#C295D8', height = 30) => (
11 |
12 | );
13 |
14 | const simpleExample = `
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | `;
23 |
24 | const advancedExample = `
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | `;
61 |
62 | storiesOf('@domparty|Components', module).add('Grid', (data) => (
63 | <>
64 |
65 |
66 |
67 | {lipsum}
68 |
69 |
70 |
71 | {div(2)}
72 | {div(1)}
73 | {div(1)}
74 | {div(2)}
75 | {div(3)}
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | {div(4)}
84 | {div(1)}
85 | {div(1)}
86 | {div(1)}
87 | {div(1)}
88 | {div(2 / 3)}
89 | {div(2 / 3)}
90 | {div(2 / 3)}
91 | {div(2 / 3)}
92 | {div(2 / 3)}
93 | {div(2 / 3)}
94 | {div(0.5)}
95 | {div(0.5)}
96 | {div(0.5)}
97 | {div(0.5)}
98 | {div(0.5)}
99 | {div(0.5)}
100 | {div(0.5)}
101 | {div(0.5)}
102 |
103 |
104 |
105 |
106 | {div(2)}
107 | {div(1)}
108 | {div(1)}
109 | {div(2)}
110 |
111 |
112 |
113 |
114 |
115 | >
116 | ));
117 |
--------------------------------------------------------------------------------
/src/components/grid/Grid.tsx:
--------------------------------------------------------------------------------
1 | import React, { cloneElement, ReactElement } from 'react';
2 | import { css } from 'goober';
3 |
4 | export interface GridProps {
5 | children: ReactElement[];
6 | breakpoints?: number[];
7 | columns?: null | number | number[];
8 | gap?: number;
9 | fullHeight?: boolean;
10 | style?: {};
11 | slices?: any;
12 | className?: string | null;
13 | }
14 |
15 | export default function Grid({
16 | children,
17 | breakpoints = [],
18 | columns = null,
19 | gap = 0,
20 | fullHeight = false,
21 | style: innerStyle = {},
22 | className = null,
23 | }: GridProps) {
24 | if (typeof children === 'undefined') return null;
25 | const childs = Array.isArray(children) === false ? [children] : children;
26 | if (breakpoints.length !== 0 && breakpoints[0] !== 0) breakpoints.unshift(0);
27 |
28 | if (columns === null) columns = 1;
29 |
30 | const wrapperStyle = {
31 | display: 'flex',
32 | flexWrap: 'wrap',
33 | margin: -(gap / 2),
34 | width: '100%',
35 | ...innerStyle,
36 | ...(fullHeight
37 | ? {
38 | position: 'relative',
39 | height: `calc(100% + ${gap}px)`,
40 | }
41 | : {}),
42 | };
43 |
44 | function renderChild(c) {
45 | const dataColumns = c.props['data-columns'] || 1;
46 | const externalClassname = c.props['className'] || false;
47 |
48 | const className = css`
49 | ${typeof columns === 'number'
50 | ? `
51 | width: calc(${(100 / columns) * dataColumns}% - ${gap}px);
52 | margin: ${gap / 2}px;
53 | `
54 | : ''}
55 | ${breakpoints
56 | .map((bp, i) => {
57 | const columnAmount =
58 | Array.isArray(columns) && typeof columns[i] !== 'undefined' ? columns[i] : 1;
59 | const gapDivided = gap / 2;
60 | const marginLeft = gapDivided;
61 | const marginRight = gapDivided;
62 | const widthEach = 100 / columnAmount;
63 | return `
64 | ${bp === 0 ? '' : `@media(min-width: ${bp}px) {`}
65 | width: calc(${widthEach * dataColumns}% - ${gap}px);
66 | margin-bottom: ${gapDivided}px;
67 | margin-top: ${gapDivided}px;
68 | margin-left: ${marginLeft}px;
69 | margin-right: ${marginRight}px;
70 | ${bp === 0 ? '' : '}'}
71 | `;
72 | })
73 | .join('')}
74 | `;
75 |
76 | return cloneElement(c, {
77 | className: `${className}${externalClassname ? ` ${externalClassname}` : ''}`,
78 | });
79 | }
80 |
81 | const wrapperProps = {
82 | ...(className ? { className, style: wrapperStyle } : { style: wrapperStyle }),
83 | };
84 |
85 | return (
86 | // @ts-ignore
87 | {childs.map(renderChild)}
88 | );
89 | }
90 |
--------------------------------------------------------------------------------
/src/components/html/Html.story.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/domparty/component-library/d00a87a7f3194ca74d7748b3f10c1eaa7c431b2b/src/components/html/Html.story.js
--------------------------------------------------------------------------------
/src/components/html/Html.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 |
3 | export interface HtmlProps {
4 | children: ReactNode;
5 | className?: string;
6 | comp?: string;
7 | }
8 |
9 | export default function Html({ className, children, comp: C = 'div' }: HtmlProps) {
10 | if (typeof children === 'string') {
11 | const tags = ['script', 'style', 'iframe'];
12 | const regex = `<(${tags.join('|')})[^>]*>.*(${tags.join('|')})>`;
13 | children = children.replace(new RegExp(regex), '');
14 | }
15 |
16 | return (
17 | // @ts-ignore for dangerouslySetInnerHTML and IntrinsicAttributes
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/lazyImage/LazyImage.story.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import React from 'react';
3 | import Hero from '../../storybook/Hero';
4 | import Example from '../../storybook/Example';
5 | import Description from '../../storybook/Description';
6 | import Page from '../../storybook/Page';
7 | import LazyImage from './LazyImage';
8 | import lipsum from '../../storybook/lipsum';
9 |
10 | const example = `
11 |
12 |
13 |
14 | `;
15 |
16 | storiesOf('@domparty|Components', module).add('LazyImage', (data) => (
17 | <>
18 |
19 |
20 |
21 | {lipsum}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | >
30 | ));
31 |
--------------------------------------------------------------------------------
/src/components/lazyImage/LazyImage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import useNativeLazyLoading from '@charlietango/use-native-lazy-loading';
3 | import { useInView } from 'react-intersection-observer';
4 |
5 | export default function LazyImage({ height, width, src, backgroundColor = '#ffffff', ...rest }) {
6 | const supportsLazyLoading = useNativeLazyLoading();
7 | const [ref, inView] = useInView({
8 | triggerOnce: true,
9 | threshold: 0,
10 | });
11 |
12 | return (
13 |
21 | {inView || supportsLazyLoading ? (
22 |
30 | ) : null}
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/modal/Modal.story.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import React, { useState } from 'react';
3 | import Hero from '../../storybook/Hero';
4 | import Example from '../../storybook/Example';
5 | import Description from '../../storybook/Description';
6 | import Page from '../../storybook/Page';
7 | import Modal from './Modal';
8 | import lipsum from '../../storybook/lipsum';
9 | import { css } from 'goober';
10 |
11 | const example = `
12 | const [visible, setVisible] = useState(false);
13 |
14 | setVisible(true)}>Show modal
15 | setVisible(false)}
18 | visible={visible}
19 | >
20 | {({ close }) => (
21 |
22 | ${lipsum}
23 |
27 | close
28 |
29 |
30 | )}
31 |
32 | `;
33 |
34 | const interfaces = `
35 | type ModalRenderProps = {
36 | close: (callback) => void;
37 | };
38 |
39 | interface ModalProps {
40 | children: (ModalRenderProps) => React.ReactElement;
41 | onClose: () => any;
42 | visible: boolean;
43 | background?: string;
44 | className?: string;
45 | }
46 | `;
47 |
48 | storiesOf('@domparty|Components', module).add('Modal', (data) => {
49 | const [visible, setVisible] = useState(false);
50 |
51 | return (
52 | <>
53 |
54 |
55 |
56 | {lipsum}
57 |
58 |
59 | setVisible(true)}>Show modal
60 | setVisible(false)}
67 | visible={visible}
68 | >
69 | {({ close }) => (
70 | <>
71 | {lipsum}
72 | {lipsum}
73 | {lipsum}
74 | {lipsum}
75 |
81 | close
82 |
83 | >
84 | )}
85 |
86 |
87 |
88 | >
89 | );
90 | });
91 |
--------------------------------------------------------------------------------
/src/components/modal/Modal.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { createPortal } from 'react-dom';
3 | import { styled, css } from 'goober';
4 |
5 | const selector = 'domparty-portal';
6 |
7 | interface BackdropInterface {
8 | background: string;
9 | onClick: () => void;
10 | }
11 |
12 | interface InnerInterface {
13 | className?: string;
14 | }
15 |
16 | const Backdrop = styled('div')`
17 | top: 0;
18 | left: 0;
19 | width: 100%;
20 | height: 100%;
21 | background: ${(props) => (props.background ? props.background : 'rgba(0, 0, 0, 0.3)')};
22 | position: absolute;
23 | `;
24 |
25 | const ModalWrapper = styled('div')`
26 | top: 0;
27 | left: 0;
28 | width: 100%;
29 | height: 100%;
30 | position: fixed;
31 | display: flex;
32 | align-items: center;
33 | justify-content: center;
34 | z-index: 100;
35 | `;
36 |
37 | const ModalInner = styled('div')`
38 | background: white;
39 | padding: 20px;
40 | overflow: scroll;
41 | z-index: 110;
42 | `;
43 |
44 | const bodyClass = css`
45 | overflow: hidden;
46 | `;
47 |
48 | function Portal({ children, domNode, background, close, className }) {
49 | return createPortal(
50 | <>
51 |
52 |
53 | {null}
54 |
55 | {children}
56 |
57 | >,
58 | domNode
59 | );
60 | }
61 |
62 | type ModalRenderProps = {
63 | close: (callback) => void;
64 | };
65 |
66 | interface ModalProps {
67 | children: (ModalRenderProps) => React.ReactElement;
68 | onClose: () => any;
69 | visible: boolean;
70 | background?: string;
71 | className?: string;
72 | }
73 |
74 | export default function Modal({
75 | children,
76 | visible = false,
77 | onClose = () => {},
78 | background,
79 | className,
80 | }: ModalProps) {
81 | const [innerVisible, setInnerVisible] = useState(visible);
82 | const [domNode, setDomNode] = useState(null);
83 |
84 | // Create required div and handle escape key
85 | useEffect(() => {
86 | let div = document.getElementById(selector);
87 | if (div === null) {
88 | div = document.createElement('div');
89 | div.id = selector;
90 | document.body.append(div);
91 | }
92 |
93 | setDomNode(div);
94 |
95 | function listenForEscape(e) {
96 | if (e.keyCode === 27) close();
97 | }
98 |
99 | document.addEventListener('keyup', listenForEscape);
100 | return function cleanup() {
101 | document.removeEventListener('keyup', listenForEscape);
102 | };
103 | }, []);
104 |
105 | // Listen to visible changes
106 | useEffect(() => {
107 | if (visible) document.body.classList.add(bodyClass);
108 | if (visible === false) document.body.classList.remove(bodyClass);
109 |
110 | setInnerVisible(visible);
111 | }, [visible]);
112 |
113 | function close() {
114 | setInnerVisible(false);
115 | if (typeof onClose === 'function') onClose();
116 | }
117 |
118 | return (
119 | domNode &&
120 | innerVisible &&
121 | Portal({ children: children({ close }), domNode, background, close, className })
122 | );
123 | }
124 |
--------------------------------------------------------------------------------
/src/components/pagination/Pagination.story.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import React from 'react';
3 | import Hero from '../../storybook/Hero';
4 | import Example from '../../storybook/Example';
5 | import Description from '../../storybook/Description';
6 | import Page from '../../storybook/Page';
7 | import Pagination from './Pagination';
8 | import lipsum from '../../storybook/lipsum';
9 |
10 | const example = `
11 | const characters = [
12 | { name: 'Delores' },
13 | { name: 'Teddy' },
14 | { name: 'Bernard' },
15 | { name: 'Maeve' },
16 | { name: 'Logan' },
17 | { name: 'Hector' },
18 | { name: 'William' },
19 | ];
20 |
21 |
22 | {({ items, page, pages, pageAmount }) => (
23 | <>
24 | The active items on page {page} are:
25 | {items.map((item) => (
26 | {item.name}
27 | ))}
28 | {pages.map((p) => (
29 |
33 | {p.number}
34 |
35 | ))}
36 |
37 | In total, there are {pageAmount} pages
38 | >
39 | )}
40 |
41 | `;
42 |
43 | const interfaces = `
44 | type Page = {
45 | number: number;
46 | onClick: () => void;
47 | };
48 |
49 | type PaginationRenderProps = {
50 | items: [any];
51 | page: number;
52 | pages: [Page];
53 | pageAmount: number;
54 | };
55 |
56 | interface PaginationProps {
57 | items: [any];
58 | children: (PaginationRenderProps) => React.ReactElement;
59 | itemsPerPage?: number;
60 | page?: number;
61 | disableUrlUpdates?: boolean;
62 | pageParam?: string;
63 | }
64 | `;
65 |
66 | const characters = [
67 | { name: 'Delores' },
68 | { name: 'Teddy' },
69 | { name: 'Bernard' },
70 | { name: 'Maeve' },
71 | { name: 'Logan' },
72 | { name: 'Hector' },
73 | { name: 'William' },
74 | ];
75 |
76 | storiesOf('@domparty|Components', module).add('Pagination', (data) => (
77 | <>
78 |
79 |
80 |
81 | {lipsum}
82 |
83 |
84 |
85 | {({ items, page, pages, pageAmount }) => (
86 | <>
87 | The active items on page {page} are:
88 | {items.map((item) => (
89 | {item.name}
90 | ))}
91 | {pages.map((p) => (
92 |
96 | {p.number}
97 |
98 | ))}
99 |
100 | In total, there are {pageAmount} pages
101 | >
102 | )}
103 |
104 |
105 |
106 | >
107 | ));
108 |
--------------------------------------------------------------------------------
/src/components/pagination/Pagination.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from 'react';
2 |
3 | type Page = {
4 | number: number;
5 | onClick: () => void;
6 | };
7 |
8 | type PaginationRenderProps = {
9 | items: [any];
10 | page: number;
11 | pages: [Page];
12 | pageAmount: number;
13 | };
14 |
15 | interface PaginationProps {
16 | items: [any];
17 | children: (PaginationRenderProps) => React.ReactElement;
18 | itemsPerPage?: number;
19 | page?: number;
20 | disableUrlUpdates?: boolean;
21 | pageParam?: string;
22 | }
23 |
24 | function Pagination({
25 | items: innerItems,
26 | children,
27 | itemsPerPage = 2,
28 | page = 1,
29 | disableUrlUpdates = false,
30 | pageParam = 'page',
31 | }: PaginationProps) {
32 | const regexp = `[\\?|&]${pageParam}=(\\d+)`;
33 | const pageRegex = useRef(new RegExp(regexp));
34 |
35 | const [activePage, setActivePage] = useState(page - 1);
36 | const totalPageAmount = useRef(Math.ceil(innerItems.length / itemsPerPage));
37 |
38 | useEffect(() => {
39 | if (disableUrlUpdates === false) {
40 | const matches = window.location.search.match(pageRegex.current);
41 | if (matches && typeof matches[1] !== 'undefined') {
42 | const p = parseInt(matches[1]);
43 | setActivePage(p > 0 ? p - 1 : 0);
44 | }
45 | }
46 | }, []);
47 |
48 | const items = innerItems.slice(
49 | itemsPerPage * activePage,
50 | itemsPerPage * activePage + itemsPerPage
51 | );
52 |
53 | const pages = [...Array(Math.ceil(innerItems.length / itemsPerPage)).keys()].map((n) => ({
54 | number: n + 1,
55 | onClick: () => onPageClick(n),
56 | }));
57 |
58 | function onPageClick(p) {
59 | if (disableUrlUpdates === false) {
60 | const s = window.location.search.replace(pageRegex.current, '');
61 | const nextQ = s === '' ? `?${pageParam}=${p + 1}` : `${s}&${pageParam}=${p + 1}`;
62 | window.location.search = nextQ;
63 | }
64 |
65 | setActivePage(p);
66 | }
67 |
68 | return children({ items, page: activePage + 1, pages, pageAmount: totalPageAmount.current });
69 | }
70 |
71 | export default Pagination;
72 |
--------------------------------------------------------------------------------
/src/components/snackbar/Snackbar.story.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import React, { useState } from 'react';
3 | import Hero from '../../storybook/Hero';
4 | import Example from '../../storybook/Example';
5 | import Description from '../../storybook/Description';
6 | import Page from '../../storybook/Page';
7 | import Snackbar from './Snackbar';
8 | import lipsum from '../../storybook/lipsum';
9 | import { styled } from 'goober';
10 | import config from '../../storybook/config';
11 |
12 | const example = `
13 |
14 | Marked as favorite
15 |
16 |
17 |
24 | Item already exists
25 |
26 |
27 |
34 | Persistent snackbar
35 |
36 |
37 |
38 | No-inline persistent snackbar
39 |
40 | `;
41 |
42 | const interfaces = `
43 | interface SnackbarProps {
44 | children: React.ReactChild;
45 | persistent?: boolean;
46 | inline?: boolean;
47 | actionLabel?: string | boolean;
48 | actionClick?: () => void;
49 | visibleTime?: number | null;
50 | background?: string;
51 | textColor?: string;
52 | actionColor?: string;
53 | fadeOutTime?: number;
54 | width?: number | string;
55 | }
56 | `;
57 |
58 | const SnackbarWrapper = styled('div')`
59 | height: 80px;
60 | padding: 10px 0;
61 |
62 | @media (min-width: 600px) {
63 | height: 40px;
64 | }
65 | `;
66 |
67 | storiesOf('@domparty|Components', module).add('Snackbar', (data) => {
68 | const [snackbarClosed, setSnackbarClosed] = useState(false);
69 |
70 | function snackbarCallback() {
71 | setSnackbarClosed(true);
72 | }
73 |
74 | return (
75 | <>
76 |
77 |
78 |
79 | {lipsum}
80 | {snackbarClosed && `Snackbar action clicked`}
81 |
82 |
83 |
90 | Marked as favorite
91 |
92 |
93 |
94 |
101 | Item already exists
102 |
103 |
104 |
105 |
112 | Persistent snackbar
113 |
114 |
115 |
116 | No-inline persistent snackbar
117 |
118 |
119 |
120 | >
121 | );
122 | });
123 |
--------------------------------------------------------------------------------
/src/components/snackbar/Snackbar.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { styled } from 'goober';
3 |
4 | interface SnackbarProps {
5 | children: React.ReactChild;
6 | persistent?: boolean;
7 | inline?: boolean;
8 | actionLabel?: string | boolean;
9 | actionClick?: () => void;
10 | visibleTime?: number | null;
11 | background?: string;
12 | textColor?: string;
13 | actionColor?: string;
14 | fadeOutTime?: number;
15 | width?: number | string;
16 | }
17 |
18 | interface SnackbarWrapperProps {
19 | background: string;
20 | textColor: string;
21 | inline: boolean;
22 | visible: boolean;
23 | fadeOutTime: number;
24 | width: number | string;
25 | }
26 |
27 | const SnackbarWrapper = styled('div')`
28 | ${(props) =>
29 | props.inline === false
30 | ? `
31 | position: fixed;
32 | bottom: 10px;
33 | margin: 0 auto;
34 | `
35 | : ''}
36 | width: calc(100% - 120px);
37 | @media (min-width: 600px) {
38 | ${(props) => (props.inline === false ? 'right: 10px' : '')};
39 | width: ${(props) =>
40 | typeof props.width === 'string'
41 | ? props.width.indexOf('%') > -1
42 | ? props.width
43 | : `${props.width}px`
44 | : `${props.width}px`};
45 | }
46 | opacity: ${(props) => (props.visible ? 1 : 0)};
47 | transition: opacity ${(props) => props.fadeOutTime}ms ease-in-out;
48 | display: flex;
49 | flex-direction: row;
50 | align-items: center;
51 | z-index: 10;
52 | border-radius: 8px;
53 | padding: 14px 20px;
54 | color: ${(props) => props.textColor};
55 | background: ${(props) => props.background};
56 | `;
57 |
58 | interface SnackbarActionProps {
59 | onClick: () => void;
60 | color: string;
61 | }
62 |
63 | interface SnackbarInnerProps {
64 | fullWidth: boolean;
65 | }
66 |
67 | const SnackbarInner = styled('div')`
68 | width: ${(props) => (props.fullWidth ? '100%' : 'calc(100% - 100px)')};
69 | `;
70 |
71 | const SnackbarAction = styled('div')`
72 | width: 100px;
73 | color: ${(props) => props.color};
74 | font-weight: bold;
75 | cursor: pointer;
76 | text-align: right;
77 | `;
78 |
79 | function Snackbar({
80 | children,
81 | persistent = false,
82 | inline = false,
83 | actionLabel = 'Dismiss',
84 | actionClick = () => {},
85 | visibleTime = 3000,
86 | background = '#323232',
87 | textColor = '#ffffff',
88 | actionColor = '#ffffff',
89 | fadeOutTime = 200,
90 | width = 400,
91 | }: SnackbarProps) {
92 | const [visible, setVisible] = useState(false);
93 | const [innerVisible, setInnerVisible] = useState(true);
94 |
95 | useEffect(() => {
96 | if (visibleTime !== null && persistent === false) {
97 | window.setTimeout(fadeOut, visibleTime + fadeOutTime);
98 | }
99 |
100 | window.setTimeout(() => setVisible(true), fadeOutTime);
101 | }, []);
102 |
103 | function fadeOut() {
104 | setVisible(false);
105 | window.setTimeout(() => setInnerVisible(false), fadeOutTime);
106 | }
107 |
108 | function onActionClick() {
109 | fadeOut();
110 | if (typeof actionClick === 'function') actionClick();
111 | }
112 |
113 | return (
114 | innerVisible && (
115 |
123 | {children}
124 | {actionLabel && (
125 |
126 | {actionLabel}
127 |
128 | )}
129 |
130 | )
131 | );
132 | }
133 |
134 | export default Snackbar;
135 |
--------------------------------------------------------------------------------
/src/components/stickynav/Stickynav.story.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import React from 'react';
3 | import Hero from '../../storybook/Hero';
4 | import Example from '../../storybook/Example';
5 | import Description from '../../storybook/Description';
6 | import Page from '../../storybook/Page';
7 | import Stickynav from './Stickynav';
8 | import lipsum from '../../storybook/lipsum';
9 |
10 | const example = `
11 |
12 | Item
13 | Item
14 | Item
15 | Item
16 | Item
17 |
18 | `;
19 |
20 | const interfaces = `
21 | interface StickynavProps {
22 | children: ReactNode;
23 | stopAtSelector: string;
24 | offset?: number;
25 | }
26 | `;
27 |
28 | storiesOf('@domparty|Components', module).add('Stickynav', (data) => (
29 | <>
30 |
31 |
32 |
33 | {lipsum}
34 |
35 |
36 |
37 |
38 |
39 | Item
40 | Item
41 | Item
42 | Item
43 | Item
44 |
45 |
46 |
47 |
Title
48 |
{lipsum}
49 |
Another title
50 |
{lipsum}
51 |
52 |
53 |
54 |
55 |
56 | Item
57 | Item
58 | Item
59 | Item
60 | Item
61 |
62 |
63 |
64 |
Title
65 |
{lipsum}
66 |
Another title
67 |
{lipsum}
68 |
69 |
70 |
71 |
72 |
73 | >
74 | ));
75 |
--------------------------------------------------------------------------------
/src/components/stickynav/Stickynav.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useRef, ReactNode } from 'react';
2 |
3 | interface StickynavProps {
4 | children: ReactNode;
5 | stopAtSelector: string;
6 | offset?: number;
7 | }
8 |
9 | export default function Stickynav({ children, stopAtSelector, offset = 0 }: StickynavProps) {
10 | const wrapperRef = useRef(null);
11 | const spanRef = useRef(null);
12 | const spanOffsetTopChange = useRef(null);
13 | const stickyStyles = useRef({ position: 'fixed', top: offset });
14 |
15 | const [sticky, setSticky] = useState(false);
16 | const [stickyAtBottom, setStickyAtBottom] = useState(false);
17 |
18 | useEffect(() => {
19 | function onScroll() {
20 | if (wrapperRef.current !== null && spanRef.current !== null) {
21 | const scrollUntilEl = document.querySelector(stopAtSelector);
22 |
23 | if (scrollUntilEl === null) return;
24 |
25 | const { pageYOffset } = window;
26 |
27 | if (spanOffsetTopChange.current === null) {
28 | // @ts-ignore
29 | spanOffsetTopChange.current = spanRef.current.offsetTop;
30 | }
31 |
32 | // @ts-ignore
33 | let spanY = spanRef.current.offsetTop - pageYOffset;
34 |
35 | let difference = 0;
36 | // @ts-ignore - Something changed that shouldn't change
37 | if (spanOffsetTopChange.current !== spanRef.current.offsetTop) {
38 | // @ts-ignore
39 | difference = spanOffsetTopChange.current - spanRef.current.offsetTop;
40 | }
41 |
42 | spanY += difference;
43 |
44 | const scrollUntilY =
45 | // @ts-ignore
46 | scrollUntilEl.offsetTop - pageYOffset - wrapperRef.current.clientHeight - offset;
47 |
48 | if (spanY < offset && sticky === false) {
49 | return setSticky(true);
50 | }
51 |
52 | if (spanY > offset && sticky === true) {
53 | return setSticky(false);
54 | }
55 |
56 | if (scrollUntilY <= 0 && sticky && stickyAtBottom === false) {
57 | // @ts-ignore
58 | setStickyAtBottom(pageYOffset + offset);
59 | }
60 |
61 | if (stickyAtBottom !== false && scrollUntilY > 0) {
62 | setStickyAtBottom(false);
63 | }
64 | }
65 | }
66 |
67 | onScroll();
68 |
69 | window.addEventListener('scroll', onScroll);
70 |
71 | return function cleanup() {
72 | window.removeEventListener('scroll', onScroll);
73 | };
74 | }, [offset, sticky, stickyAtBottom]);
75 |
76 | const stickyAtBottomStyles = {
77 | position: 'absolute',
78 | top: stickyAtBottom,
79 | };
80 |
81 | return (
82 | <>
83 |
84 |
91 | {children}
92 |
93 | >
94 | );
95 | }
96 |
--------------------------------------------------------------------------------
/src/core/Core.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext } from 'react';
2 | import { setPragma, extractCss as extractStyles } from 'goober';
3 |
4 | // Here is the best spot to call it
5 | setPragma(React.createElement);
6 |
7 | const ctx = createContext({});
8 |
9 | export const extractCss = () => extractStyles();
10 |
11 | export const Provider = ({ value, children }) => {
12 | return {children} ;
13 | };
14 |
--------------------------------------------------------------------------------
/src/core/CorePreact.tsx:
--------------------------------------------------------------------------------
1 | /* @jsx h */
2 | import { h, createContext } from 'preact';
3 | import { setPragma, extractCss as extractStyles } from 'goober';
4 |
5 | // Here is the best spot to call it
6 | setPragma(h);
7 |
8 | const ctx = createContext({});
9 |
10 | export const extractCss = () => extractStyles();
11 |
12 | export const Provider = ({ value, children }) => {
13 | return {children} ;
14 | };
15 |
--------------------------------------------------------------------------------
/src/library.ts:
--------------------------------------------------------------------------------
1 | export { default as Grid } from './components/grid/Grid';
2 | export { default as Image } from './components/lazyImage/LazyImage';
3 | export { default as Html } from './components/html/Html';
4 | export { default as Pagination } from './components/pagination/Pagination';
5 |
--------------------------------------------------------------------------------
/src/storybook/Code.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from 'react';
2 | import Prism from 'prismjs';
3 | import { styled } from 'goober';
4 | import './styles/prism.css';
5 |
6 | const Pre = styled('pre')`
7 | padding: 20px;
8 | width: 100%;
9 | display: block;
10 | background: 'black';
11 | border-bottom: 1px solid #f2f2f2;
12 | font-size: 0.9em;
13 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell,
14 | Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
15 | `;
16 |
17 | export default function Code({ children, type = 'jsx' }) {
18 | const [codeHtml, setCodeHtml] = useState(false);
19 | const c = useRef(children);
20 |
21 | useEffect(() => {
22 | const html = Prism.highlight(c.current.replace(/^\n/, ''), Prism.languages[type], type);
23 | setCodeHtml(html);
24 | });
25 |
26 | return (
27 | codeHtml && (
28 | <>
29 |
30 |
31 |
32 | >
33 | )
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/src/storybook/Description.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { styled } from 'goober';
3 |
4 | const P = styled('p')`
5 | width: 80%;
6 |
7 | @media (min-width: 1100px) {
8 | width: calc((100% / 3) * 2);
9 | }
10 |
11 | padding: 12px 0;
12 | font-size: 1rem;
13 | font-weight: 400;
14 | line-height: 1.5rem;
15 | letter-spacing: 0;
16 | `;
17 |
18 | export default function Description({ children }) {
19 | return {children}
;
20 | }
21 |
--------------------------------------------------------------------------------
/src/storybook/Example.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { styled, css } from 'goober';
3 | import Code from './Code';
4 |
5 | const width = (100 / 3) * 2;
6 |
7 | const titleClass = css`
8 | margin: 0;
9 | display: block;
10 | padding-bottom: 12px;
11 | `;
12 |
13 | const Description = styled('p')`
14 | padding: 12px 0;
15 | `;
16 |
17 | const Wrapper = styled('div')`
18 | width: 80%;
19 |
20 | @media (min-width: 1100px) {
21 | width: ${width}%;
22 | }
23 |
24 | padding: 12px 0;
25 | `;
26 |
27 | function Example({ title, description, children, code, interfaces }) {
28 | return (
29 |
30 | {title &&
{title} }
31 | {description && {description} }
32 | {children}
33 | {code && (
34 | <>
35 | Code example
36 |
37 | {code}
38 |
39 | >
40 | )}
41 | {interfaces && (
42 | <>
43 | Interface
44 |
45 | {interfaces}
46 |
47 | >
48 | )}
49 |
50 | );
51 | }
52 |
53 | export default Example;
54 |
--------------------------------------------------------------------------------
/src/storybook/FullWidth.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { styled } from 'goober';
3 |
4 | const Div = styled('div')`
5 | width: calc(100%);
6 | padding: ${(props) => props.height / 2}px 0px;
7 | background: ${(props) => props.background};
8 | margin: -8px -8px 0 0;
9 | position: relative;
10 | `;
11 |
12 | const Inner = styled('div')`
13 | color: ${(props) => props.color};
14 | margin: 8px 8px 0 8px;
15 | `;
16 |
17 | function FullWidth({ children, height = 250, color = '#ffffff', background = '#000000' }) {
18 | return (
19 |
20 | {children}
21 |
22 | );
23 | }
24 |
25 | export default FullWidth;
26 |
--------------------------------------------------------------------------------
/src/storybook/Hero.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { styled } from 'goober';
3 | import copyclip from 'copyclip';
4 | import ImportPath from './ImportPath';
5 | import config from './config';
6 |
7 | const heroHeight = 250;
8 |
9 | const Div = styled('div')`
10 | width: calc(100%);
11 | height: ${heroHeight}px;
12 | background: black;
13 | margin: -8px -8px 0 -8px;
14 | position: relative;
15 | `;
16 |
17 | const Title = styled('h1')`
18 | color: #ffffff;
19 | margin: 0;
20 | display: block;
21 | padding-top: ${heroHeight / 3}px;
22 | padding-left: 48px;
23 |
24 | @media (min-width: 1100px) {
25 | padding-left: 88px;
26 | }
27 | `;
28 |
29 | const Import = styled('div')`
30 | color: ${config.primaryColor};
31 | padding-top: 24px;
32 | padding-left: 48px;
33 | cursor: pointer;
34 |
35 | @media (min-width: 1100px) {
36 | padding-left: 88px;
37 | }
38 | `;
39 |
40 | function Hero({ title, data = {} }) {
41 | const path = ImportPath({ data });
42 |
43 | function onPathClick() {
44 | copyclip(path);
45 | }
46 |
47 | return (
48 |
49 |
{title}
50 | {path}
51 |
52 | );
53 | }
54 |
55 | export default Hero;
56 |
--------------------------------------------------------------------------------
/src/storybook/ImportPath.js:
--------------------------------------------------------------------------------
1 | const seperator = '|';
2 |
3 | export default function ImportPath({ data }) {
4 | if (typeof data.kind === 'undefined') return null;
5 |
6 | const exploded = data.kind.split(seperator);
7 | return `import { ${data.name} } from '${exploded[0]}/fe/${exploded[1].toLowerCase()}';`;
8 | }
9 |
--------------------------------------------------------------------------------
/src/storybook/Page.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { styled } from 'goober';
3 | import FullWidth from './FullWidth';
4 | import config from './config';
5 |
6 | const P = styled('div')`
7 | padding-left: 40px;
8 | height: ${(props) => (props.height ? props.height : 'auto')};
9 |
10 | @media (min-width: 1100px) {
11 | padding-left: 80px;
12 | }
13 |
14 | background: ${(props) => (props.background ? props.background : '#ffffff')};
15 | `;
16 |
17 | const Link = styled('a')`
18 | color: ${config.primaryColor};
19 | `;
20 |
21 | export default function Page({ children, height = null }) {
22 | return (
23 | <>
24 | {children}
25 |
26 |
27 |
32 | Github project
33 |
34 |
35 |
36 | >
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/src/storybook/config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | primaryColor: '#C295D8',
3 | };
4 |
--------------------------------------------------------------------------------
/src/storybook/lipsum.js:
--------------------------------------------------------------------------------
1 | export default `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam tristique nulla vel ipsum
2 | suscipit rutrum. Aliquam pulvinar lobortis purus, sed vestibulum arcu lacinia et.
3 | Pellentesque pulvinar lacus id tellus ullamcorper dignissim. Duis auctor justo nibh. Mauris
4 | interdum vitae lorem vel lobortis. Maecenas ornare dapibus ultrices. Nulla id ipsum
5 | vehicula, vestibulum erat varius, porta risus. Quisque vitae mattis ligula. Suspendisse
6 | auctor eros non cursus scelerisque. Maecenas dapibus risus ac vehicula euismod. Vestibulum
7 | lobortis blandit convallis. Suspendisse lobortis lacus a justo cursus, vitae fermentum ante
8 | faucibus. Nulla placerat, mi vel semper porttitor, massa nunc dapibus augue, et interdum est
9 | urna non leo.`;
10 |
--------------------------------------------------------------------------------
/src/storybook/styles/prism.css:
--------------------------------------------------------------------------------
1 | code[class*='language-'],
2 | pre[class*='language-'] {
3 | text-align: left;
4 | white-space: pre;
5 | word-spacing: normal;
6 | word-break: normal;
7 | word-wrap: normal;
8 | color: #c3cee3;
9 | background: #263238;
10 | font-family: Roboto Mono, monospace;
11 | font-size: 1em;
12 | line-height: 1.5em;
13 |
14 | -moz-tab-size: 4;
15 | -o-tab-size: 4;
16 | tab-size: 4;
17 |
18 | -webkit-hyphens: none;
19 | -moz-hyphens: none;
20 | -ms-hyphens: none;
21 | hyphens: none;
22 | }
23 |
24 | code[class*='language-']::-moz-selection,
25 | pre[class*='language-']::-moz-selection,
26 | code[class*='language-'] ::-moz-selection,
27 | pre[class*='language-'] ::-moz-selection {
28 | background: #363636;
29 | }
30 |
31 | code[class*='language-']::selection,
32 | pre[class*='language-']::selection,
33 | code[class*='language-'] ::selection,
34 | pre[class*='language-'] ::selection {
35 | background: #363636;
36 | }
37 |
38 | :not(pre) > code[class*='language-'] {
39 | white-space: normal;
40 | border-radius: 0.2em;
41 | padding: 0.1em;
42 | }
43 |
44 | pre[class*='language-'] {
45 | overflow: auto;
46 | position: relative;
47 | margin: 0.5em 0;
48 | padding: 1.25em 1em;
49 | }
50 |
51 | .language-css > code,
52 | .language-sass > code,
53 | .language-scss > code {
54 | color: #fd9170;
55 | }
56 |
57 | [class*='language-'] .namespace {
58 | opacity: 0.7;
59 | }
60 |
61 | .token.atrule {
62 | color: #c792ea;
63 | }
64 |
65 | .token.attr-name {
66 | color: #ffcb6b;
67 | }
68 |
69 | .token.attr-value {
70 | color: #c3e88d;
71 | }
72 |
73 | .token.attribute {
74 | color: #c3e88d;
75 | }
76 |
77 | .token.boolean {
78 | color: #c792ea;
79 | }
80 |
81 | .token.builtin {
82 | color: #ffcb6b;
83 | }
84 |
85 | .token.cdata {
86 | color: #80cbc4;
87 | }
88 |
89 | .token.char {
90 | color: #80cbc4;
91 | }
92 |
93 | .token.class {
94 | color: #ffcb6b;
95 | }
96 |
97 | .token.class-name {
98 | color: #f2ff00;
99 | }
100 |
101 | .token.color {
102 | color: #f2ff00;
103 | }
104 |
105 | .token.comment {
106 | color: #546e7a;
107 | }
108 |
109 | .token.constant {
110 | color: #c792ea;
111 | }
112 |
113 | .token.deleted {
114 | color: #f07178;
115 | }
116 |
117 | .token.doctype {
118 | color: #546e7a;
119 | }
120 |
121 | .token.entity {
122 | color: #f07178;
123 | }
124 |
125 | .token.function {
126 | color: #c792ea;
127 | }
128 |
129 | .token.hexcode {
130 | color: #f2ff00;
131 | }
132 |
133 | .token.id {
134 | color: #c792ea;
135 | font-weight: bold;
136 | }
137 |
138 | .token.important {
139 | color: #c792ea;
140 | font-weight: bold;
141 | }
142 |
143 | .token.inserted {
144 | color: #80cbc4;
145 | }
146 |
147 | .token.keyword {
148 | color: #c792ea;
149 | font-style: italic;
150 | }
151 |
152 | .token.number {
153 | color: #fd9170;
154 | }
155 |
156 | .token.operator {
157 | color: #89ddff;
158 | }
159 |
160 | .token.prolog {
161 | color: #546e7a;
162 | }
163 |
164 | .token.property {
165 | color: #80cbc4;
166 | }
167 |
168 | .token.pseudo-class {
169 | color: #c3e88d;
170 | }
171 |
172 | .token.pseudo-element {
173 | color: #c3e88d;
174 | }
175 |
176 | .token.punctuation {
177 | color: #89ddff;
178 | }
179 |
180 | .token.regex {
181 | color: #f2ff00;
182 | }
183 |
184 | .token.selector {
185 | color: #f07178;
186 | }
187 |
188 | .token.string {
189 | color: #c3e88d;
190 | }
191 |
192 | .token.symbol {
193 | color: #c792ea;
194 | }
195 |
196 | .token.tag {
197 | color: #f07178;
198 | }
199 |
200 | .token.unit {
201 | color: #f07178;
202 | }
203 |
204 | .token.url {
205 | color: #fd9170;
206 | }
207 |
208 | .token.variable {
209 | color: #f07178;
210 | }
211 |
--------------------------------------------------------------------------------
/src/welcome/Welcome.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Hero from '../storybook/Hero';
3 | import Description from '../storybook/Description';
4 | import Page from '../storybook/Page';
5 |
6 | function Welcome() {
7 | return (
8 |
9 |
10 |
11 |
12 | A white label component library, ready for you to style. Unbiased on how styling should
13 | look on your website, while giving you the handles to kick-start you application with some
14 | helpful functional components.
15 |
16 |
17 | This component library focusses fully on performance and delivering small, useful
18 | components.
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | export default Welcome;
26 |
--------------------------------------------------------------------------------
/src/welcome/welcome.story.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import React from 'react';
3 | import Welcome from './Welcome';
4 |
5 | storiesOf('@domparty|Info', module).add('Welcome', () => );
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "skipLibCheck": true,
4 | "target": "esnext",
5 | "module": "esnext",
6 | "noImplicitAny": false,
7 | "lib": ["dom", "dom.iterable", "esnext"],
8 | "jsx": "react",
9 | "declaration": true,
10 | "outDir": "./types",
11 | "rootDir": "./src",
12 | "strict": true,
13 | "moduleResolution": "node",
14 | "allowSyntheticDefaultImports": true,
15 | "esModuleInterop": true
16 | },
17 | "include": ["src/**/*"],
18 | "exclude": ["src/welcome"]
19 | }
20 |
--------------------------------------------------------------------------------