Skip to content

Commit

Permalink
bootstrap: poc
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisrzhou committed Apr 7, 2021
0 parents commit a433c3f
Show file tree
Hide file tree
Showing 40 changed files with 1,086 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
24 changes: 24 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: main
on:
- pull_request
- push
jobs:
main:
name: '${{matrix.node}} on ${{matrix.os}}'
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v2
- uses: dcodeIO/setup-node-nvm@master
with:
node-version: ${{matrix.node}}
- run: npm install
- run: npm test
- uses: codecov/codecov-action@v1
strategy:
matrix:
os:
- ubuntu-latest
- windows-latest
node:
- lts/erbium
- node
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.DS_Store
*.log
coverage/
dist/
node_modules/
package-lock.json

cypress/fixtures/example.json
cypress/screenshots/
cypress/videos/
5 changes: 5 additions & 0 deletions cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"componentFolder": "lib/components",
"experimentalComponentTesting": true,
"video": false
}
4 changes: 4 additions & 0 deletions cypress/plugins/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = (on, config) => {
require('@cypress/react/plugins/react-scripts')(on, config);
return config;
};
1 change: 1 addition & 0 deletions cypress/support/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('@cypress/react/support'); // eslint-disable-line import/no-unassigned-import
12 changes: 12 additions & 0 deletions cypress/util/mount-with-theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export { mountWithTheme };

import { mount } from '@cypress/react';
import React from 'react';

import { Provider } from '../../index.js';

const icons = { x: '<svg></svg>' };

const mountWithTheme = (component) => {
return mount(<Provider icons={icons}>{component}</Provider>);
};
Empty file added index.d.ts
Empty file.
11 changes: 11 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// components
export { Element } from './lib/components/element.js';
export { Icon } from './lib/components/icon.js';
export { Layout } from './lib/components/layout.js';

// context
export { useIcon, useStyles, useTheme } from './lib/context/hooks.js';
export { Provider } from './lib/context/provider.js';

// theme
export { createTheme } from 'uinix-theme';
33 changes: 33 additions & 0 deletions lib/components/element.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export { Element };

import { createElement as e } from 'react';

import { useStyles } from '../context/hooks.js';
import { shorthandStyle } from '../styles/shorthand-style.js';
import { themedStyle } from '../styles/themed-style.js';
import { resolveClassName } from '../util/resolve-class-name.js';
import { resolveDomProps } from '../util/resolve-dom-props.js';

const Element = (props) => {
const {
as = 'div',
children,
className = '',
styles = [],
...restProps
} = props;

const { css, styles: defaultStyles } = useStyles(props);

return e(
as,
{
...resolveDomProps(restProps),
className: resolveClassName(
css(shorthandStyle, themedStyle, defaultStyles.Element, ...styles),
className,
),
},
children,
);
};
40 changes: 40 additions & 0 deletions lib/components/icon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export { Icon };

import { createElement as e } from 'react';
import InlineSvg from 'react-inlinesvg';

import { useIcon, useStyles } from '../context/hooks.js';
import { iconStyle } from '../styles/icon-style.js';
import { Element } from './element.js';

const Icon = (props) => {
const { icon, styles = [], title, ...restProps } = props;

const { styles: defaultStyles } = useStyles(props);
const iconSvg = useIcon(icon);

let SvgElement;
if (iconSvg) {
SvgElement = e(InlineSvg, {
height: '100%',
src: iconSvg,
title: title || icon,
width: '100%',
});
} else {
throw new Error(
`Icon "${icon}" does not exist. Please make sure it is specified in the "icons" Provider value.`,
);
}

return e(
Element,
{
...restProps,
'aria-labelledby': title,
as: props.onClick ? 'button' : 'div',
styles: [iconStyle, ...defaultStyles.Icon, ...styles],
},
SvgElement,
);
};
19 changes: 19 additions & 0 deletions lib/components/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export { Layout };

import { createElement as e } from 'react';

import { useStyles } from '../context/hooks.js';
import { layoutStyle } from '../styles/layout-style.js';
import { Element } from './element.js';

const Layout = (props) => {
const { children, styles = [], ...restProps } = props;

const { styles: defaultStyles } = useStyles(props);

return e(
Element,
{ ...restProps, styles: [layoutStyle, defaultStyles.Layout, ...styles] },
children,
);
};
5 changes: 5 additions & 0 deletions lib/context/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { Context };

import { createContext } from 'react';

const Context = createContext();
63 changes: 63 additions & 0 deletions lib/context/create-renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
export { createRenderer };

import { createRenderer as createFelaRenderer } from 'fela';
import enforceLonghands from 'fela-enforce-longhands';
import monolithic from 'fela-monolithic';
import responsiveValue from 'fela-plugin-responsive-value';
import themeValue from 'fela-plugin-theme-value';
import webPreset from 'fela-preset-web';
import { prop } from 'uinix-fp';
import { themeSpec } from 'uinix-theme';

const createRenderer = (theme, options) => {
const { isAtomicCss } = options;
const themeMapping = createThemeMapping(themeSpec);
const responsiveAttributes = Object.keys(themeMapping).reduce(
(acc, cssProperty) => {
acc[cssProperty] = true;
return acc;
},
{},
);

let enhancers = [enforceLonghands()];
if (!isAtomicCss) {
enhancers.push(monolithic());
}

const plugins = [
// order matters
responsiveValue(
() => Object.values(theme.breakpoints),
responsiveAttributes,
),
themeValue(themeMapping),
...webPreset,
];

const renderer = createFelaRenderer({ enhancers, plugins });

renderStaticStyles(renderer, theme);

return renderer;
};

const createThemeMapping = (themeSpec) => {
return Object.entries(themeSpec).reduce((acc, [themeKey, cssProperties]) => {
cssProperties.forEach((cssProperty) => {
acc[cssProperty] = prop(themeKey);
});
return acc;
}, {});
};

// TODO: https://github.com/robinweser/fela/issues/876
// specifically nested and pseudo selectors and responsiveValue does not work
const renderStaticStyles = (renderer, theme) => {
Object.entries(theme.styles).forEach(([selector, style]) => {
const staticStyle = renderer.plugins.reduce((_acc, plugin) => {
return plugin(style, null, null, { theme });
}, style);
renderer.renderStatic(staticStyle, selector);
});
};
16 changes: 16 additions & 0 deletions lib/context/defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export { defaults };

import { createTheme } from 'uinix-theme';

const defaults = {
icons: {},
options: {
isAtomicCss: false,
},
styles: {
Element: [],
Icon: [],
Layout: [],
},
theme: createTheme(),
};
22 changes: 22 additions & 0 deletions lib/context/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export { useIcon, useStyles, useTheme };

import { useContext } from 'react';
import { useFela } from 'react-fela';

import { Context } from './context.js';

const useIcon = (icon) => {
const { icons } = useContext(Context);
return icons[icon];
};

const useStyles = (props) => {
const { css } = useFela(props);
const { styles } = useContext(Context);
return { css, styles };
};

const useTheme = (props) => {
const { theme } = useFela(props);
return theme;
};
37 changes: 37 additions & 0 deletions lib/context/provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export { Provider };

import { createElement as e } from 'react';
import { RendererProvider, ThemeProvider } from 'react-fela';
import { merge } from 'uinix-fp';
import { createTheme } from 'uinix-theme';

import { Context } from './context.js';
import { createRenderer } from './create-renderer.js';
import { defaults } from './defaults.js';

const Provider = (props) => {
const {
children,
icons: overrideIcons = defaults.icons,
options: overrideOptions = defaults.options,
styles: overrideStyles = defaults.styles,
theme: overrideTheme = defaults.theme,
} = props;

const icons = merge(defaults.icons, overrideIcons);
const options = merge(defaults.options, overrideOptions);
const styles = merge(defaults.styles, overrideStyles);
const theme = createTheme(overrideTheme);

const renderer = createRenderer(theme, options);

return e(
RendererProvider,
{ renderer },
e(
ThemeProvider,
{ theme },
e(Context.Provider, { value: { icons, styles } }, children),
),
);
};
15 changes: 15 additions & 0 deletions lib/styles/icon-style.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export { iconStyle };

const iconStyle = (props) => {
const { size } = props;
return {
alignItems: 'center',
border: 'none',
display: 'inline-flex',
flex: 'none',
height: size,
margin: 0,
padding: 0,
width: size,
};
};
36 changes: 36 additions & 0 deletions lib/styles/layout-style.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export { layoutStyle };

const layoutStyle = (props) => {
const {
theme,
align,
direction,
flex,
inline,
justify,
spacing,
wrapSpacing,
wrap,
} = props;

const marginDirection =
direction === 'column' ? 'marginBottom' : 'marginRight';
const wrapSpacingValue = wrap ? theme.spacings[wrapSpacing] : undefined;

return {
alignItems: align,
display: inline ? 'inline-flex' : 'flex',
flex,
flexDirection: direction,
flexWrap: wrap ? 'wrap' : undefined,
justifyContent: justify,
marginTop: -wrapSpacingValue,
'> :not(:last-child)': {
[marginDirection]: spacing,
marginTop: wrapSpacing,
},
'> :last-child': {
marginTop: wrapSpacing,
},
};
};
Loading

0 comments on commit a433c3f

Please sign in to comment.