feature/docs
vers master
il y a 3 ans
@@ -66,6 +66,4 @@ typings/ | |||
.env | |||
.next | |||
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', | |||
], | |||
collectCoverageFrom: [ | |||
'./lib/**/*.{ts,tsx}', | |||
'!./lib/**/*.stories.{ts,tsx}' | |||
'./packages/**/*.{ts,tsx}', | |||
'!./packages/**/*.stories.{ts,tsx}' | |||
], | |||
preset: 'ts-jest', | |||
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", | |||
"private": false, | |||
"devDependencies": { | |||
"@babel/runtime": "^7.12.5", | |||
"@reach/slider": "^0.10.5", | |||
"@rollup/plugin-typescript": "^5.0.2", | |||
"@types/enzyme": "^3.10.5", | |||
"@types/enzyme-adapter-react-16": "^1.0.6", | |||
@@ -29,23 +31,20 @@ | |||
"jest-axe": "3.4.0", | |||
"jest-enzyme": "7.1.2", | |||
"jest-extended": "0.11.5", | |||
"pascal-case": "3.1.1", | |||
"plop": "2.6.0", | |||
"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-plugin-peer-deps-external": "2.2.2", | |||
"rollup-plugin-terser": "5.3.0", | |||
"styled-components": "5.1.0", | |||
"ts-jest": "^26.1.3", | |||
"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": { | |||
"prepublishOnly": "NODE_ENV=production rm -rf dist/ && rollup -c", | |||
@@ -64,5 +63,6 @@ | |||
"react-feather": "2.0.3", | |||
"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 of={Button} /> | |||
<Props of="Button" /> | |||
## 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 | |||
route: /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 path="../../../utilities/jest/extensions.ts" /> | |||
/// <reference path="../../../../../utilities/jest/extensions.ts" /> | |||
import * as fc from 'fast-check' | |||
import * as Enzyme from 'enzyme' |
@@ -172,6 +172,9 @@ const 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>( | |||
( | |||
{ |
@@ -1,5 +1,5 @@ | |||
/// <reference types="jest-enzyme" /> | |||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||
/// <reference path="../../../../../utilities/jest/extensions.ts" /> | |||
import * as fc from 'fast-check' | |||
import * as Enzyme from 'enzyme' |
@@ -1,5 +1,5 @@ | |||
/// <reference types="jest-enzyme" /> | |||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||
/// <reference path="../../../../../utilities/jest/extensions.ts" /> | |||
import * as fc from 'fast-check' | |||
import * as Enzyme from 'enzyme' |
@@ -43,6 +43,14 @@ const 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 iconName = pascalCase(name, { transform: pascalCaseTransformMerge }) | |||
const { [iconName as keyof typeof FeatherIcon]: TheIcon = null } = FeatherIcon |
@@ -1,5 +1,5 @@ | |||
/// <reference types="jest-enzyme" /> | |||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||
/// <reference path="../../../../../utilities/jest/extensions.ts" /> | |||
import * as fc from 'fast-check' | |||
import * as Enzyme from 'enzyme' |
@@ -1,5 +1,5 @@ | |||
/// <reference types="jest-enzyme" /> | |||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||
/// <reference path="../../../../../utilities/jest/extensions.ts" /> | |||
import * as fc from 'fast-check' | |||
import * as Enzyme from 'enzyme' |
@@ -218,6 +218,9 @@ const propTypes = { | |||
type Props = PropTypes.InferProps<typeof propTypes> | |||
/** | |||
* Component for selecting values from a larger number of options. | |||
*/ | |||
const Select = React.forwardRef<HTMLSelectElement, Props>( | |||
( | |||
{ |
@@ -1,5 +1,5 @@ | |||
/// <reference types="jest-enzyme" /> | |||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||
/// <reference path="../../../../../utilities/jest/extensions.ts" /> | |||
import * as fc from 'fast-check' | |||
import * as Enzyme from 'enzyme' |
@@ -1,5 +1,5 @@ | |||
/// <reference types="jest-enzyme" /> | |||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||
/// <reference path="../../../../../utilities/jest/extensions.ts" /> | |||
import * as fc from 'fast-check' | |||
import * as Enzyme from 'enzyme' |
@@ -4,6 +4,8 @@ import styled from 'styled-components' | |||
import stringify from '../../services/stringify' | |||
import { Size, SizeMap } from '../../services/utilities' | |||
// TODO implement web-client text inputs! | |||
const MIN_HEIGHTS: SizeMap<string | number> = { | |||
small: '2.5rem', | |||
medium: '3rem', | |||
@@ -60,7 +62,6 @@ const LabelWrapper = styled('span')({ | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
paddingLeft: '0.5rem', | |||
fontSize: '0.85em', | |||
maxWidth: '100%', | |||
overflow: 'hidden', | |||
@@ -118,7 +119,6 @@ const Input = styled('input')({ | |||
position: 'relative', | |||
border: 0, | |||
borderRadius: 'inherit', | |||
paddingLeft: '1rem', | |||
margin: 0, | |||
font: 'inherit', | |||
minHeight: '4rem', | |||
@@ -148,7 +148,6 @@ const TextArea = styled('textarea')({ | |||
position: 'relative', | |||
border: 0, | |||
borderRadius: 'inherit', | |||
paddingLeft: '1rem', | |||
margin: 0, | |||
font: 'inherit', | |||
minHeight: '4rem', | |||
@@ -166,9 +165,7 @@ TextArea.displayName = 'textarea' | |||
const HintWrapper = styled('span')({ | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: 0, | |||
left: 0, | |||
paddingLeft: '1rem', | |||
fontSize: '0.85em', | |||
opacity: 0.5, | |||
maxWidth: '100%', | |||
@@ -250,10 +247,17 @@ const propTypes = { | |||
* Event handler triggered when the component loses focus. | |||
*/ | |||
onBlur: PropTypes.func, | |||
/** | |||
* Should the component be displayed with an alternate appearance? | |||
*/ | |||
alternate: PropTypes.bool, | |||
} | |||
type Props = PropTypes.InferProps<typeof propTypes> | |||
/** | |||
* Component for inputting textual values. | |||
*/ | |||
const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props>( | |||
( | |||
{ | |||
@@ -270,6 +274,7 @@ const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props | |||
onChange, | |||
onFocus, | |||
onBlur, | |||
alternate = false, | |||
...etcProps | |||
}, | |||
ref, | |||
@@ -283,6 +288,7 @@ const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props | |||
<CaptureArea> | |||
<LabelWrapper | |||
style={{ | |||
paddingLeft: alternate ? '0.5rem' : undefined, | |||
paddingTop: LABEL_VERTICAL_PADDING_SIZES[size!], | |||
paddingBottom: LABEL_VERTICAL_PADDING_SIZES[size!], | |||
paddingRight: indicator ? MIN_HEIGHTS[size!] : '0.5rem', | |||
@@ -306,9 +312,10 @@ const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props | |||
height: `calc(${MIN_HEIGHTS[size!]} * ${rows})`, | |||
fontSize: INPUT_FONT_SIZES[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!], | |||
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>} | |||
disabled={disabled!} | |||
style={{ | |||
paddingLeft: alternate ? '1rem' : undefined, | |||
fontSize: INPUT_FONT_SIZES[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 && ( | |||
<HintWrapper | |||
style={{ | |||
top: alternate ? undefined : '0.75rem', | |||
bottom: alternate ? 0 : undefined, | |||
paddingLeft: alternate ? '1rem' : undefined, | |||
paddingTop: LABEL_VERTICAL_PADDING_SIZES[size!], | |||
paddingBottom: LABEL_VERTICAL_PADDING_SIZES[size!], | |||
paddingRight: indicator ? MIN_HEIGHTS[size!] : '1rem', |
@@ -1,5 +1,5 @@ | |||
/// <reference types="jest-enzyme" /> | |||
/// <reference path="../utilities/jest/extensions.ts" /> | |||
/// <reference path="../../../utilities/jest/extensions.ts" /> | |||
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 fcArb from '../../utilities/fast-check/arbitraries' | |||
import * as fcArb from '../../../../utilities/fast-check/arbitraries' | |||
import stringify from './stringify' | |||
it('should exist', () => { |
@@ -1,4 +1,3 @@ | |||
const testName = require('./plop/helpers/testName.js') | |||
module.exports = plop => { | |||
plop.setGenerator('component', { | |||
description: 'Creates a component.', | |||
@@ -21,9 +20,15 @@ module.exports = plop => { | |||
actions: [ | |||
{ | |||
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' | |||
const ENTRY_POINT = './lib/index.ts' | |||
const ENTRY_POINT = './packages/react-common/src/index.ts' | |||
export default { | |||
input: ENTRY_POINT, | |||