@@ -66,6 +66,4 @@ typings/ | |||||
.env | .env | ||||
.next | .next | ||||
dist/ | dist/ | ||||
.gitignore | |||||
.docz/ | |||||
/docz.config.js | |||||
.next/ |
@@ -1,91 +0,0 @@ | |||||
# Tesseract Web - React Common | |||||
Common front-end components for Web using the [Tesseract design system](https://make.modal.sh/tesseract/design), written for [React](https://reactjs.org). | |||||
Package: | |||||
[![@tesseract-design/react-common](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=package&color=C78AB3&kind=name)](https://js.pack.modal.sh/-/web/detail/@tesseract-design/react-common) | |||||
[![@tesseract-design/react-common](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=package&color=C78AB3&kind=version)](https://js.pack.modal.sh/-/web/detail/@tesseract-design/react-common#versions) | |||||
Dependencies: | |||||
[![React](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=react&kind=peerDependencies)](https://github.com/facebook/react) | |||||
[![Styled Components](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=styled-components&kind=peerDependencies)](https://github.com/styled-components/styled-components) | |||||
[![TypeScript](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=typescript&kind=devDependencies)](https://github.com/microsoft/typescript) | |||||
[![Jest](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=jest&kind=devDependencies)](https://github.com/facebook/jest) | |||||
[![Rollup](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=rollup&kind=devDependencies)](https://github.com/rollup/rollup) | |||||
## Installation | |||||
Since this package resides in the [Modal.sh JavaScript Package Registry](https://js.pack.modal.sh/), you may need to | |||||
adjust configuration in your chosen package manager. | |||||
With [Yarn](https://yarnpkg.com), add this to your `.yarnrc` file: | |||||
``` | |||||
"@tesseract-design:registry" "https://js.pack.modal.sh/" | |||||
``` | |||||
Then, install the package by running the following command: | |||||
```shell script | |||||
yarn add @tesseract-design/react-common | |||||
``` | |||||
## Usage | |||||
The package includes components as named exports. Simply import the components you need individually or use a namespace | |||||
import, like so: | |||||
```jsx harmony | |||||
import * as React from 'react' | |||||
import ReactDOM from 'react-dom' | |||||
import * as T from '@tesseract-design/react-common' | |||||
const LoginForm = etcProps => ( | |||||
<form | |||||
{...etcProps} | |||||
> | |||||
<fieldset> | |||||
<legend> | |||||
Log In | |||||
</legend> | |||||
<div> | |||||
<T.TextInput | |||||
block | |||||
label="Username" | |||||
/> | |||||
</div> | |||||
<div> | |||||
<T.TextInput | |||||
block | |||||
type="password" | |||||
label="Password" | |||||
/> | |||||
</div> | |||||
<div> | |||||
<T.Button> | |||||
Log In | |||||
</T.Button> | |||||
</div> | |||||
</fieldset> | |||||
</form> | |||||
) | |||||
const mountNode = window.document.createElement('div') | |||||
ReactDOM.render( | |||||
<LoginForm />, | |||||
mountNode, | |||||
) | |||||
window.document.body.appendChild(mountNode) | |||||
``` | |||||
Detailed usage guides can be found in the [Tesseract Design - React Common documentation](https://make.modal.sh/tesseract/web/react/common). | |||||
## TypeScript | |||||
The package is written and tested using TypeScript. Thus, typings for consumption in TypeScript are bundled with the | |||||
compiled source. | |||||
@@ -0,0 +1 @@ | |||||
packages/react-common-docs/src/pages/index.md |
@@ -5,8 +5,8 @@ module.exports = { | |||||
'./jest.setup.ts', | './jest.setup.ts', | ||||
], | ], | ||||
collectCoverageFrom: [ | collectCoverageFrom: [ | ||||
'./lib/**/*.{ts,tsx}', | |||||
'!./lib/**/*.stories.{ts,tsx}' | |||||
'./packages/**/*.{ts,tsx}', | |||||
'!./packages/**/*.stories.{ts,tsx}' | |||||
], | ], | ||||
preset: 'ts-jest', | preset: 'ts-jest', | ||||
testTimeout: 30000, | testTimeout: 30000, | ||||
@@ -1,25 +0,0 @@ | |||||
--- | |||||
name: Checkbox | |||||
route: /components/checkbox | |||||
menu: Components | |||||
--- | |||||
import { Playground, Props, Link } from 'docz' | |||||
import Checkbox from './Checkbox' | |||||
# Checkbox | |||||
Component for values that have an on/off state. | |||||
<Playground> | |||||
<Checkbox label="Accept Terms" /> | |||||
</Playground> | |||||
## Props | |||||
<Props of={Checkbox} /> | |||||
## See Also | |||||
- <Link to="../select">Select</Link> for a similar component suitable for selecting more values. | |||||
- <Link to="../radiobutton">RadioButton</Link> for a similar component on selecting a single value among very few choices. |
@@ -1,20 +0,0 @@ | |||||
--- | |||||
name: Icon | |||||
route: /components/icon | |||||
menu: Components | |||||
--- | |||||
import { Playground, Props } from 'docz' | |||||
import Icon from './Icon' | |||||
# Icon | |||||
Component for displaying graphics. | |||||
<Playground> | |||||
<Icon name="check" label="OK" size="1.5rem" weight={0.125} /> | |||||
</Playground> | |||||
## Props | |||||
<Props of={Icon} /> |
@@ -1,32 +0,0 @@ | |||||
--- | |||||
name: RadioButton | |||||
route: /components/radiobutton | |||||
menu: Components | |||||
--- | |||||
import { Playground, Props, Link } from 'docz' | |||||
import RadioButton from './RadioButton' | |||||
# RadioButton | |||||
Component for values which are to be selected from a few list of options. | |||||
<Playground> | |||||
<div style={{ display: 'grid', gap: '1rem', }}> | |||||
<div> | |||||
<RadioButton name="flavor" label="Chocolate" /> | |||||
</div> | |||||
<div> | |||||
<RadioButton name="flavor" label="Vanilla" /> | |||||
</div> | |||||
</div> | |||||
</Playground> | |||||
## Props | |||||
<Props of={RadioButton} /> | |||||
## See Also | |||||
- <Link to="../checkbox">Checkbox</Link> for a similar component on selecting values among very few choices. | |||||
- <Link to="../select">Select</Link> for a similar component suitable for selecting more values. |
@@ -1,45 +0,0 @@ | |||||
--- | |||||
name: Select | |||||
route: /components/select | |||||
menu: Components | |||||
--- | |||||
import { Playground, Props, Link } from 'docz' | |||||
import Select from './Select' | |||||
# Select | |||||
Component for selecting values from a larger number of options. | |||||
<Playground> | |||||
<Select> | |||||
<optgroup | |||||
label="Fruits" | |||||
> | |||||
<option value="mango">Mango</option> | |||||
<option value="strawberry">Strawberry</option> | |||||
<option value="blueberry">Blueberry</option> | |||||
</optgroup> | |||||
<optgroup | |||||
label="Classic" | |||||
> | |||||
<option value="chocolate">Chocolate</option> | |||||
<option value="vanilla">Vanilla</option> | |||||
</optgroup> | |||||
</Select> | |||||
</Playground> | |||||
## Props | |||||
<Props of={Select} /> | |||||
## Usage Notes | |||||
The component will behave as `block`, i.e. it takes the remaining of the horizontal space. | |||||
To use the component together with layouts, see [TextInput](./textinput) for examples. Both `Select` and | |||||
`TextInput` have similar strategies on usage with layouts. | |||||
## See Also | |||||
- <Link to="../checkbox">Checkbox</Link> for a similar component on selecting values among very few choices. | |||||
- <Link to="../radiobutton">RadioButton</Link> for a similar component on selecting a single value among very few choices. |
@@ -1,27 +0,0 @@ | |||||
--- | |||||
name: Slider | |||||
route: /components/slider | |||||
menu: Components | |||||
--- | |||||
import { Playground, Props } from 'docz' | |||||
import Slider from './Slider' | |||||
# Slider | |||||
Component for inputting numeric values in a graphical manner. | |||||
The component is styled using client-side scripts. When the component is rendered server-side, | |||||
the component falls back into the original `<input type="range">` element. | |||||
<Playground> | |||||
<Slider /> | |||||
</Playground> | |||||
## Props | |||||
<Props of={Slider} /> | |||||
## See Also | |||||
- [Reach UI Slider](//reacttraining.com/reach-ui/slider/#sliderinput) for the client-side implementation. |
@@ -1,71 +0,0 @@ | |||||
--- | |||||
name: TextInput | |||||
route: /components/textinput | |||||
menu: Components | |||||
--- | |||||
import { Playground, Props, Link } from 'docz' | |||||
import TextInput from './TextInput' | |||||
# TextInput | |||||
Component for inputting textual values. | |||||
<Playground> | |||||
<TextInput label="Username" placeholder="johndoe" hint="the name you use to log in" /> | |||||
</Playground> | |||||
## Props | |||||
<Props of={TextInput} /> | |||||
## Usage Notes | |||||
The component will behave as `block`, i.e. it takes the remaining of the horizontal space. | |||||
To use the component together with layouts, see the following examples. | |||||
### Inline | |||||
The components are surrounded by `inline-block` elements. These surrounding elements have specified widths, which could | |||||
act as guide to the user on how long the expected input values are. | |||||
<Playground> | |||||
<form> | |||||
I am <span style={{ display: 'inline-block', width: '16rem', }}><TextInput label="Full name" hint="given and family name" /></span> and I live in <span style={{ display: 'inline-block', width: '24rem', }}><TextInput label="Address" hint="city, state and country" /></span>. | |||||
</form> | |||||
</Playground> | |||||
### Grid | |||||
It is advisable to put surrounding elements instead of the `TextInput` components themselves as children of the | |||||
element specified as `grid`. This is to be able to add complementing content to the components, if for example there are | |||||
some content that is best displayed outside the component instead of putting in the `hint` prop. | |||||
<Playground> | |||||
<form | |||||
style={{ display: 'grid', gridTemplateColumns: '4fr 4fr 5fr', gap: '1rem', }} | |||||
> | |||||
<div style={{ gridColumnStart: 1, gridColumnEnd: 4, }}> | |||||
<TextInput label="Address line 1" hint="unit/house number, building" /> | |||||
</div> | |||||
<div style={{ gridColumnStart: 1, gridColumnEnd: 4, }}> | |||||
<TextInput label="Address line 2" hint="street, area" /> | |||||
</div> | |||||
<div> | |||||
<TextInput size="large" label="City/Town" /> | |||||
</div> | |||||
<div> | |||||
<TextInput size="large" label="State/Province" /> | |||||
</div> | |||||
<div> | |||||
<TextInput size="large" label="Country" hint="abbreviations are accepted" /> | |||||
<small> | |||||
Consult the <a href="#">fees table</a> for shipping fee details. | |||||
</small> | |||||
</div> | |||||
</form> | |||||
</Playground> | |||||
## See Also | |||||
- <Link to="../select">Select</Link> for a graphically-similar component suitable for selecting more values. |
@@ -1,42 +0,0 @@ | |||||
import * as fc from 'fast-check' | |||||
import isEmpty from './isEmpty' | |||||
describe('lib/services/isEmpty', () => { | |||||
it('should exist', () => { | |||||
expect(isEmpty).toBeDefined() | |||||
}) | |||||
it('should be a function', () => { | |||||
expect(isEmpty).toBeFunction() | |||||
}) | |||||
it('should accept 1 argument', () => { | |||||
expect(isEmpty).toHaveLength(1) | |||||
}) | |||||
it('should return a boolean value', () => { | |||||
fc.assert( | |||||
fc.property(fc.anything(), (v) => { | |||||
expect(typeof isEmpty(v)).toBe('boolean') | |||||
}), | |||||
) | |||||
}) | |||||
describe('on arguments', () => { | |||||
it('should return `true` on an argument with value of `undefined`', () => { | |||||
expect(isEmpty(undefined)).toBe(true) | |||||
}) | |||||
it('should return `true` on an argument with value of `null`', () => { | |||||
expect(isEmpty(null)).toBe(true) | |||||
}) | |||||
it('should return `false` on an argument with value that is neither `undefined` nor `null`', () => { | |||||
fc.assert( | |||||
fc.property(fc.anything().filter((v) => typeof v !== 'undefined' && v !== null), (v) => { | |||||
expect(isEmpty(v)).toBe(false) | |||||
}), | |||||
) | |||||
}) | |||||
}) | |||||
}) |
@@ -12,6 +12,8 @@ | |||||
"license": "MIT", | "license": "MIT", | ||||
"private": false, | "private": false, | ||||
"devDependencies": { | "devDependencies": { | ||||
"@babel/runtime": "^7.12.5", | |||||
"@reach/slider": "^0.10.5", | |||||
"@rollup/plugin-typescript": "^5.0.2", | "@rollup/plugin-typescript": "^5.0.2", | ||||
"@types/enzyme": "^3.10.5", | "@types/enzyme": "^3.10.5", | ||||
"@types/enzyme-adapter-react-16": "^1.0.6", | "@types/enzyme-adapter-react-16": "^1.0.6", | ||||
@@ -29,23 +31,20 @@ | |||||
"jest-axe": "3.4.0", | "jest-axe": "3.4.0", | ||||
"jest-enzyme": "7.1.2", | "jest-enzyme": "7.1.2", | ||||
"jest-extended": "0.11.5", | "jest-extended": "0.11.5", | ||||
"pascal-case": "3.1.1", | |||||
"plop": "2.6.0", | "plop": "2.6.0", | ||||
"prettier": "1.19.1", | "prettier": "1.19.1", | ||||
"react-is": "^16.13.1", | |||||
"prop-types": "15.7.2", | |||||
"react": "16.13.1", | |||||
"react-dom": "16.13.1", | |||||
"react-feather": "2.0.3", | |||||
"rollup": "^2.23.0", | "rollup": "^2.23.0", | ||||
"rollup-plugin-peer-deps-external": "2.2.2", | "rollup-plugin-peer-deps-external": "2.2.2", | ||||
"rollup-plugin-terser": "5.3.0", | "rollup-plugin-terser": "5.3.0", | ||||
"styled-components": "5.1.0", | |||||
"ts-jest": "^26.1.3", | "ts-jest": "^26.1.3", | ||||
"tslib": "^2.0.0", | "tslib": "^2.0.0", | ||||
"typescript": "^3.9.7", | |||||
"@reach/slider": "^0.10.5", | |||||
"docz": "^2.3.1", | |||||
"pascal-case": "3.1.1", | |||||
"prop-types": "15.7.2", | |||||
"react": "16.13.1", | |||||
"react-dom": "16.13.1", | |||||
"react-feather": "2.0.3", | |||||
"styled-components": "5.1.0" | |||||
"typescript": "^3.9.7" | |||||
}, | }, | ||||
"scripts": { | "scripts": { | ||||
"prepublishOnly": "NODE_ENV=production rm -rf dist/ && rollup -c", | "prepublishOnly": "NODE_ENV=production rm -rf dist/ && rollup -c", | ||||
@@ -64,5 +63,6 @@ | |||||
"react-feather": "2.0.3", | "react-feather": "2.0.3", | ||||
"styled-components": "5.1.0" | "styled-components": "5.1.0" | ||||
}, | }, | ||||
"homepage": "https://make.modal.sh/tesseract/web/react/common" | |||||
"homepage": "https://make.modal.sh/tesseract/web/react/common", | |||||
"dependencies": {} | |||||
} | } |
@@ -0,0 +1,40 @@ | |||||
import * as React from 'react' | |||||
import styled from 'styled-components' | |||||
import pkg from '../../package.json' | |||||
const Base = styled('div')({ | |||||
position: 'relative', | |||||
}) | |||||
const Title = styled('strong')({ | |||||
fontSize: '2rem', | |||||
fontFamily: 'var(--font-family-headings), sans-serif', | |||||
fontWeight: 'var(--font-weight-headings, 400)', | |||||
lineHeight: 'var(--line-height-headings, 1.5)', | |||||
fontStretch: 'var(--font-stretch-headings, normal)', | |||||
textTransform: 'lowercase', | |||||
whiteSpace: 'nowrap', | |||||
}) | |||||
const Subtitle = styled('small')({ | |||||
position: 'absolute', | |||||
bottom: '-1em', | |||||
right: 0, | |||||
fontWeight: 'bolder', | |||||
}) | |||||
const Brand = () => { | |||||
const name = pkg.name.includes('@') ? pkg.name.split('/')[1] : pkg.name | |||||
return ( | |||||
<Base> | |||||
<Title> | |||||
{name} | |||||
</Title> | |||||
<Subtitle> | |||||
v.{pkg.version} | |||||
</Subtitle> | |||||
</Base> | |||||
) | |||||
} | |||||
export default Brand |
@@ -0,0 +1,3 @@ | |||||
/// <reference types="next" /> | |||||
/// <reference types="next/types/global" /> | |||||
declare module 'remark-react' |
@@ -0,0 +1,25 @@ | |||||
const path = require('path') | |||||
const withMDX = require('next-mdx-frontmatter')({ | |||||
extension: /\.mdx?$/ | |||||
}) | |||||
const e = withMDX({ | |||||
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'] | |||||
}) | |||||
module.exports = { | |||||
...e, | |||||
webpack(...args) { | |||||
const oldWebpack = e.webpack(...args) | |||||
const [config, { defaultLoaders, }] = args | |||||
config.module.rules.push({ | |||||
test: /\.(ts|tsx)$/, | |||||
include: [path.resolve(__dirname, '../react-common/')], | |||||
use: [defaultLoaders.babel], | |||||
}) | |||||
return oldWebpack | |||||
} | |||||
} |
@@ -0,0 +1,26 @@ | |||||
{ | |||||
"name": "react-common-docs", | |||||
"version": "0.1.0", | |||||
"private": true, | |||||
"scripts": { | |||||
"predev": "./scripts/docgen", | |||||
"dev": "next dev", | |||||
"prebuild": "./scripts/docgen", | |||||
"build": "next build", | |||||
"prestart": "./scripts/docgen", | |||||
"start": "next start", | |||||
"docgen": "./scripts/docgen" | |||||
}, | |||||
"dependencies": { | |||||
"@mdx-js/loader": "^1.6.19", | |||||
"next": "10.0.1", | |||||
"next-mdx-frontmatter": "^0.0.3", | |||||
"pascal-case": "^3.1.1", | |||||
"react-docgen-typescript": "^1.20.5", | |||||
"react-live": "^2.2.3", | |||||
"remark-parse": "^9.0.0", | |||||
"remark-react": "^8.0.0", | |||||
"styled-components": "^5.2.1", | |||||
"unified": "^9.2.0" | |||||
} | |||||
} |
@@ -0,0 +1,52 @@ | |||||
body { | |||||
margin: 0; | |||||
} | |||||
h1 { | |||||
font-size: 3em; | |||||
text-transform: lowercase; | |||||
} | |||||
h2 { | |||||
font-size: 2em; | |||||
text-transform: lowercase; | |||||
} | |||||
h3 { | |||||
font-size: 1.75em; | |||||
text-transform: lowercase; | |||||
} | |||||
h4 { | |||||
text-transform: lowercase; | |||||
} | |||||
h5 { | |||||
text-transform: lowercase; | |||||
} | |||||
h6 { | |||||
text-transform: lowercase; | |||||
} | |||||
p { | |||||
margin: 2em 0; | |||||
} | |||||
small { | |||||
font-size: 0.75em; | |||||
} | |||||
a:focus { | |||||
color: var(--color-active); | |||||
outline: 0; | |||||
} | |||||
::selection { | |||||
background-color: var(--color-active); | |||||
color: var(--color-fg); | |||||
} | |||||
:root { | |||||
caret-color: var(--color-active); | |||||
} |
@@ -0,0 +1,138 @@ | |||||
@font-face { | |||||
font-family: 'Encode Sans'; | |||||
font-stretch: semi-expanded; | |||||
font-weight: 400; | |||||
src: | |||||
local('Encode Sans Semi Expanded'), | |||||
local('Encode Sans'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Encode Sans'; | |||||
font-stretch: semi-expanded; | |||||
font-weight: 700; | |||||
src: | |||||
local('Encode Sans Semi Expanded Bold'), | |||||
local('Encode Sans Semi Expanded'), | |||||
local('Encode Sans'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Encode Sans'; | |||||
font-stretch: condensed; | |||||
font-weight: 100; | |||||
src: | |||||
local('Encode Sans Condensed Thin'), | |||||
local('Encode Sans Condensed'), | |||||
local('Encode Sans'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Encode Sans'; | |||||
font-stretch: condensed; | |||||
font-weight: 200; | |||||
src: | |||||
local('Encode Sans Condensed ExtraLight'), | |||||
local('Encode Sans Condensed Extra Light'), | |||||
local('Encode Sans Condensed'), | |||||
local('Encode Sans'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Encode Sans'; | |||||
font-stretch: condensed; | |||||
font-weight: 300; | |||||
src: | |||||
local('Encode Sans Condensed Light'), | |||||
local('Encode Sans Condensed'), | |||||
local('Encode Sans'); | |||||
} | |||||
:root { | |||||
--color-active: #f90; | |||||
--font-family-base: 'Encode Sans Semi Expanded', 'Encode Sans', system-ui; | |||||
--font-stretch-base: semi-expanded; | |||||
--font-weight-base: 400; | |||||
--line-height-base: 2; | |||||
--font-family-headings:'Encode Sans Condensed', 'Encode Sans', system-ui; | |||||
--font-stretch-headings: condensed; | |||||
--font-weight-headings: 100; | |||||
--line-height-headings: 1.5; | |||||
--font-family-monospace: 'mononoki'; | |||||
--font-size-root: 16px; | |||||
--color-negative: #222; | |||||
--color-positive: #eee; | |||||
--color-accent: #C78AB3; | |||||
--opacity-light: 0.25; | |||||
--opacity-lighter: 0.5; | |||||
--opacity-lightest: 0.75; | |||||
} | |||||
:root { | |||||
--color-bg: var(--color-negative, white); | |||||
--color-fg: var(--color-positive, black); | |||||
background-color: var(--color-bg); | |||||
color: var(--color-fg); | |||||
font-size: var(--font-size-root); | |||||
font-family: var(--font-family-base), sans-serif; | |||||
font-stretch: var(--font-stretch-base, normal); | |||||
font-weight: var(--font-weight-base, 400); | |||||
line-height: var(--line-height-base, 2); | |||||
transition-property: color, background-color; | |||||
transition-timing-function: ease; | |||||
transition-duration: 350ms; | |||||
} | |||||
h1 { | |||||
font-family: var(--font-family-headings), sans-serif; | |||||
font-stretch: var(--font-stretch-headings, normal); | |||||
font-weight: var(--font-weight-headings, 400); | |||||
line-height: var(--line-height-headings, 1.5); | |||||
} | |||||
h2 { | |||||
font-family: var(--font-family-headings), sans-serif; | |||||
font-stretch: var(--font-stretch-headings, normal); | |||||
font-weight: var(--font-weight-headings, 400); | |||||
line-height: var(--line-height-headings, 1.5); | |||||
} | |||||
h3 { | |||||
font-family: var(--font-family-headings), sans-serif; | |||||
font-stretch: var(--font-stretch-headings, normal); | |||||
font-weight: var(--font-weight-headings, 400); | |||||
line-height: var(--line-height-headings, 1.5); | |||||
} | |||||
h4 { | |||||
font-family: var(--font-family-headings), sans-serif; | |||||
font-stretch: var(--font-stretch-headings, normal); | |||||
font-weight: var(--font-weight-headings, 400); | |||||
line-height: var(--line-height-headings, 1.5); | |||||
} | |||||
h5 { | |||||
font-family: var(--font-family-headings), sans-serif; | |||||
font-stretch: var(--font-stretch-headings, normal); | |||||
font-weight: var(--font-weight-headings, 400); | |||||
line-height: var(--line-height-headings, 1.5); | |||||
} | |||||
h6 { | |||||
font-family: var(--font-family-headings), sans-serif; | |||||
font-stretch: var(--font-stretch-headings, normal); | |||||
font-weight: var(--font-weight-headings, 400); | |||||
line-height: var(--line-height-headings, 1.5); | |||||
} | |||||
a { | |||||
color: var(--color-accent); | |||||
} | |||||
code { | |||||
font-family: var(--font-family-monospace), monospace; | |||||
} | |||||
pre { | |||||
font-family: var(--font-family-monospace), monospace; | |||||
} |
@@ -0,0 +1,146 @@ | |||||
@font-face { | |||||
font-family: 'Encode Sans'; | |||||
font-stretch: semi-expanded; | |||||
font-weight: 400; | |||||
src: | |||||
local('Encode Sans Semi Expanded'), | |||||
local('Encode Sans'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Encode Sans'; | |||||
font-stretch: semi-expanded; | |||||
font-weight: 700; | |||||
src: | |||||
local('Encode Sans Semi Expanded Bold'), | |||||
local('Encode Sans Semi Expanded'), | |||||
local('Encode Sans'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Encode Sans'; | |||||
font-stretch: condensed; | |||||
font-weight: 100; | |||||
src: | |||||
local('Encode Sans Condensed Thin'), | |||||
local('Encode Sans Condensed'), | |||||
local('Encode Sans'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Encode Sans'; | |||||
font-stretch: condensed; | |||||
font-weight: 200; | |||||
src: | |||||
local('Encode Sans Condensed ExtraLight'), | |||||
local('Encode Sans Condensed Extra Light'), | |||||
local('Encode Sans Condensed'), | |||||
local('Encode Sans'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Encode Sans'; | |||||
font-stretch: condensed; | |||||
font-weight: 300; | |||||
src: | |||||
local('Encode Sans Condensed Light'), | |||||
local('Encode Sans Condensed'), | |||||
local('Encode Sans'); | |||||
} | |||||
:root { | |||||
--color-negative: #eee; | |||||
--color-positive: #222; | |||||
--color-accent: #ba6a9c; | |||||
--color-active: #f90; | |||||
--font-family-base: 'Encode Sans Semi Expanded', 'Encode Sans', system-ui; | |||||
--font-stretch-base: semi-expanded; | |||||
--font-weight-base: 400; | |||||
--line-height-base: 2; | |||||
--font-family-headings:'Encode Sans Condensed', 'Encode Sans', system-ui; | |||||
--font-stretch-headings: condensed; | |||||
--font-weight-headings: 100; | |||||
--line-height-headings: 1.5; | |||||
--font-family-monospace: 'mononoki'; | |||||
--font-size-root: 16px; | |||||
--opacity-light: 0.5; | |||||
--opacity-lighter: 0.75; | |||||
--opacity-lightest: 0.875; | |||||
} | |||||
/*@media (prefers-color-scheme: dark) {*/ | |||||
/* :root {*/ | |||||
/* --color-negative: #222;*/ | |||||
/* --color-positive: #eee;*/ | |||||
/* --color-accent: #C78AB3;*/ | |||||
/* --opacity-light: 0.25;*/ | |||||
/* --opacity-lighter: 0.5;*/ | |||||
/* --opacity-lightest: 0.75;*/ | |||||
/* }*/ | |||||
/*}*/ | |||||
:root { | |||||
--color-bg: var(--color-negative, white); | |||||
--color-fg: var(--color-positive, black); | |||||
background-color: var(--color-bg); | |||||
color: var(--color-fg); | |||||
font-size: var(--font-size-root); | |||||
font-family: var(--font-family-base), sans-serif; | |||||
font-stretch: var(--font-stretch-base, normal); | |||||
font-weight: var(--font-weight-base, 400); | |||||
line-height: var(--line-height-base, 2); | |||||
} | |||||
h1 { | |||||
font-family: var(--font-family-headings), sans-serif; | |||||
font-stretch: var(--font-stretch-headings, normal); | |||||
font-weight: var(--font-weight-headings, 400); | |||||
line-height: var(--line-height-headings, 1.5); | |||||
} | |||||
h2 { | |||||
font-family: var(--font-family-headings), sans-serif; | |||||
font-stretch: var(--font-stretch-headings, normal); | |||||
font-weight: var(--font-weight-headings, 400); | |||||
line-height: var(--line-height-headings, 1.5); | |||||
} | |||||
h3 { | |||||
font-family: var(--font-family-headings), sans-serif; | |||||
font-stretch: var(--font-stretch-headings, normal); | |||||
font-weight: var(--font-weight-headings, 400); | |||||
line-height: var(--line-height-headings, 1.5); | |||||
} | |||||
h4 { | |||||
font-family: var(--font-family-headings), sans-serif; | |||||
font-stretch: var(--font-stretch-headings, normal); | |||||
font-weight: var(--font-weight-headings, 400); | |||||
line-height: var(--line-height-headings, 1.5); | |||||
} | |||||
h5 { | |||||
font-family: var(--font-family-headings), sans-serif; | |||||
font-stretch: var(--font-stretch-headings, normal); | |||||
font-weight: var(--font-weight-headings, 400); | |||||
line-height: var(--line-height-headings, 1.5); | |||||
} | |||||
h6 { | |||||
font-family: var(--font-family-headings), sans-serif; | |||||
font-stretch: var(--font-stretch-headings, normal); | |||||
font-weight: var(--font-weight-headings, 400); | |||||
line-height: var(--line-height-headings, 1.5); | |||||
} | |||||
a { | |||||
color: var(--color-accent); | |||||
} | |||||
code { | |||||
font-family: var(--font-family-monospace), monospace; | |||||
} | |||||
pre { | |||||
font-family: var(--font-family-monospace), monospace; | |||||
} |
@@ -0,0 +1,4 @@ | |||||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none" | |||||
xmlns="http://www.w3.org/2000/svg"> | |||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/> | |||||
</svg> |
@@ -0,0 +1,24 @@ | |||||
#!/usr/bin/env node | |||||
const docgen = require('react-docgen-typescript') | |||||
const path = require('path') | |||||
const fs = require('fs') | |||||
const componentsPath = path.resolve(__dirname, '..', '..', 'react-common', 'src', 'components') | |||||
fs.readdir(componentsPath, (err, dir) => { | |||||
const trueFilePaths = dir.map(d => path.resolve(__dirname, '..', '..', 'react-common', 'src', 'components', d, `${d}.tsx`)) | |||||
const docs = docgen.parse( | |||||
trueFilePaths, | |||||
{ | |||||
shouldExtractLiteralValuesFromEnum: true, | |||||
shouldRemoveUndefinedFromOptional: true, | |||||
propFilter: { | |||||
skipPropsWithName: ['key', 'ref'], | |||||
}, | |||||
} | |||||
) | |||||
fs.writeFile(path.resolve(__dirname, '..', 'src', 'docgen.json'), JSON.stringify(docs, null, 2), (err) => { | |||||
}) | |||||
}) |
@@ -0,0 +1,35 @@ | |||||
import * as React from 'react' | |||||
import Head from 'next/head' | |||||
import unified from 'unified' | |||||
import parse from 'remark-parse' | |||||
import remark2react from 'remark-react' | |||||
import docgen from '../../docgen.json' | |||||
const Header = ({ of: ofAttr }) => { | |||||
const docs = docgen.find(d => d.displayName === ofAttr) | |||||
if (!docs) { | |||||
return null | |||||
} | |||||
return ( | |||||
<React.Fragment> | |||||
<Head> | |||||
<title> | |||||
{docs.displayName} | React Common | |||||
</title> | |||||
</Head> | |||||
<h1>{docs.displayName}</h1> | |||||
<p> | |||||
{ | |||||
unified() | |||||
.use(parse) | |||||
.use(remark2react) | |||||
.processSync(docs.description).result | |||||
} | |||||
</p> | |||||
</React.Fragment> | |||||
) | |||||
} | |||||
export default Header |
@@ -0,0 +1,50 @@ | |||||
import * as React from 'react' | |||||
import * as PropTypes from 'prop-types' | |||||
import styled from 'styled-components' | |||||
import { Icon } from '../../../../react-common/src' | |||||
const Image = styled('img')({ | |||||
display: 'block', | |||||
maxWidth: '100%', | |||||
maxHeight: '100%', | |||||
}) | |||||
export type MenuGraphicsKind = 'icon' | 'image' | |||||
export const propTypes = { | |||||
kind: PropTypes.oneOf<MenuGraphicsKind>(['icon', 'image']).isRequired, | |||||
alt: PropTypes.string, | |||||
id: PropTypes.string.isRequired, | |||||
} | |||||
type Props = PropTypes.InferProps<typeof propTypes> | |||||
const MenuGraphics: React.FC<Props> = ({ | |||||
kind, | |||||
alt, | |||||
id, | |||||
}) => { | |||||
switch (kind) { | |||||
case 'icon': | |||||
return ( | |||||
<Icon | |||||
name={id} | |||||
/> | |||||
) | |||||
case 'image': | |||||
return ( | |||||
<Image | |||||
src={id} | |||||
alt={alt} | |||||
/> | |||||
) | |||||
default: | |||||
break | |||||
} | |||||
return null | |||||
} | |||||
MenuGraphics.propTypes = propTypes | |||||
export default MenuGraphics |
@@ -0,0 +1,91 @@ | |||||
import * as React from 'react' | |||||
import Link from 'next/link' | |||||
import styled from 'styled-components' | |||||
import NavLink from '../NavLink/NavLink' | |||||
const StyledLink = styled('a')({ | |||||
display: 'block', | |||||
textDecoration: 'none', | |||||
}) | |||||
const Container = styled('span')({ | |||||
display: 'flex', | |||||
alignItems: 'center', | |||||
padding: '0 1rem', | |||||
height: '100%', | |||||
margin: '0 0 0 auto', | |||||
boxSizing: 'border-box', | |||||
'@media (min-width: 720px)': { | |||||
maxWidth: 'var(--max-width)', | |||||
}, | |||||
}) | |||||
const HeaderContainer = styled(Container)({ | |||||
marginTop: '2rem', | |||||
}) | |||||
const NavContainer = styled('div')({ | |||||
}) | |||||
const Nav = ({ | |||||
data, | |||||
}) => { | |||||
if (Array.isArray(data)) { | |||||
return ( | |||||
<NavContainer> | |||||
{ | |||||
data.map(d => ( | |||||
<Nav | |||||
data={d} | |||||
/> | |||||
)) | |||||
} | |||||
</NavContainer> | |||||
) | |||||
} | |||||
if (typeof data === 'object') { | |||||
const [entry] = Object.entries(data) | |||||
const [key, value] = entry | |||||
if (Array.isArray(value)) { | |||||
return ( | |||||
<NavContainer> | |||||
<HeaderContainer> | |||||
{key} | |||||
</HeaderContainer> | |||||
{ | |||||
value.map(v => ( | |||||
<Nav | |||||
data={v} | |||||
/> | |||||
)) | |||||
} | |||||
</NavContainer> | |||||
) | |||||
} | |||||
return ( | |||||
<Link | |||||
href={value} | |||||
passHref | |||||
> | |||||
<NavLink | |||||
title={key} | |||||
enclosingComponent={Container} | |||||
/> | |||||
</Link> | |||||
) | |||||
} | |||||
return ( | |||||
<Link | |||||
href={data} | |||||
passHref | |||||
> | |||||
<NavLink | |||||
title={data} | |||||
enclosingComponent={Container} | |||||
/> | |||||
</Link> | |||||
) | |||||
} | |||||
export default Nav |
@@ -0,0 +1,151 @@ | |||||
import * as React from 'react' | |||||
import * as PropTypes from 'prop-types' | |||||
import styled from 'styled-components' | |||||
import { Icon } from '../../../../react-common/src' | |||||
import MenuGraphics, { propTypes as menuGraphicsPropTypes } from '../MenuGraphics/MenuGraphics' | |||||
const Link = styled('a')({ | |||||
display: 'block', | |||||
height: 'var(--size-link, 4rem)', | |||||
textDecoration: 'none', | |||||
position: 'relative', | |||||
'::before': { | |||||
display: 'block', | |||||
content: "''", | |||||
position: 'absolute', | |||||
top: 0, | |||||
left: 0, | |||||
width: '100%', | |||||
height: '100%', | |||||
backgroundColor: 'currentColor', | |||||
opacity: 0, | |||||
zIndex: -1, | |||||
}, | |||||
'::after': { | |||||
display: 'block', | |||||
content: "''", | |||||
position: 'absolute', | |||||
top: 0, | |||||
left: 0, | |||||
width: '0.25rem', | |||||
height: '100%', | |||||
backgroundColor: 'currentColor', | |||||
opacity: 0, | |||||
zIndex: -1, | |||||
}, | |||||
':hover::before': { | |||||
opacity: 0.25, | |||||
}, | |||||
':hover::after': { | |||||
opacity: 0.5, | |||||
}, | |||||
}) | |||||
const LinkText = styled('span')({ | |||||
alignSelf: 'center', | |||||
display: 'block', | |||||
lineHeight: 1.25, | |||||
gridColumnStart: 2, | |||||
':first-child': { | |||||
gridColumnStart: 1, | |||||
}, | |||||
':last-child': { | |||||
gridColumnEnd: 4, | |||||
}, | |||||
}) | |||||
const LinkTitle = styled('strong')({ | |||||
display: 'block', | |||||
}) | |||||
const LinkSubtitle = styled('span')({ | |||||
display: 'block', | |||||
}) | |||||
const IndicatorContainer = styled('span')({ | |||||
alignSelf: 'center', | |||||
lineHeight: 0, | |||||
}) | |||||
const MenuGraphicsContainer = styled('span')({ | |||||
display: 'block', | |||||
padding: '0.5rem', | |||||
lineHeight: 0, | |||||
}) | |||||
export const basePropTypes = { | |||||
as: PropTypes.string, | |||||
href: PropTypes.string.isRequired, | |||||
title: PropTypes.string.isRequired, | |||||
subtitle: PropTypes.string, | |||||
graphics: PropTypes.shape(menuGraphicsPropTypes), | |||||
indicator: PropTypes.string, | |||||
onClick: PropTypes.func, | |||||
} | |||||
const propTypes = { | |||||
...basePropTypes, | |||||
enclosingComponent: PropTypes.elementType, | |||||
as: PropTypes.elementType, | |||||
} | |||||
type Props = PropTypes.InferProps<typeof propTypes> | |||||
const NavLink = React.forwardRef<HTMLAnchorElement, Props>(( | |||||
{ | |||||
enclosingComponent: EnclosingComponent = React.Fragment, | |||||
href, | |||||
title, | |||||
graphics, | |||||
subtitle, | |||||
indicator, | |||||
onClick, | |||||
}, | |||||
ref | |||||
) => ( | |||||
<Link | |||||
ref={ref} | |||||
href={href} | |||||
onClick={onClick} | |||||
> | |||||
<EnclosingComponent> | |||||
{ | |||||
graphics as object | |||||
&& ( | |||||
<MenuGraphicsContainer> | |||||
<MenuGraphics | |||||
{...graphics} | |||||
/> | |||||
</MenuGraphicsContainer> | |||||
) | |||||
} | |||||
<LinkText> | |||||
<LinkTitle> | |||||
{title} | |||||
</LinkTitle> | |||||
{ | |||||
subtitle as string | |||||
&& ( | |||||
<LinkSubtitle> | |||||
{subtitle} | |||||
</LinkSubtitle> | |||||
) | |||||
} | |||||
</LinkText> | |||||
{ | |||||
indicator as string | |||||
&& ( | |||||
<IndicatorContainer> | |||||
<Icon | |||||
name={indicator!} | |||||
/> | |||||
</IndicatorContainer> | |||||
) | |||||
} | |||||
</EnclosingComponent> | |||||
</Link> | |||||
)) | |||||
NavLink.propTypes = propTypes | |||||
export default NavLink |
@@ -0,0 +1,53 @@ | |||||
import * as React from 'react' | |||||
import { | |||||
LiveProvider, | |||||
LiveEditor, | |||||
LivePreview | |||||
} from 'react-live' | |||||
import styled from 'styled-components' | |||||
const Figure = styled('figure')({ | |||||
margin: 0, | |||||
}) | |||||
const StyledLiveEditor = styled(LiveEditor)({ | |||||
lineHeight: 1.125, | |||||
fontFamily: 'var(--font-family-monospace), monospace !important', | |||||
marginTop: '1rem', | |||||
}) | |||||
const Playground = ({ | |||||
components, | |||||
code, | |||||
label, | |||||
}) => { | |||||
const indentLevel = code.indexOf('<') - 1 | |||||
const normalizedCode = code | |||||
.split('\n') | |||||
.map((s: string) => s.slice(indentLevel)) | |||||
.filter((s: string) => s.trim().length > 0) | |||||
.join('\n') | |||||
return ( | |||||
<Figure> | |||||
{ | |||||
label | |||||
&& ( | |||||
<figcaption> | |||||
{label} | |||||
</figcaption> | |||||
) | |||||
} | |||||
<div> | |||||
<LiveProvider code={normalizedCode} scope={{ | |||||
...components, | |||||
}}> | |||||
<LivePreview /> | |||||
<StyledLiveEditor /> | |||||
</LiveProvider> | |||||
</div> | |||||
</Figure> | |||||
) | |||||
} | |||||
export default Playground |
@@ -0,0 +1,176 @@ | |||||
import * as React from 'react' | |||||
import styled from 'styled-components' | |||||
import docgen from '../../docgen.json' | |||||
const Base = styled('table')({ | |||||
borderCollapse: 'collapse', | |||||
border: 0, | |||||
display: 'block', | |||||
'@media (min-width: 720px)': { | |||||
display: 'table', | |||||
margin: '0 -0.5rem', | |||||
}, | |||||
}) | |||||
const HeaderCellGroup = styled('thead')({ | |||||
display: 'none', | |||||
'@media (min-width: 720px)': { | |||||
display: 'table-header-group', | |||||
}, | |||||
}) | |||||
const HeaderRow = styled('tr')({ | |||||
display: 'block', | |||||
margin: '2rem 0', | |||||
'@media (min-width: 720px)': { | |||||
display: 'table-row', | |||||
margin: 0, | |||||
}, | |||||
}) | |||||
const HeaderCell = styled('th')({ | |||||
verticalAlign: 'top', | |||||
textAlign: 'left', | |||||
fontSize: '0.75rem', | |||||
fontWeight: 'bolder', | |||||
textTransform: 'uppercase', | |||||
border: 0, | |||||
display: 'block', | |||||
padding: 0, | |||||
'@media (min-width: 720px)': { | |||||
display: 'table-cell', | |||||
padding: '0 0.5rem', | |||||
}, | |||||
}) | |||||
const MainBodyCell = styled('th')({ | |||||
verticalAlign: 'top', | |||||
textAlign: 'left', | |||||
border: 0, | |||||
lineHeight: 1, | |||||
display: 'block', | |||||
padding: 0, | |||||
'@media (min-width: 720px)': { | |||||
display: 'table-cell', | |||||
padding: '0.5rem', | |||||
}, | |||||
}) | |||||
const BodyCellGroup = styled('tbody')({ | |||||
display: 'block', | |||||
'@media (min-width: 720px)': { | |||||
display: 'table-row-group', | |||||
}, | |||||
}) | |||||
const BodyCell = styled('td')({ | |||||
verticalAlign: 'top', | |||||
padding: 0, | |||||
border: 0, | |||||
display: 'block', | |||||
'&[data-column-name]::before': { | |||||
display: 'inline', | |||||
fontSize: '0.75rem', | |||||
fontWeight: 'bolder', | |||||
textTransform: 'uppercase', | |||||
content: "attr(data-column-name) ':'", | |||||
marginRight: '1rem', | |||||
}, | |||||
'@media (min-width: 720px)': { | |||||
padding: '0.5rem 0.5rem', | |||||
display: 'table-cell', | |||||
lineHeight: 1.25, | |||||
'&[data-column-name]::before': { | |||||
display: 'none', | |||||
}, | |||||
}, | |||||
}) | |||||
const Code = styled('code')({ | |||||
display: 'inline-block', | |||||
}) | |||||
const Props = ({ of: ofAttr }) => { | |||||
const docs = docgen.find(d => d.displayName === ofAttr) | |||||
if (!docs) { | |||||
return null | |||||
} | |||||
return ( | |||||
<Base> | |||||
<colgroup> | |||||
<col width="20%" /> | |||||
<col width="25%" /> | |||||
<col width="25%" /> | |||||
<col width="*" /> | |||||
</colgroup> | |||||
<HeaderCellGroup> | |||||
<HeaderRow> | |||||
<HeaderCell> | |||||
Name | |||||
</HeaderCell> | |||||
<HeaderCell> | |||||
Type | |||||
</HeaderCell> | |||||
<HeaderCell> | |||||
Default | |||||
</HeaderCell> | |||||
<HeaderCell> | |||||
Description | |||||
</HeaderCell> | |||||
</HeaderRow> | |||||
</HeaderCellGroup> | |||||
<BodyCellGroup> | |||||
{ | |||||
Object.entries(docs.props).map(([name, def]) => ( | |||||
<HeaderRow> | |||||
<MainBodyCell> | |||||
<Code> | |||||
{def.required ? name : name + '?'} | |||||
</Code> | |||||
</MainBodyCell> | |||||
<BodyCell | |||||
data-column-name="Type" | |||||
> | |||||
{ | |||||
def.type.name === 'enum' | |||||
&& ( | |||||
<Code> | |||||
{def.type.value.map(v => v.value).join(' | ')} | |||||
</Code> | |||||
) | |||||
} | |||||
{ | |||||
def.type.name !== 'enum' | |||||
&& ( | |||||
<Code> | |||||
{def.type.name} | |||||
</Code> | |||||
) | |||||
} | |||||
</BodyCell> | |||||
<BodyCell | |||||
data-column-name={def.defaultValue ? 'Default' : undefined } | |||||
> | |||||
{ | |||||
def.defaultValue | |||||
&& ( | |||||
<Code> | |||||
{JSON.stringify(def.defaultValue.value)} | |||||
</Code> | |||||
) | |||||
} | |||||
</BodyCell> | |||||
<BodyCell> | |||||
{def.description} | |||||
</BodyCell> | |||||
</HeaderRow> | |||||
)) | |||||
} | |||||
</BodyCellGroup> | |||||
</Base> | |||||
) | |||||
} | |||||
export default Props |
@@ -0,0 +1,186 @@ | |||||
import { Icon } from '../../../../react-common/src' | |||||
import pkg from '../../../../../package.json' | |||||
import styled from 'styled-components' | |||||
import Link from 'next/link' | |||||
import * as React from 'react' | |||||
import Nav from '../Nav/Nav' | |||||
const StyledLink = styled('a')({ | |||||
display: 'block', | |||||
textDecoration: 'none', | |||||
marginTop: '3rem', | |||||
marginBottom: '3rem', | |||||
}) | |||||
const Container = styled('span')({ | |||||
display: 'flex', | |||||
alignItems: 'center', | |||||
padding: '0 1rem', | |||||
height: '2rem', | |||||
margin: '0 0 0 auto', | |||||
boxSizing: 'border-box', | |||||
'@media (min-width: 720px)': { | |||||
maxWidth: 'var(--max-width)', | |||||
}, | |||||
}) | |||||
const Base = styled('aside')({ | |||||
'--max-width': 240, | |||||
position: 'fixed', | |||||
top: 0, | |||||
left: '-100%', | |||||
width: '100%', | |||||
height: '100%', | |||||
backgroundColor: 'var(--color-bg)', | |||||
zIndex: 4, | |||||
transitionProperty: 'color, background-color', | |||||
transitionTimingFunction: 'ease', | |||||
transitionDuration: '350ms', | |||||
'@media (min-width: 720px)': { | |||||
left: 0, | |||||
width: `${100 / 4}%`, | |||||
maxWidth: 'none', | |||||
height: '100%', | |||||
'+ *': { | |||||
paddingLeft: `${100 / 4}%`, | |||||
boxSizing: 'border-box', | |||||
}, | |||||
}, | |||||
}) | |||||
const Actions = styled('div')({ | |||||
marginBottom: '4rem', | |||||
display: 'flex', | |||||
gap: '1rem', | |||||
alignItems: 'center', | |||||
}) | |||||
const ToggleWrapper = styled('label')({ | |||||
cursor: 'pointer', | |||||
color: 'var(--color-accent)', | |||||
display: 'inline-block', | |||||
}) | |||||
const ToggleIcon = styled('span')({ | |||||
}) | |||||
const ToggleInput = styled('input')({ | |||||
position: 'absolute', | |||||
left: -999999, | |||||
}) | |||||
const NavWrapper = styled('nav')({ | |||||
'--size-link': '3rem', | |||||
}) | |||||
const RepoLink = styled('a')({ | |||||
display: 'inline-grid', | |||||
width: '1.5rem', | |||||
height: '1.5rem', | |||||
placeContent: 'center', | |||||
}) | |||||
const Sidebar = ({ | |||||
data, | |||||
brand: Brand, | |||||
initialTheme = 'Dark', | |||||
}) => { | |||||
const [theme, setTheme] = React.useState(initialTheme) | |||||
const toggleDarkMode = (b: string) => () => { | |||||
setTheme(b) | |||||
} | |||||
React.useEffect(() => { | |||||
let storageTheme = window.localStorage.getItem('tesseract-theme') | |||||
|| ( | |||||
window.matchMedia('(prefers-color-scheme: dark)').matches | |||||
? 'Dark' | |||||
: 'Light' | |||||
) | |||||
window.localStorage.setItem('tesseract-theme', storageTheme) | |||||
setTimeout(() => { | |||||
setTheme(storageTheme) | |||||
}) | |||||
}, []) | |||||
React.useEffect(() => { | |||||
window.localStorage.setItem('tesseract-theme', theme) | |||||
}, [theme]) | |||||
React.useEffect(() => { | |||||
const stylesheets = Array.from(window.document.querySelectorAll('link[title]')) as HTMLLinkElement[] | |||||
stylesheets.forEach(s => { | |||||
const enabled = s.title === theme | |||||
s.setAttribute('rel', enabled ? 'stylesheet' : 'alternate stylesheet') | |||||
if (enabled) { | |||||
s.removeAttribute('disabled') | |||||
} else { | |||||
s.setAttribute('disabled', 'disabled') | |||||
} | |||||
}) | |||||
}, [theme]) | |||||
return ( | |||||
<Base> | |||||
<NavWrapper> | |||||
<Link | |||||
href="/" | |||||
passHref | |||||
> | |||||
<StyledLink> | |||||
<Container> | |||||
<Brand /> | |||||
</Container> | |||||
</StyledLink> | |||||
</Link> | |||||
<Container> | |||||
<Actions> | |||||
<ToggleWrapper> | |||||
<ToggleInput | |||||
type="checkbox" | |||||
defaultChecked={theme === 'Dark'} | |||||
onChange={toggleDarkMode(theme === 'Dark' ? 'Light' : 'Dark')} | |||||
/> | |||||
<ToggleIcon> | |||||
{ | |||||
theme === 'Dark' | |||||
&& ( | |||||
<Icon | |||||
label="Set Light Mode" | |||||
name="moon" | |||||
/> | |||||
) | |||||
} | |||||
{ | |||||
theme === 'Light' | |||||
&& ( | |||||
<Icon | |||||
label="Set Dark Mode" | |||||
name="sun" | |||||
/> | |||||
) | |||||
} | |||||
</ToggleIcon> | |||||
</ToggleWrapper> | |||||
<RepoLink | |||||
href={pkg.repository} | |||||
target="_blank" | |||||
rel="noopener noreferer" | |||||
> | |||||
<Icon | |||||
name="code" | |||||
label="Visit Repository" | |||||
/> | |||||
</RepoLink> | |||||
</Actions> | |||||
</Container> | |||||
<Nav | |||||
data={data.nav} | |||||
/> | |||||
</NavWrapper> | |||||
</Base> | |||||
) | |||||
} | |||||
export default Sidebar |
@@ -0,0 +1,694 @@ | |||||
[ | |||||
{ | |||||
"description": "Component for performing an action upon activation (e.g. when clicked).", | |||||
"displayName": "Button", | |||||
"methods": [], | |||||
"props": { | |||||
"size": { | |||||
"defaultValue": { | |||||
"value": "medium" | |||||
}, | |||||
"description": "Size of the component.", | |||||
"name": "size", | |||||
"required": false, | |||||
"type": { | |||||
"name": "enum", | |||||
"raw": "Size", | |||||
"value": [ | |||||
{ | |||||
"value": "\"small\"" | |||||
}, | |||||
{ | |||||
"value": "\"medium\"" | |||||
}, | |||||
{ | |||||
"value": "\"large\"" | |||||
} | |||||
] | |||||
} | |||||
}, | |||||
"variant": { | |||||
"defaultValue": { | |||||
"value": "outline" | |||||
}, | |||||
"description": "Variant of the component.", | |||||
"name": "variant", | |||||
"required": false, | |||||
"type": { | |||||
"name": "enum", | |||||
"raw": "Variant", | |||||
"value": [ | |||||
{ | |||||
"value": "\"outline\"" | |||||
}, | |||||
{ | |||||
"value": "\"primary\"" | |||||
} | |||||
] | |||||
} | |||||
}, | |||||
"children": { | |||||
"defaultValue": null, | |||||
"description": "Text to identify the action associated upon activation of the component.", | |||||
"name": "children", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"disabled": { | |||||
"defaultValue": { | |||||
"value": false | |||||
}, | |||||
"description": "Can the component be activated?", | |||||
"name": "disabled", | |||||
"required": false, | |||||
"type": { | |||||
"name": "boolean" | |||||
} | |||||
}, | |||||
"element": { | |||||
"defaultValue": { | |||||
"value": "button" | |||||
}, | |||||
"description": "The corresponding HTML element of the component.", | |||||
"name": "element", | |||||
"required": false, | |||||
"type": { | |||||
"name": "enum", | |||||
"raw": "ButtonElement", | |||||
"value": [ | |||||
{ | |||||
"value": "\"a\"" | |||||
}, | |||||
{ | |||||
"value": "\"button\"" | |||||
} | |||||
] | |||||
} | |||||
}, | |||||
"href": { | |||||
"defaultValue": null, | |||||
"description": "The URL of the page to navigate to, if element is set to \"a\".", | |||||
"name": "href", | |||||
"required": false, | |||||
"type": { | |||||
"name": "string" | |||||
} | |||||
}, | |||||
"target": { | |||||
"defaultValue": null, | |||||
"description": "The target on where to display the page navigated to, if element is set to \"a\".", | |||||
"name": "target", | |||||
"required": false, | |||||
"type": { | |||||
"name": "string" | |||||
} | |||||
}, | |||||
"rel": { | |||||
"defaultValue": null, | |||||
"description": "The relationship of the current page to the referred page in \"href\", if element is set to \"a\".", | |||||
"name": "rel", | |||||
"required": false, | |||||
"type": { | |||||
"name": "string" | |||||
} | |||||
}, | |||||
"type": { | |||||
"defaultValue": { | |||||
"value": "button" | |||||
}, | |||||
"description": "The type of the button, if element is set to \"button\".", | |||||
"name": "type", | |||||
"required": false, | |||||
"type": { | |||||
"name": "enum", | |||||
"raw": "ButtonType", | |||||
"value": [ | |||||
{ | |||||
"value": "\"button\"" | |||||
}, | |||||
{ | |||||
"value": "\"submit\"" | |||||
}, | |||||
{ | |||||
"value": "\"reset\"" | |||||
} | |||||
] | |||||
} | |||||
}, | |||||
"border": { | |||||
"defaultValue": { | |||||
"value": false | |||||
}, | |||||
"description": "Does the button display a border?", | |||||
"name": "border", | |||||
"required": false, | |||||
"type": { | |||||
"name": "boolean" | |||||
} | |||||
}, | |||||
"onClick": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component is clicked.", | |||||
"name": "onClick", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
}, | |||||
"onFocus": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component receives focus.", | |||||
"name": "onFocus", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
}, | |||||
"onBlur": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component loses focus.", | |||||
"name": "onBlur", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
{ | |||||
"description": "Component for values that have an on/off state.", | |||||
"displayName": "Checkbox", | |||||
"methods": [], | |||||
"props": { | |||||
"onFocus": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component receives focus.", | |||||
"name": "onFocus", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
}, | |||||
"onBlur": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component loses focus.", | |||||
"name": "onBlur", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
}, | |||||
"label": { | |||||
"defaultValue": { | |||||
"value": "" | |||||
}, | |||||
"description": "Short textual description indicating the nature of the component's value.", | |||||
"name": "label", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"name": { | |||||
"defaultValue": null, | |||||
"description": "Name of the form field associated with this component.", | |||||
"name": "name", | |||||
"required": false, | |||||
"type": { | |||||
"name": "string" | |||||
} | |||||
}, | |||||
"onChange": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component is toggled.", | |||||
"name": "onChange", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
{ | |||||
"description": "Component for displaying graphics.", | |||||
"displayName": "Icon", | |||||
"methods": [], | |||||
"props": { | |||||
"size": { | |||||
"defaultValue": { | |||||
"value": "1.5rem" | |||||
}, | |||||
"description": "Size of the icon. This controls both the width and the height.", | |||||
"name": "size", | |||||
"required": false, | |||||
"type": { | |||||
"name": "ReactText" | |||||
} | |||||
}, | |||||
"label": { | |||||
"defaultValue": { | |||||
"value": null | |||||
}, | |||||
"description": "Describe of what the component represents.", | |||||
"name": "label", | |||||
"required": false, | |||||
"type": { | |||||
"name": "string" | |||||
} | |||||
}, | |||||
"name": { | |||||
"defaultValue": null, | |||||
"description": "Name of the icon to display.", | |||||
"name": "name", | |||||
"required": false, | |||||
"type": { | |||||
"name": "string" | |||||
} | |||||
}, | |||||
"weight": { | |||||
"defaultValue": { | |||||
"value": "0.125rem" | |||||
}, | |||||
"description": "Width of the icon's strokes.", | |||||
"name": "weight", | |||||
"required": false, | |||||
"type": { | |||||
"name": "ReactText" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
{ | |||||
"description": "Component for values which are to be selected from a few list of options.", | |||||
"displayName": "RadioButton", | |||||
"methods": [], | |||||
"props": { | |||||
"onFocus": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component receives focus.", | |||||
"name": "onFocus", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
}, | |||||
"onBlur": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component loses focus.", | |||||
"name": "onBlur", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
}, | |||||
"label": { | |||||
"defaultValue": { | |||||
"value": "" | |||||
}, | |||||
"description": "Short textual description indicating the nature of the component's value.", | |||||
"name": "label", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"name": { | |||||
"defaultValue": null, | |||||
"description": "Group where the component belongs.", | |||||
"name": "name", | |||||
"required": false, | |||||
"type": { | |||||
"name": "string" | |||||
} | |||||
}, | |||||
"onChange": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component is selected.", | |||||
"name": "onChange", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
{ | |||||
"description": "Component for selecting values from a larger number of options.", | |||||
"displayName": "Select", | |||||
"methods": [], | |||||
"props": { | |||||
"size": { | |||||
"defaultValue": { | |||||
"value": "medium" | |||||
}, | |||||
"description": "Size of the component.", | |||||
"name": "size", | |||||
"required": false, | |||||
"type": { | |||||
"name": "enum", | |||||
"raw": "Size", | |||||
"value": [ | |||||
{ | |||||
"value": "\"small\"" | |||||
}, | |||||
{ | |||||
"value": "\"medium\"" | |||||
}, | |||||
{ | |||||
"value": "\"large\"" | |||||
} | |||||
] | |||||
} | |||||
}, | |||||
"disabled": { | |||||
"defaultValue": { | |||||
"value": false | |||||
}, | |||||
"description": "Is the component active?", | |||||
"name": "disabled", | |||||
"required": false, | |||||
"type": { | |||||
"name": "boolean" | |||||
} | |||||
}, | |||||
"border": { | |||||
"defaultValue": { | |||||
"value": false | |||||
}, | |||||
"description": "Does the button display a border?", | |||||
"name": "border", | |||||
"required": false, | |||||
"type": { | |||||
"name": "boolean" | |||||
} | |||||
}, | |||||
"onFocus": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component receives focus.", | |||||
"name": "onFocus", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
}, | |||||
"onBlur": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component loses focus.", | |||||
"name": "onBlur", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
}, | |||||
"label": { | |||||
"defaultValue": { | |||||
"value": "" | |||||
}, | |||||
"description": "Short textual description indicating the nature of the component's value.", | |||||
"name": "label", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"name": { | |||||
"defaultValue": null, | |||||
"description": "Name of the form field associated with this component.", | |||||
"name": "name", | |||||
"required": false, | |||||
"type": { | |||||
"name": "string" | |||||
} | |||||
}, | |||||
"onChange": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component changes value.", | |||||
"name": "onChange", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
}, | |||||
"hint": { | |||||
"defaultValue": { | |||||
"value": "" | |||||
}, | |||||
"description": "Short textual description as guidelines for valid input values.", | |||||
"name": "hint", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"multiple": { | |||||
"defaultValue": { | |||||
"value": false | |||||
}, | |||||
"description": "Can multiple values be selected?", | |||||
"name": "multiple", | |||||
"required": false, | |||||
"type": { | |||||
"name": "boolean" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
{ | |||||
"description": "Component for inputting numeric values in a graphical manner.\n\nThe component is styled using client-side scripts. When the component is rendered server-side,\nthe component falls back into the original `<input type=\"range\">` element.", | |||||
"displayName": "Slider", | |||||
"methods": [], | |||||
"props": { | |||||
"disabled": { | |||||
"defaultValue": { | |||||
"value": false | |||||
}, | |||||
"description": "Is the component active?", | |||||
"name": "disabled", | |||||
"required": false, | |||||
"type": { | |||||
"name": "boolean" | |||||
} | |||||
}, | |||||
"label": { | |||||
"defaultValue": { | |||||
"value": "" | |||||
}, | |||||
"description": "Short textual description indicating the nature of the component's value.", | |||||
"name": "label", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"onChange": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component changes value.", | |||||
"name": "onChange", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
}, | |||||
"orientation": { | |||||
"defaultValue": { | |||||
"value": "horizontal" | |||||
}, | |||||
"description": "The component orientation.", | |||||
"name": "orientation", | |||||
"required": false, | |||||
"type": { | |||||
"name": "enum", | |||||
"raw": "Orientation", | |||||
"value": [ | |||||
{ | |||||
"value": "\"vertical\"" | |||||
}, | |||||
{ | |||||
"value": "\"horizontal\"" | |||||
} | |||||
] | |||||
} | |||||
}, | |||||
"length": { | |||||
"defaultValue": { | |||||
"value": "16rem" | |||||
}, | |||||
"description": "CSS size for the component length.", | |||||
"name": "length", | |||||
"required": false, | |||||
"type": { | |||||
"name": "ReactText" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
{ | |||||
"description": "Component for inputting textual values.", | |||||
"displayName": "TextInput", | |||||
"methods": [], | |||||
"props": { | |||||
"size": { | |||||
"defaultValue": { | |||||
"value": "medium" | |||||
}, | |||||
"description": "Size of the component.", | |||||
"name": "size", | |||||
"required": false, | |||||
"type": { | |||||
"name": "enum", | |||||
"raw": "Size", | |||||
"value": [ | |||||
{ | |||||
"value": "\"small\"" | |||||
}, | |||||
{ | |||||
"value": "\"medium\"" | |||||
}, | |||||
{ | |||||
"value": "\"large\"" | |||||
} | |||||
] | |||||
} | |||||
}, | |||||
"disabled": { | |||||
"defaultValue": { | |||||
"value": false | |||||
}, | |||||
"description": "Is the component active?", | |||||
"name": "disabled", | |||||
"required": false, | |||||
"type": { | |||||
"name": "boolean" | |||||
} | |||||
}, | |||||
"border": { | |||||
"defaultValue": { | |||||
"value": false | |||||
}, | |||||
"description": "Does the button display a border?", | |||||
"name": "border", | |||||
"required": false, | |||||
"type": { | |||||
"name": "boolean" | |||||
} | |||||
}, | |||||
"onFocus": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component receives focus.", | |||||
"name": "onFocus", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
}, | |||||
"onBlur": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component loses focus.", | |||||
"name": "onBlur", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
}, | |||||
"label": { | |||||
"defaultValue": { | |||||
"value": "" | |||||
}, | |||||
"description": "Short textual description indicating the nature of the component's value.", | |||||
"name": "label", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"onChange": { | |||||
"defaultValue": null, | |||||
"description": "Event handler triggered when the component changes value.", | |||||
"name": "onChange", | |||||
"required": false, | |||||
"type": { | |||||
"name": "(...args: any[]) => any" | |||||
} | |||||
}, | |||||
"hint": { | |||||
"defaultValue": { | |||||
"value": "" | |||||
}, | |||||
"description": "Short textual description as guidelines for valid input values.", | |||||
"name": "hint", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"indicator": { | |||||
"defaultValue": { | |||||
"value": null | |||||
}, | |||||
"description": "Additional description, usually graphical, indicating the nature of the component's value.", | |||||
"name": "indicator", | |||||
"required": false, | |||||
"type": { | |||||
"name": "ReactNodeLike" | |||||
} | |||||
}, | |||||
"multiline": { | |||||
"defaultValue": { | |||||
"value": false | |||||
}, | |||||
"description": "Should the component accept multiple lines of input?", | |||||
"name": "multiline", | |||||
"required": false, | |||||
"type": { | |||||
"name": "boolean" | |||||
} | |||||
}, | |||||
"autoResize": { | |||||
"defaultValue": { | |||||
"value": false | |||||
}, | |||||
"description": "Should the component resize itself to show all its value?", | |||||
"name": "autoResize", | |||||
"required": false, | |||||
"type": { | |||||
"name": "boolean" | |||||
} | |||||
}, | |||||
"placeholder": { | |||||
"defaultValue": { | |||||
"value": "" | |||||
}, | |||||
"description": "Placeholder of the component when there is no value.", | |||||
"name": "placeholder", | |||||
"required": false, | |||||
"type": { | |||||
"name": "string" | |||||
} | |||||
}, | |||||
"rows": { | |||||
"defaultValue": { | |||||
"value": 3 | |||||
}, | |||||
"description": "How many rows should the component display if it accepts multiline input?", | |||||
"name": "rows", | |||||
"required": false, | |||||
"type": { | |||||
"name": "number" | |||||
} | |||||
}, | |||||
"alternate": { | |||||
"defaultValue": { | |||||
"value": false | |||||
}, | |||||
"description": "Should the component be displayed with an alternate appearance?", | |||||
"name": "alternate", | |||||
"required": false, | |||||
"type": { | |||||
"name": "boolean" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
] |
@@ -0,0 +1,40 @@ | |||||
import * as React from 'react' | |||||
import styled from 'styled-components' | |||||
import sidebar from '../sidebar.json' | |||||
import brand from '../../brand' | |||||
import Sidebar from '../components/Sidebar/Sidebar' | |||||
import '../../public/global.css' | |||||
import '../../public/theme/dark.css' | |||||
const Container = styled('div')({ | |||||
maxWidth: 720, | |||||
margin: '0 auto', | |||||
padding: '0 1rem', | |||||
boxSizing: 'border-box', | |||||
}) | |||||
type AppProps = { | |||||
Component: React.ElementType, | |||||
pageProps: Record<string, unknown>, | |||||
} | |||||
const App: React.FC<AppProps> = ({ | |||||
Component, | |||||
pageProps, | |||||
}) => ( | |||||
<React.Fragment> | |||||
<Sidebar | |||||
brand={brand} | |||||
data={sidebar} | |||||
/> | |||||
<main> | |||||
<Container> | |||||
<Component | |||||
{...pageProps} | |||||
/> | |||||
</Container> | |||||
</main> | |||||
</React.Fragment> | |||||
) | |||||
export default App |
@@ -0,0 +1,45 @@ | |||||
import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document' | |||||
import { ServerStyleSheet } from 'styled-components' | |||||
export default class MyDocument extends Document { | |||||
static async getInitialProps(ctx: DocumentContext) { | |||||
const sheet = new ServerStyleSheet() | |||||
const originalRenderPage = ctx.renderPage | |||||
try { | |||||
ctx.renderPage = () => | |||||
originalRenderPage({ | |||||
enhanceApp: (App) => (props) => | |||||
sheet.collectStyles(<App {...props} />), | |||||
}) | |||||
const initialProps = await Document.getInitialProps(ctx) | |||||
return { | |||||
...initialProps, | |||||
styles: ( | |||||
<> | |||||
{initialProps.styles} | |||||
<link rel="stylesheet" href="/global.css" /> | |||||
<link rel="stylesheet" title="Dark" href="/theme/dark.css" /> | |||||
<link rel="alternate stylesheet" title="Light" href="/theme/light.css" /> | |||||
{sheet.getStyleElement()} | |||||
</> | |||||
), | |||||
} | |||||
} finally { | |||||
sheet.seal() | |||||
} | |||||
} | |||||
render() { | |||||
return ( | |||||
<Html lang="en-PH"> | |||||
<Head /> | |||||
<body> | |||||
<Main /> | |||||
<NextScript /> | |||||
</body> | |||||
</Html> | |||||
) | |||||
} | |||||
} |
@@ -1,23 +1,26 @@ | |||||
--- | --- | ||||
name: Button | |||||
route: /components/button | |||||
menu: Components | |||||
title: Button | |||||
--- | --- | ||||
import { Playground, Props } from 'docz' | |||||
import Button from './Button' | |||||
import { Button } from '../../../../react-common/src' | |||||
import Playground from '../../components/Playground/Playground' | |||||
import Props from '../../components/Props/Props' | |||||
import Header from '../../components/Header/Header' | |||||
# Button | |||||
<Header of="Button" /> | |||||
Component for performing an action upon activation (e.g. when clicked). | |||||
<Playground> | |||||
<Button>Perform Action</Button> | |||||
</Playground> | |||||
<Playground | |||||
code={` | |||||
<Button> | |||||
Perform Action | |||||
</Button> | |||||
`} | |||||
components={{ Button }} | |||||
/> | |||||
## Props | ## Props | ||||
<Props of={Button} /> | |||||
<Props of="Button" /> | |||||
## Usage Notes | ## Usage Notes | ||||
@@ -0,0 +1,26 @@ | |||||
--- | |||||
name: Checkbox | |||||
--- | |||||
import { Checkbox } from '../../../../react-common/src' | |||||
import Playground from '../../components/Playground/Playground' | |||||
import Props from '../../components/Props/Props' | |||||
import Header from '../../components/Header/Header' | |||||
<Header of="Checkbox" /> | |||||
<Playground | |||||
components={{ Checkbox }} | |||||
code={` | |||||
<Checkbox label="Accept Terms" /> | |||||
`} | |||||
/> | |||||
## Props | |||||
<Props of="Checkbox" /> | |||||
## See Also | |||||
- [Select](./Select) for a similar component suitable for selecting more values. | |||||
- [RadioButton](./RadioButton) for a similar component on selecting a single value among very few choices. |
@@ -0,0 +1,21 @@ | |||||
--- | |||||
name: Icon | |||||
--- | |||||
import { Icon } from '../../../../react-common/src' | |||||
import Playground from '../../components/Playground/Playground' | |||||
import Props from '../../components/Props/Props' | |||||
import Header from '../../components/Header/Header' | |||||
<Header of="Icon" /> | |||||
<Playground | |||||
components={{ Icon }} | |||||
code={` | |||||
<Icon name="check" label="OK" size="1.5rem" weight={0.125} /> | |||||
`} | |||||
/> | |||||
## Props | |||||
<Props of="Icon" /> |
@@ -0,0 +1,33 @@ | |||||
--- | |||||
name: RadioButton | |||||
--- | |||||
import { RadioButton } from '../../../../react-common/src' | |||||
import Playground from '../../components/Playground/Playground' | |||||
import Props from '../../components/Props/Props' | |||||
import Header from '../../components/Header/Header' | |||||
<Header of="RadioButton" /> | |||||
<Playground | |||||
components={{ RadioButton }} | |||||
code={` | |||||
<div style={{ display: 'grid', gap: '1rem', }}> | |||||
<div> | |||||
<RadioButton name="flavor" label="Chocolate" /> | |||||
</div> | |||||
<div> | |||||
<RadioButton name="flavor" label="Vanilla" /> | |||||
</div> | |||||
</div> | |||||
`} | |||||
/> | |||||
## Props | |||||
<Props of="RadioButton" /> | |||||
## See Also | |||||
- [Checkbox](./Checkbox) for a similar component on selecting values among very few choices. | |||||
- [Select](./Select) for a similar component suitable for selecting more values. |
@@ -0,0 +1,46 @@ | |||||
--- | |||||
name: Select | |||||
--- | |||||
import { Select } from '../../../../react-common/src' | |||||
import Playground from '../../components/Playground/Playground' | |||||
import Props from '../../components/Props/Props' | |||||
import Header from '../../components/Header/Header' | |||||
<Header of="Select" /> | |||||
<Playground | |||||
components={{ Select }} | |||||
code={` | |||||
<Select> | |||||
<optgroup | |||||
label="Fruits" | |||||
> | |||||
<option value="mango">Mango</option> | |||||
<option value="strawberry">Strawberry</option> | |||||
<option value="blueberry">Blueberry</option> | |||||
</optgroup> | |||||
<optgroup | |||||
label="Classic" | |||||
> | |||||
<option value="chocolate">Chocolate</option> | |||||
<option value="vanilla">Vanilla</option> | |||||
</optgroup> | |||||
</Select> | |||||
`} | |||||
/> | |||||
## Props | |||||
<Props of="Select" /> | |||||
## Usage Notes | |||||
The component will behave as `block`, i.e. it takes the remaining of the horizontal space. | |||||
To use the component together with layouts, see [TextInput](./TextInput) for examples. Both `Select` and | |||||
`TextInput` have similar strategies on usage with layouts. | |||||
## See Also | |||||
- [Checkbox](./Checkbox) for a similar component on selecting values among very few choices. | |||||
- [RadioButton](./RadioButton) for a similar component on selecting a single value among very few choices. |
@@ -0,0 +1,25 @@ | |||||
--- | |||||
name: Slider | |||||
--- | |||||
import { Slider } from '../../../../react-common/src' | |||||
import Playground from '../../components/Playground/Playground' | |||||
import Props from '../../components/Props/Props' | |||||
import Header from '../../components/Header/Header' | |||||
<Header of="Slider" /> | |||||
<Playground | |||||
components={{ Slider }} | |||||
code={` | |||||
<Slider /> | |||||
`} | |||||
/> | |||||
## Props | |||||
<Props of="Slider" /> | |||||
## See Also | |||||
- [Reach UI Slider](//reacttraining.com/reach-ui/slider/#sliderinput) for the client-side implementation. |
@@ -0,0 +1,82 @@ | |||||
--- | |||||
name: TextInput | |||||
--- | |||||
import { TextInput } from '../../../../react-common/src' | |||||
import Playground from '../../components/Playground/Playground' | |||||
import Props from '../../components/Props/Props' | |||||
import Header from '../../components/Header/Header' | |||||
<Header of="TextInput" /> | |||||
<Playground | |||||
components={{ TextInput }} | |||||
code={` | |||||
<TextInput | |||||
label="Username" | |||||
placeholder="johndoe" | |||||
hint="the name you use to log in" | |||||
/> | |||||
`} | |||||
/> | |||||
## Props | |||||
<Props of="TextInput" /> | |||||
## Usage Notes | |||||
The component will behave as `block`, i.e. it takes the remaining of the horizontal space. | |||||
To use the component together with layouts, see the following examples. | |||||
### Inline | |||||
The components are surrounded by `inline-block` elements. These surrounding elements have specified widths, which could | |||||
act as guide to the user on how long the expected input values are. | |||||
<Playground | |||||
components={{ TextInput }} | |||||
code={` | |||||
<form> | |||||
I am <span style={{ display: 'inline-block', width: '16rem', verticalAlign: 'bottom', }}><TextInput label="Full name" hint="given and family name" /></span> and I live in <span style={{ display: 'inline-block', width: '24rem', verticalAlign: 'bottom', }}><TextInput label="Address" hint="city, state and country" /></span>. | |||||
</form> | |||||
`} | |||||
/> | |||||
### Grid | |||||
It is advisable to put surrounding elements instead of the `TextInput` components themselves as children of the | |||||
element specified as `grid`. This is to be able to add complementing content to the components, if for example there are | |||||
some content that is best displayed outside the component instead of putting in the `hint` prop. | |||||
<Playground | |||||
components={{ TextInput }} | |||||
code={` | |||||
<form | |||||
style={{ display: 'grid', gridTemplateColumns: '4fr 4fr 5fr', gap: '1rem', }} | |||||
> | |||||
<div style={{ gridColumnStart: 1, gridColumnEnd: 4, }}> | |||||
<TextInput alternate border label="Address line 1" hint="unit/house number, building" /> | |||||
</div> | |||||
<div style={{ gridColumnStart: 1, gridColumnEnd: 4, }}> | |||||
<TextInput alternate border label="Address line 2" hint="street, area" /> | |||||
</div> | |||||
<div> | |||||
<TextInput alternate border size="large" label="City/Town" /> | |||||
</div> | |||||
<div> | |||||
<TextInput alternate border size="large" label="State/Province" /> | |||||
</div> | |||||
<div> | |||||
<TextInput alternate border size="large" label="Country" hint="abbreviations are accepted" /> | |||||
<small> | |||||
Consult the <a href="#">fees table</a> for shipping fee details. | |||||
</small> | |||||
</div> | |||||
</form> | |||||
`} | |||||
/> | |||||
## See Also | |||||
- [Select](./Select) for a graphically-similar component suitable for selecting more values. |
@@ -0,0 +1,91 @@ | |||||
# Tesseract Web - React Common | |||||
Common front-end components for Web using the [Tesseract design system](https://make.modal.sh/tesseract/design), written for [React](https://reactjs.org). | |||||
Package: | |||||
[![@tesseract-design/react-common](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=package&color=C78AB3&kind=name)](https://js.pack.modal.sh/-/web/detail/@tesseract-design/react-common) | |||||
[![@tesseract-design/react-common](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=package&color=C78AB3&kind=version)](https://js.pack.modal.sh/-/web/detail/@tesseract-design/react-common#versions) | |||||
Dependencies: | |||||
[![React](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=react&kind=peerDependencies)](https://github.com/facebook/react) | |||||
[![Styled Components](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=styled-components&kind=peerDependencies)](https://github.com/styled-components/styled-components) | |||||
[![TypeScript](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=typescript&kind=devDependencies)](https://github.com/microsoft/typescript) | |||||
[![Jest](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=jest&kind=devDependencies)](https://github.com/facebook/jest) | |||||
[![Rollup](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=rollup&kind=devDependencies)](https://github.com/rollup/rollup) | |||||
## Installation | |||||
Since this package resides in the [Modal.sh JavaScript Package Registry](https://js.pack.modal.sh/), you may need to | |||||
adjust configuration in your chosen package manager. | |||||
With [Yarn](https://yarnpkg.com), add this to your `.yarnrc` file: | |||||
``` | |||||
"@tesseract-design:registry" "https://js.pack.modal.sh/" | |||||
``` | |||||
Then, install the package by running the following command: | |||||
```shell script | |||||
yarn add @tesseract-design/react-common | |||||
``` | |||||
## Usage | |||||
The package includes components as named exports. Simply import the components you need individually or use a namespace | |||||
import, like so: | |||||
```jsx harmony | |||||
import * as React from 'react' | |||||
import ReactDOM from 'react-dom' | |||||
import * as T from '@tesseract-design/react-common' | |||||
const LoginForm = etcProps => ( | |||||
<form | |||||
{...etcProps} | |||||
> | |||||
<fieldset> | |||||
<legend> | |||||
Log In | |||||
</legend> | |||||
<div> | |||||
<T.TextInput | |||||
block | |||||
label="Username" | |||||
/> | |||||
</div> | |||||
<div> | |||||
<T.TextInput | |||||
block | |||||
type="password" | |||||
label="Password" | |||||
/> | |||||
</div> | |||||
<div> | |||||
<T.Button> | |||||
Log In | |||||
</T.Button> | |||||
</div> | |||||
</fieldset> | |||||
</form> | |||||
) | |||||
const mountNode = window.document.createElement('div') | |||||
ReactDOM.render( | |||||
<LoginForm />, | |||||
mountNode, | |||||
) | |||||
window.document.body.appendChild(mountNode) | |||||
``` | |||||
Detailed usage guides can be found in the [Tesseract Design - React Common documentation](https://make.modal.sh/tesseract/web/react/common). | |||||
## TypeScript | |||||
The package is written and tested using TypeScript. Thus, typings for consumption in TypeScript are bundled with the | |||||
compiled source. | |||||
@@ -1,6 +1,5 @@ | |||||
--- | --- | ||||
name: Theming | name: Theming | ||||
route: /theming | |||||
--- | --- | ||||
# Theming | # Theming |
@@ -0,0 +1,18 @@ | |||||
{ | |||||
"nav": [ | |||||
{ | |||||
"Theming": "/theming" | |||||
}, | |||||
{ | |||||
"Components": [ | |||||
{ "Button": "/components/Button" }, | |||||
{ "Checkbox": "/components/Checkbox" }, | |||||
{ "Icon": "/components/Icon" }, | |||||
{ "RadioButton": "/components/RadioButton" }, | |||||
{ "Select": "/components/Select" }, | |||||
{ "Slider": "/components/Slider" }, | |||||
{ "TextInput": "/components/TextInput" } | |||||
] | |||||
} | |||||
] | |||||
} |
@@ -0,0 +1,37 @@ | |||||
{ | |||||
"compilerOptions": { | |||||
"target": "es5", | |||||
"lib": [ | |||||
"dom", | |||||
"dom.iterable", | |||||
"esnext" | |||||
], | |||||
"allowJs": true, | |||||
"skipLibCheck": true, | |||||
"esModuleInterop": true, | |||||
"allowSyntheticDefaultImports": true, | |||||
"strict": true, | |||||
"forceConsistentCasingInFileNames": true, | |||||
"module": "esnext", | |||||
"moduleResolution": "node", | |||||
"resolveJsonModule": true, | |||||
"isolatedModules": true, | |||||
"noEmit": true, | |||||
"jsx": "preserve", | |||||
"declaration": true, | |||||
"declarationDir": "./dist", | |||||
"sourceMap": true | |||||
}, | |||||
"exclude": [ | |||||
"node_modules", | |||||
"**/*.test.ts", | |||||
"**/*.test.tsx", | |||||
"utilities/**/*", | |||||
"jest.setup.ts" | |||||
], | |||||
"include": [ | |||||
"next-env.d.ts", | |||||
"**/*.ts", | |||||
"**/*.tsx" | |||||
] | |||||
} |
@@ -1,5 +1,5 @@ | |||||
/// <reference types="jest-enzyme" /> | /// <reference types="jest-enzyme" /> | ||||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||||
/// <reference path="../../../../../utilities/jest/extensions.ts" /> | |||||
import * as fc from 'fast-check' | import * as fc from 'fast-check' | ||||
import * as Enzyme from 'enzyme' | import * as Enzyme from 'enzyme' |
@@ -172,6 +172,9 @@ const propTypes = { | |||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
/** | |||||
* Component for performing an action upon activation (e.g. when clicked). | |||||
*/ | |||||
const Button = React.forwardRef<HTMLAnchorElement | HTMLButtonElement | HTMLSpanElement, Props>( | const Button = React.forwardRef<HTMLAnchorElement | HTMLButtonElement | HTMLSpanElement, Props>( | ||||
( | ( | ||||
{ | { |
@@ -1,5 +1,5 @@ | |||||
/// <reference types="jest-enzyme" /> | /// <reference types="jest-enzyme" /> | ||||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||||
/// <reference path="../../../../../utilities/jest/extensions.ts" /> | |||||
import * as fc from 'fast-check' | import * as fc from 'fast-check' | ||||
import * as Enzyme from 'enzyme' | import * as Enzyme from 'enzyme' |
@@ -1,5 +1,5 @@ | |||||
/// <reference types="jest-enzyme" /> | /// <reference types="jest-enzyme" /> | ||||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||||
/// <reference path="../../../../../utilities/jest/extensions.ts" /> | |||||
import * as fc from 'fast-check' | import * as fc from 'fast-check' | ||||
import * as Enzyme from 'enzyme' | import * as Enzyme from 'enzyme' |
@@ -43,6 +43,14 @@ const propTypes = { | |||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
/** | |||||
* Component for displaying graphics. | |||||
* @param name | |||||
* @param weight | |||||
* @param size | |||||
* @param label | |||||
* @constructor | |||||
*/ | |||||
const Icon: React.FC<Props> = ({ name, weight = '0.125rem', size = '1.5rem', label = name }) => { | const Icon: React.FC<Props> = ({ name, weight = '0.125rem', size = '1.5rem', label = name }) => { | ||||
const iconName = pascalCase(name, { transform: pascalCaseTransformMerge }) | const iconName = pascalCase(name, { transform: pascalCaseTransformMerge }) | ||||
const { [iconName as keyof typeof FeatherIcon]: TheIcon = null } = FeatherIcon | const { [iconName as keyof typeof FeatherIcon]: TheIcon = null } = FeatherIcon |
@@ -1,5 +1,5 @@ | |||||
/// <reference types="jest-enzyme" /> | /// <reference types="jest-enzyme" /> | ||||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||||
/// <reference path="../../../../../utilities/jest/extensions.ts" /> | |||||
import * as fc from 'fast-check' | import * as fc from 'fast-check' | ||||
import * as Enzyme from 'enzyme' | import * as Enzyme from 'enzyme' |
@@ -1,5 +1,5 @@ | |||||
/// <reference types="jest-enzyme" /> | /// <reference types="jest-enzyme" /> | ||||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||||
/// <reference path="../../../../../utilities/jest/extensions.ts" /> | |||||
import * as fc from 'fast-check' | import * as fc from 'fast-check' | ||||
import * as Enzyme from 'enzyme' | import * as Enzyme from 'enzyme' |
@@ -218,6 +218,9 @@ const propTypes = { | |||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
/** | |||||
* Component for selecting values from a larger number of options. | |||||
*/ | |||||
const Select = React.forwardRef<HTMLSelectElement, Props>( | const Select = React.forwardRef<HTMLSelectElement, Props>( | ||||
( | ( | ||||
{ | { |
@@ -1,5 +1,5 @@ | |||||
/// <reference types="jest-enzyme" /> | /// <reference types="jest-enzyme" /> | ||||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||||
/// <reference path="../../../../../utilities/jest/extensions.ts" /> | |||||
import * as fc from 'fast-check' | import * as fc from 'fast-check' | ||||
import * as Enzyme from 'enzyme' | import * as Enzyme from 'enzyme' |
@@ -1,5 +1,5 @@ | |||||
/// <reference types="jest-enzyme" /> | /// <reference types="jest-enzyme" /> | ||||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||||
/// <reference path="../../../../../utilities/jest/extensions.ts" /> | |||||
import * as fc from 'fast-check' | import * as fc from 'fast-check' | ||||
import * as Enzyme from 'enzyme' | import * as Enzyme from 'enzyme' |
@@ -4,6 +4,8 @@ import styled from 'styled-components' | |||||
import stringify from '../../services/stringify' | import stringify from '../../services/stringify' | ||||
import { Size, SizeMap } from '../../services/utilities' | import { Size, SizeMap } from '../../services/utilities' | ||||
// TODO implement web-client text inputs! | |||||
const MIN_HEIGHTS: SizeMap<string | number> = { | const MIN_HEIGHTS: SizeMap<string | number> = { | ||||
small: '2.5rem', | small: '2.5rem', | ||||
medium: '3rem', | medium: '3rem', | ||||
@@ -60,7 +62,6 @@ const LabelWrapper = styled('span')({ | |||||
position: 'absolute', | position: 'absolute', | ||||
top: 0, | top: 0, | ||||
left: 0, | left: 0, | ||||
paddingLeft: '0.5rem', | |||||
fontSize: '0.85em', | fontSize: '0.85em', | ||||
maxWidth: '100%', | maxWidth: '100%', | ||||
overflow: 'hidden', | overflow: 'hidden', | ||||
@@ -118,7 +119,6 @@ const Input = styled('input')({ | |||||
position: 'relative', | position: 'relative', | ||||
border: 0, | border: 0, | ||||
borderRadius: 'inherit', | borderRadius: 'inherit', | ||||
paddingLeft: '1rem', | |||||
margin: 0, | margin: 0, | ||||
font: 'inherit', | font: 'inherit', | ||||
minHeight: '4rem', | minHeight: '4rem', | ||||
@@ -148,7 +148,6 @@ const TextArea = styled('textarea')({ | |||||
position: 'relative', | position: 'relative', | ||||
border: 0, | border: 0, | ||||
borderRadius: 'inherit', | borderRadius: 'inherit', | ||||
paddingLeft: '1rem', | |||||
margin: 0, | margin: 0, | ||||
font: 'inherit', | font: 'inherit', | ||||
minHeight: '4rem', | minHeight: '4rem', | ||||
@@ -166,9 +165,7 @@ TextArea.displayName = 'textarea' | |||||
const HintWrapper = styled('span')({ | const HintWrapper = styled('span')({ | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
position: 'absolute', | position: 'absolute', | ||||
bottom: 0, | |||||
left: 0, | left: 0, | ||||
paddingLeft: '1rem', | |||||
fontSize: '0.85em', | fontSize: '0.85em', | ||||
opacity: 0.5, | opacity: 0.5, | ||||
maxWidth: '100%', | maxWidth: '100%', | ||||
@@ -250,10 +247,17 @@ const propTypes = { | |||||
* Event handler triggered when the component loses focus. | * Event handler triggered when the component loses focus. | ||||
*/ | */ | ||||
onBlur: PropTypes.func, | onBlur: PropTypes.func, | ||||
/** | |||||
* Should the component be displayed with an alternate appearance? | |||||
*/ | |||||
alternate: PropTypes.bool, | |||||
} | } | ||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
/** | |||||
* Component for inputting textual values. | |||||
*/ | |||||
const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props>( | const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props>( | ||||
( | ( | ||||
{ | { | ||||
@@ -270,6 +274,7 @@ const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props | |||||
onChange, | onChange, | ||||
onFocus, | onFocus, | ||||
onBlur, | onBlur, | ||||
alternate = false, | |||||
...etcProps | ...etcProps | ||||
}, | }, | ||||
ref, | ref, | ||||
@@ -283,6 +288,7 @@ const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props | |||||
<CaptureArea> | <CaptureArea> | ||||
<LabelWrapper | <LabelWrapper | ||||
style={{ | style={{ | ||||
paddingLeft: alternate ? '0.5rem' : undefined, | |||||
paddingTop: LABEL_VERTICAL_PADDING_SIZES[size!], | paddingTop: LABEL_VERTICAL_PADDING_SIZES[size!], | ||||
paddingBottom: LABEL_VERTICAL_PADDING_SIZES[size!], | paddingBottom: LABEL_VERTICAL_PADDING_SIZES[size!], | ||||
paddingRight: indicator ? MIN_HEIGHTS[size!] : '0.5rem', | paddingRight: indicator ? MIN_HEIGHTS[size!] : '0.5rem', | ||||
@@ -306,9 +312,10 @@ const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props | |||||
height: `calc(${MIN_HEIGHTS[size!]} * ${rows})`, | height: `calc(${MIN_HEIGHTS[size!]} * ${rows})`, | ||||
fontSize: INPUT_FONT_SIZES[size!], | fontSize: INPUT_FONT_SIZES[size!], | ||||
minHeight: MIN_HEIGHTS[size!], | minHeight: MIN_HEIGHTS[size!], | ||||
paddingTop: VERTICAL_PADDING_SIZES[size!], | |||||
paddingTop: alternate ? VERTICAL_PADDING_SIZES[size!] : `calc(${SECONDARY_TEXT_SIZES[size!]} * 2)`, | |||||
paddingBottom: VERTICAL_PADDING_SIZES[size!], | paddingBottom: VERTICAL_PADDING_SIZES[size!], | ||||
paddingRight: indicator ? MIN_HEIGHTS[size!] : '1rem', | |||||
paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? '1rem' : undefined), | |||||
paddingLeft: alternate ? '1rem' : undefined, | |||||
}} | }} | ||||
/> | /> | ||||
)} | )} | ||||
@@ -322,9 +329,11 @@ const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props | |||||
ref={ref as React.Ref<HTMLInputElement>} | ref={ref as React.Ref<HTMLInputElement>} | ||||
disabled={disabled!} | disabled={disabled!} | ||||
style={{ | style={{ | ||||
paddingLeft: alternate ? '1rem' : undefined, | |||||
fontSize: INPUT_FONT_SIZES[size!], | fontSize: INPUT_FONT_SIZES[size!], | ||||
minHeight: MIN_HEIGHTS[size!], | minHeight: MIN_HEIGHTS[size!], | ||||
paddingRight: indicator ? MIN_HEIGHTS[size!] : '1rem', | |||||
paddingTop: alternate ? undefined : `calc(${SECONDARY_TEXT_SIZES[size!]} * 2)`, | |||||
paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? '1rem' : undefined), | |||||
}} | }} | ||||
/> | /> | ||||
)} | )} | ||||
@@ -333,6 +342,9 @@ const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props | |||||
{stringify(hint).length > 0 && ( | {stringify(hint).length > 0 && ( | ||||
<HintWrapper | <HintWrapper | ||||
style={{ | style={{ | ||||
top: alternate ? undefined : '0.75rem', | |||||
bottom: alternate ? 0 : undefined, | |||||
paddingLeft: alternate ? '1rem' : undefined, | |||||
paddingTop: LABEL_VERTICAL_PADDING_SIZES[size!], | paddingTop: LABEL_VERTICAL_PADDING_SIZES[size!], | ||||
paddingBottom: LABEL_VERTICAL_PADDING_SIZES[size!], | paddingBottom: LABEL_VERTICAL_PADDING_SIZES[size!], | ||||
paddingRight: indicator ? MIN_HEIGHTS[size!] : '1rem', | paddingRight: indicator ? MIN_HEIGHTS[size!] : '1rem', |
@@ -1,5 +1,5 @@ | |||||
/// <reference types="jest-enzyme" /> | /// <reference types="jest-enzyme" /> | ||||
/// <reference path="../utilities/jest/extensions.ts" /> | |||||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||||
import * as T from './index' | import * as T from './index' | ||||
@@ -0,0 +1,40 @@ | |||||
import * as fc from 'fast-check' | |||||
import isEmpty from './isEmpty' | |||||
it('should exist', () => { | |||||
expect(isEmpty).toBeDefined() | |||||
}) | |||||
it('should be a function', () => { | |||||
expect(typeof isEmpty).toBe('function') | |||||
}) | |||||
it('should accept 1 argument', () => { | |||||
expect(isEmpty).toHaveLength(1) | |||||
}) | |||||
it('should return a boolean value', () => { | |||||
fc.assert( | |||||
fc.property(fc.anything(), (v) => { | |||||
expect(typeof isEmpty(v)).toBe('boolean') | |||||
}), | |||||
) | |||||
}) | |||||
describe('on arguments', () => { | |||||
it('should return `true` on an argument with value of `undefined`', () => { | |||||
expect(isEmpty(undefined)).toBe(true) | |||||
}) | |||||
it('should return `true` on an argument with value of `null`', () => { | |||||
expect(isEmpty(null)).toBe(true) | |||||
}) | |||||
it('should return `false` on an argument with value that is neither `undefined` nor `null`', () => { | |||||
fc.assert( | |||||
fc.property(fc.anything().filter((v) => typeof v !== 'undefined' && v !== null), (v) => { | |||||
expect(isEmpty(v)).toBe(false) | |||||
}), | |||||
) | |||||
}) | |||||
}) |
@@ -1,5 +1,5 @@ | |||||
import * as fc from 'fast-check' | import * as fc from 'fast-check' | ||||
import * as fcArb from '../../utilities/fast-check/arbitraries' | |||||
import * as fcArb from '../../../../utilities/fast-check/arbitraries' | |||||
import stringify from './stringify' | import stringify from './stringify' | ||||
it('should exist', () => { | it('should exist', () => { |
@@ -1,4 +1,3 @@ | |||||
const testName = require('./plop/helpers/testName.js') | |||||
module.exports = plop => { | module.exports = plop => { | ||||
plop.setGenerator('component', { | plop.setGenerator('component', { | ||||
description: 'Creates a component.', | description: 'Creates a component.', | ||||
@@ -21,9 +20,15 @@ module.exports = plop => { | |||||
actions: [ | actions: [ | ||||
{ | { | ||||
type: 'addMany', | type: 'addMany', | ||||
templateFiles: 'plop/templates/component/*', | |||||
base: 'plop/templates/component', | |||||
destination: 'lib/components/{{pascalCase name}}', | |||||
templateFiles: 'plop/templates/react-common/component/*', | |||||
base: 'plop/templates/react-common/component', | |||||
destination: 'packages/react-common/src/components/{{pascalCase name}}', | |||||
}, | |||||
{ | |||||
type: 'addMany', | |||||
templateFiles: 'plop/templates/react-common-docs/component/*', | |||||
base: 'plop/templates/react-common-docs/component', | |||||
destination: 'packages/react-common-docs/src/pages/components', | |||||
}, | }, | ||||
], | ], | ||||
}) | }) | ||||
@@ -4,7 +4,7 @@ import typescript from '@rollup/plugin-typescript' | |||||
import pkg from './package.json' | import pkg from './package.json' | ||||
const ENTRY_POINT = './lib/index.ts' | |||||
const ENTRY_POINT = './packages/react-common/src/index.ts' | |||||
export default { | export default { | ||||
input: ENTRY_POINT, | input: ENTRY_POINT, | ||||