#1 feature/docs

Merged
TheoryOfNekomata merged 6 commits from feature/docs into master 4 years ago
  1. +1
    -3
      .gitignore
  2. +0
    -91
      README.md
  3. +1
    -0
      README.md
  4. +2
    -2
      jest.config.js
  5. +0
    -25
      lib/components/Checkbox/Checkbox.mdx
  6. +0
    -20
      lib/components/Icon/Icon.mdx
  7. +0
    -32
      lib/components/RadioButton/RadioButton.mdx
  8. +0
    -45
      lib/components/Select/Select.mdx
  9. +0
    -27
      lib/components/Slider/Slider.mdx
  10. +0
    -71
      lib/components/TextInput/TextInput.mdx
  11. +0
    -42
      lib/services/isEmpty.test.ts
  12. +11
    -11
      package.json
  13. +40
    -0
      packages/react-common-docs/brand.tsx
  14. +3
    -0
      packages/react-common-docs/next-env.d.ts
  15. +25
    -0
      packages/react-common-docs/next.config.js
  16. +26
    -0
      packages/react-common-docs/package.json
  17. BIN
      packages/react-common-docs/public/favicon.ico
  18. +52
    -0
      packages/react-common-docs/public/global.css
  19. +138
    -0
      packages/react-common-docs/public/theme/dark.css
  20. +146
    -0
      packages/react-common-docs/public/theme/light.css
  21. +4
    -0
      packages/react-common-docs/public/vercel.svg
  22. +24
    -0
      packages/react-common-docs/scripts/docgen
  23. +35
    -0
      packages/react-common-docs/src/components/Header/Header.tsx
  24. +50
    -0
      packages/react-common-docs/src/components/MenuGraphics/MenuGraphics.tsx
  25. +91
    -0
      packages/react-common-docs/src/components/Nav/Nav.tsx
  26. +151
    -0
      packages/react-common-docs/src/components/NavLink/NavLink.tsx
  27. +53
    -0
      packages/react-common-docs/src/components/Playground/Playground.tsx
  28. +176
    -0
      packages/react-common-docs/src/components/Props/Props.tsx
  29. +186
    -0
      packages/react-common-docs/src/components/Sidebar/Sidebar.tsx
  30. +694
    -0
      packages/react-common-docs/src/docgen.json
  31. +40
    -0
      packages/react-common-docs/src/pages/_app.tsx
  32. +45
    -0
      packages/react-common-docs/src/pages/_document.tsx
  33. +15
    -12
      packages/react-common-docs/src/pages/components/Button.mdx
  34. +26
    -0
      packages/react-common-docs/src/pages/components/Checkbox.mdx
  35. +21
    -0
      packages/react-common-docs/src/pages/components/Icon.mdx
  36. +33
    -0
      packages/react-common-docs/src/pages/components/RadioButton.mdx
  37. +46
    -0
      packages/react-common-docs/src/pages/components/Select.mdx
  38. +25
    -0
      packages/react-common-docs/src/pages/components/Slider.mdx
  39. +82
    -0
      packages/react-common-docs/src/pages/components/TextInput.mdx
  40. +91
    -0
      packages/react-common-docs/src/pages/index.md
  41. +0
    -1
      packages/react-common-docs/src/pages/theming.md
  42. +18
    -0
      packages/react-common-docs/src/sidebar.json
  43. +37
    -0
      packages/react-common-docs/tsconfig.json
  44. +6563
    -0
      packages/react-common-docs/yarn.lock
  45. +1
    -1
      packages/react-common/src/components/Button/Button.test.tsx
  46. +3
    -0
      packages/react-common/src/components/Button/Button.tsx
  47. +1
    -1
      packages/react-common/src/components/Checkbox/Checkbox.test.tsx
  48. +0
    -0
      packages/react-common/src/components/Checkbox/Checkbox.tsx
  49. +1
    -1
      packages/react-common/src/components/Icon/Icon.test.tsx
  50. +8
    -0
      packages/react-common/src/components/Icon/Icon.tsx
  51. +1
    -1
      packages/react-common/src/components/RadioButton/RadioButton.test.tsx
  52. +0
    -0
      packages/react-common/src/components/RadioButton/RadioButton.tsx
  53. +1
    -1
      packages/react-common/src/components/Select/Select.test.tsx
  54. +3
    -0
      packages/react-common/src/components/Select/Select.tsx
  55. +1
    -1
      packages/react-common/src/components/Slider/Slider.test.tsx
  56. +0
    -0
      packages/react-common/src/components/Slider/Slider.tsx
  57. +1
    -1
      packages/react-common/src/components/TextInput/TextInput.test.tsx
  58. +20
    -8
      packages/react-common/src/components/TextInput/TextInput.tsx
  59. +1
    -1
      packages/react-common/src/index.test.ts
  60. +0
    -0
      packages/react-common/src/index.ts
  61. +40
    -0
      packages/react-common/src/services/isEmpty.test.ts
  62. +0
    -0
      packages/react-common/src/services/isEmpty.ts
  63. +0
    -0
      packages/react-common/src/services/splitValueAndUnit.test.ts
  64. +0
    -0
      packages/react-common/src/services/splitValueAndUnit.ts
  65. +1
    -1
      packages/react-common/src/services/stringify.test.ts
  66. +0
    -0
      packages/react-common/src/services/stringify.ts
  67. +0
    -0
      packages/react-common/src/services/utilities.ts
  68. +0
    -0
      plop/templates/react-common-docs/component/{{pascalCase name}}.mdx.hbs
  69. +0
    -0
      plop/templates/react-common/component/{{pascalCase name}}.test.tsx.hbs
  70. +0
    -0
      plop/templates/react-common/component/{{pascalCase name}}.tsx.hbs
  71. +9
    -4
      plopfile.js
  72. +1
    -1
      rollup.config.js
  73. +1253
    -11314
      yarn.lock

+ 1
- 3
.gitignore View File

@@ -66,6 +66,4 @@ typings/
.env
.next
dist/
.gitignore
.docz/
/docz.config.js
.next/

+ 0
- 91
README.md View File

@@ -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.


+ 1
- 0
README.md View File

@@ -0,0 +1 @@
packages/react-common-docs/src/pages/index.md

+ 2
- 2
jest.config.js View File

@@ -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,


+ 0
- 25
lib/components/Checkbox/Checkbox.mdx View File

@@ -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.

+ 0
- 20
lib/components/Icon/Icon.mdx View File

@@ -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} />

+ 0
- 32
lib/components/RadioButton/RadioButton.mdx View File

@@ -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.

+ 0
- 45
lib/components/Select/Select.mdx View File

@@ -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.

+ 0
- 27
lib/components/Slider/Slider.mdx View File

@@ -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.

+ 0
- 71
lib/components/TextInput/TextInput.mdx View File

@@ -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.

+ 0
- 42
lib/services/isEmpty.test.ts View File

@@ -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)
}),
)
})
})
})

+ 11
- 11
package.json View File

@@ -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": {}
}

+ 40
- 0
packages/react-common-docs/brand.tsx View File

@@ -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

+ 3
- 0
packages/react-common-docs/next-env.d.ts View File

@@ -0,0 +1,3 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
declare module 'remark-react'

+ 25
- 0
packages/react-common-docs/next.config.js View File

@@ -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
}
}

+ 26
- 0
packages/react-common-docs/package.json View File

@@ -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"
}
}

BIN
packages/react-common-docs/public/favicon.ico View File

Before After

+ 52
- 0
packages/react-common-docs/public/global.css View File

@@ -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);
}

+ 138
- 0
packages/react-common-docs/public/theme/dark.css View File

@@ -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;
}

+ 146
- 0
packages/react-common-docs/public/theme/light.css View File

@@ -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;
}

+ 4
- 0
packages/react-common-docs/public/vercel.svg View File

@@ -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>

+ 24
- 0
packages/react-common-docs/scripts/docgen View File

@@ -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) => {

})
})

+ 35
- 0
packages/react-common-docs/src/components/Header/Header.tsx View File

@@ -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

+ 50
- 0
packages/react-common-docs/src/components/MenuGraphics/MenuGraphics.tsx View File

@@ -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

+ 91
- 0
packages/react-common-docs/src/components/Nav/Nav.tsx View File

@@ -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

+ 151
- 0
packages/react-common-docs/src/components/NavLink/NavLink.tsx View File

@@ -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

+ 53
- 0
packages/react-common-docs/src/components/Playground/Playground.tsx View File

@@ -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

+ 176
- 0
packages/react-common-docs/src/components/Props/Props.tsx View File

@@ -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

+ 186
- 0
packages/react-common-docs/src/components/Sidebar/Sidebar.tsx View File

@@ -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

+ 694
- 0
packages/react-common-docs/src/docgen.json View File

@@ -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"
}
}
}
}
]

+ 40
- 0
packages/react-common-docs/src/pages/_app.tsx View File

@@ -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

+ 45
- 0
packages/react-common-docs/src/pages/_document.tsx View File

@@ -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>
)
}
}

lib/components/Button/Button.mdx → packages/react-common-docs/src/pages/components/Button.mdx View File

@@ -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


+ 26
- 0
packages/react-common-docs/src/pages/components/Checkbox.mdx View File

@@ -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.

+ 21
- 0
packages/react-common-docs/src/pages/components/Icon.mdx View File

@@ -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" />

+ 33
- 0
packages/react-common-docs/src/pages/components/RadioButton.mdx View File

@@ -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.

+ 46
- 0
packages/react-common-docs/src/pages/components/Select.mdx View File

@@ -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.

+ 25
- 0
packages/react-common-docs/src/pages/components/Slider.mdx View File

@@ -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.

+ 82
- 0
packages/react-common-docs/src/pages/components/TextInput.mdx View File

@@ -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.

+ 91
- 0
packages/react-common-docs/src/pages/index.md View File

@@ -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.


docs/theming.md → packages/react-common-docs/src/pages/theming.md View File

@@ -1,6 +1,5 @@
---
name: Theming
route: /theming
---

# Theming

+ 18
- 0
packages/react-common-docs/src/sidebar.json View File

@@ -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" }
]
}
]
}

+ 37
- 0
packages/react-common-docs/tsconfig.json View File

@@ -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"
]
}

+ 6563
- 0
packages/react-common-docs/yarn.lock
File diff suppressed because it is too large
View File


lib/components/Button/Button.test.tsx → packages/react-common/src/components/Button/Button.test.tsx View File

@@ -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'

lib/components/Button/Button.tsx → packages/react-common/src/components/Button/Button.tsx View File

@@ -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>(
(
{

lib/components/Checkbox/Checkbox.test.tsx → packages/react-common/src/components/Checkbox/Checkbox.test.tsx View File

@@ -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'

lib/components/Checkbox/Checkbox.tsx → packages/react-common/src/components/Checkbox/Checkbox.tsx View File


lib/components/Icon/Icon.test.tsx → packages/react-common/src/components/Icon/Icon.test.tsx View File

@@ -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'

lib/components/Icon/Icon.tsx → packages/react-common/src/components/Icon/Icon.tsx View File

@@ -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

lib/components/RadioButton/RadioButton.test.tsx → packages/react-common/src/components/RadioButton/RadioButton.test.tsx View File

@@ -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'

lib/components/RadioButton/RadioButton.tsx → packages/react-common/src/components/RadioButton/RadioButton.tsx View File


lib/components/Select/Select.test.tsx → packages/react-common/src/components/Select/Select.test.tsx View File

@@ -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'

lib/components/Select/Select.tsx → packages/react-common/src/components/Select/Select.tsx View File

@@ -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>(
(
{

lib/components/Slider/Slider.test.tsx → packages/react-common/src/components/Slider/Slider.test.tsx View File

@@ -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'

lib/components/Slider/Slider.tsx → packages/react-common/src/components/Slider/Slider.tsx View File


lib/components/TextInput/TextInput.test.tsx → packages/react-common/src/components/TextInput/TextInput.test.tsx View File

@@ -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'

lib/components/TextInput/TextInput.tsx → packages/react-common/src/components/TextInput/TextInput.tsx View File

@@ -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',

lib/index.test.ts → packages/react-common/src/index.test.ts View File

@@ -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'


lib/index.ts → packages/react-common/src/index.ts View File


+ 40
- 0
packages/react-common/src/services/isEmpty.test.ts View File

@@ -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)
}),
)
})
})

lib/services/isEmpty.ts → packages/react-common/src/services/isEmpty.ts View File


lib/services/splitValueAndUnit.test.ts → packages/react-common/src/services/splitValueAndUnit.test.ts View File


lib/services/splitValueAndUnit.ts → packages/react-common/src/services/splitValueAndUnit.ts View File


lib/services/stringify.test.ts → packages/react-common/src/services/stringify.test.ts View File

@@ -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', () => {

lib/services/stringify.ts → packages/react-common/src/services/stringify.ts View File


lib/services/utilities.ts → packages/react-common/src/services/utilities.ts View File


plop/templates/component/{{pascalCase name}}.mdx.hbs → plop/templates/react-common-docs/component/{{pascalCase name}}.mdx.hbs View File


plop/templates/component/{{pascalCase name}}.test.tsx.hbs → plop/templates/react-common/component/{{pascalCase name}}.test.tsx.hbs View File


plop/templates/component/{{pascalCase name}}.tsx.hbs → plop/templates/react-common/component/{{pascalCase name}}.tsx.hbs View File


+ 9
- 4
plopfile.js View File

@@ -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',
},
],
})


+ 1
- 1
rollup.config.js View File

@@ -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,


+ 1253
- 11314
yarn.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save