Browse Source

Initial commit

Add files from create-next-app.
master
TheoryOfNekomata 2 years ago
commit
009eea95d0
68 changed files with 186874 additions and 0 deletions
  1. +3
    -0
      .eslintrc.json
  2. +37
    -0
      .gitignore
  3. +1
    -0
      .npmrc
  4. +34
    -0
      README.md
  5. +10
    -0
      jest.config.js
  6. +6
    -0
      next-env.d.ts
  7. +4
    -0
      next.config.js
  8. +27
    -0
      package.json
  9. BIN
      public/favicon.ico
  10. BIN
      public/fonts/mononoki/mononoki-Bold.eot
  11. BIN
      public/fonts/mononoki/mononoki-Bold.ttf
  12. BIN
      public/fonts/mononoki/mononoki-Bold.woff
  13. BIN
      public/fonts/mononoki/mononoki-Bold.woff2
  14. BIN
      public/fonts/mononoki/mononoki-BoldItalic.eot
  15. BIN
      public/fonts/mononoki/mononoki-BoldItalic.ttf
  16. BIN
      public/fonts/mononoki/mononoki-BoldItalic.woff
  17. BIN
      public/fonts/mononoki/mononoki-BoldItalic.woff2
  18. BIN
      public/fonts/mononoki/mononoki-Italic.eot
  19. BIN
      public/fonts/mononoki/mononoki-Italic.ttf
  20. BIN
      public/fonts/mononoki/mononoki-Italic.woff
  21. BIN
      public/fonts/mononoki/mononoki-Italic.woff2
  22. BIN
      public/fonts/mononoki/mononoki-Regular.eot
  23. BIN
      public/fonts/mononoki/mononoki-Regular.ttf
  24. BIN
      public/fonts/mononoki/mononoki-Regular.woff
  25. BIN
      public/fonts/mononoki/mononoki-Regular.woff2
  26. +93
    -0
      public/global.css
  27. +175241
    -0
      public/tailwind-utilities.css
  28. +181
    -0
      public/theme.css
  29. +20
    -0
      public/theme/dark.css
  30. +20
    -0
      public/theme/light.css
  31. +4
    -0
      public/vercel.svg
  32. +30
    -0
      src/components/DefaultLayout/index.tsx
  33. +166
    -0
      src/modules/action/components/ActionButton/index.tsx
  34. +1
    -0
      src/modules/action/index.ts
  35. +303
    -0
      src/modules/base-button/index.ts
  36. +310
    -0
      src/modules/base-checkcontrol/index.ts
  37. +367
    -0
      src/modules/base-textcontrol/index.ts
  38. +124
    -0
      src/modules/css-utils/index.test.ts
  39. +143
    -0
      src/modules/css-utils/index.ts
  40. +18
    -0
      src/modules/freeform/components/MaskedTextInput/MaskedTextInput.test.tsx
  41. +117
    -0
      src/modules/freeform/components/MaskedTextInput/index.tsx
  42. +17
    -0
      src/modules/freeform/components/MultilineTextInput/MultilineTextInput.test.tsx
  43. +118
    -0
      src/modules/freeform/components/MultilineTextInput/index.tsx
  44. +17
    -0
      src/modules/freeform/components/TextInput/TextInput.test.tsx
  45. +126
    -0
      src/modules/freeform/components/TextInput/index.tsx
  46. +3
    -0
      src/modules/freeform/index.ts
  47. +66
    -0
      src/modules/info/components/Badge/index.tsx
  48. +1
    -0
      src/modules/info/index.ts
  49. +189
    -0
      src/modules/navigation/components/LinkButton/index.tsx
  50. +1
    -0
      src/modules/navigation/index.ts
  51. +277
    -0
      src/modules/option/components/Checkbox/index.tsx
  52. +17
    -0
      src/modules/option/components/DropdownSelect/DropdownSelect.test.tsx
  53. +218
    -0
      src/modules/option/components/DropdownSelect/index.tsx
  54. +250
    -0
      src/modules/option/components/RadioButton/index.tsx
  55. +3
    -0
      src/modules/option/index.ts
  56. +13
    -0
      src/pages/_app.tsx
  57. +48
    -0
      src/pages/_document.tsx
  58. +251
    -0
      src/pages/categories/action/index.tsx
  59. +45
    -0
      src/pages/categories/code/index.tsx
  60. +666
    -0
      src/pages/categories/freeform/index.tsx
  61. +480
    -0
      src/pages/categories/navigation/index.tsx
  62. +51
    -0
      src/pages/categories/number/index.tsx
  63. +953
    -0
      src/pages/categories/option/index.tsx
  64. +92
    -0
      src/pages/examples/registration-form.tsx
  65. +111
    -0
      src/pages/index.tsx
  66. +35
    -0
      tsconfig.json
  67. +3
    -0
      tsconfig.test.json
  68. +5563
    -0
      yarn.lock

+ 3
- 0
.eslintrc.json View File

@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

+ 37
- 0
.gitignore View File

@@ -0,0 +1,37 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel

.idea/

+ 1
- 0
.npmrc View File

@@ -0,0 +1 @@
@tesseract-design:registry=http://localhost:4873/

+ 34
- 0
README.md View File

@@ -0,0 +1,34 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.

The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

+ 10
- 0
jest.config.js View File

@@ -0,0 +1,10 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
globals: {
'ts-jest': {
tsconfig: 'tsconfig.test.json',
},
},
};

+ 6
- 0
next-env.d.ts View File

@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

+ 4
- 0
next.config.js View File

@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
module.exports = {
reactStrictMode: true,
}

+ 27
- 0
package.json View File

@@ -0,0 +1,27 @@
{
"name": "@tesseract-design/web-kitchen-sink",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"goober": "^2.0.41",
"next": "11.1.2",
"react": "17.0.2",
"react-dom": "17.0.2",
"tailwindcss": "^2.2.16"
},
"devDependencies": {
"@types/jest": "^27.0.3",
"@types/react": "17.0.27",
"eslint": "^7.32.0",
"eslint-config-next": "11.1.2",
"jest": "^27.4.3",
"ts-jest": "^27.0.7",
"typescript": "4.4.3"
}
}

BIN
public/favicon.ico View File

Before After

BIN
public/fonts/mononoki/mononoki-Bold.eot View File


BIN
public/fonts/mononoki/mononoki-Bold.ttf View File


BIN
public/fonts/mononoki/mononoki-Bold.woff View File


BIN
public/fonts/mononoki/mononoki-Bold.woff2 View File


BIN
public/fonts/mononoki/mononoki-BoldItalic.eot View File


BIN
public/fonts/mononoki/mononoki-BoldItalic.ttf View File


BIN
public/fonts/mononoki/mononoki-BoldItalic.woff View File


BIN
public/fonts/mononoki/mononoki-BoldItalic.woff2 View File


BIN
public/fonts/mononoki/mononoki-Italic.eot View File


BIN
public/fonts/mononoki/mononoki-Italic.ttf View File


BIN
public/fonts/mononoki/mononoki-Italic.woff View File


BIN
public/fonts/mononoki/mononoki-Italic.woff2 View File


BIN
public/fonts/mononoki/mononoki-Regular.eot View File


BIN
public/fonts/mononoki/mononoki-Regular.ttf View File


BIN
public/fonts/mononoki/mononoki-Regular.woff View File


BIN
public/fonts/mononoki/mononoki-Regular.woff2 View File


+ 93
- 0
public/global.css View File

@@ -0,0 +1,93 @@
body {
margin: 0;
}

title {
font-size: 4em;
text-transform: lowercase;
}

meta[name="display-title"] {
font-size: 4em;
text-transform: lowercase;
}

meta[name="display-title"]::before {
display: block;
content: attr(content);
}

h1 {
font-size: 3em;
text-transform: lowercase;
}

h2 {
font-size: 2em;
text-transform: lowercase;
}

h3 {
font-size: 1.75em;
text-transform: lowercase;
}

h4 {
font-size: 1.5em;
text-transform: lowercase;
}

h5 {
font-size: 1.25em;
text-transform: lowercase;
}

h6 {
font-size: 1.125em;
text-transform: lowercase;
}

p {
margin: 2em 0;
}

small {
font-size: 0.75em;
}

a:focus {
outline: 0;
}

pre {
overflow: auto;
margin: 0 -1rem;
padding: 0 1rem;
line-height: 1.2;
box-sizing: border-box;
}

@media only print {
pre, pre * {
color: inherit !important;
}

code, code * {
color: inherit !important;
}

img {
filter: grayscale(100%);
}

:root {
--color-accent: black !important;
--color-active: black !important;
}
}

/* kitchen-sink styles */

.container {
max-width: 720px;
}

+ 175241
- 0
public/tailwind-utilities.css
File diff suppressed because it is too large
View File


+ 181
- 0
public/theme.css View File

@@ -0,0 +1,181 @@
@font-face {
font-family: 'mononoki';
font-weight: 400;
font-style: normal;
font-display: swap;
src:
local('mononoki'),
url(fonts/mononoki/mononoki-Regular.woff2) format('woff2');
}

@font-face {
font-family: 'mononoki';
font-weight: 700;
font-style: normal;
font-display: swap;
src:
local('mononoki Bold'),
local('mononoki'),
url(fonts/mononoki/mononoki-Bold.woff2) format('woff2');
}

@font-face {
font-family: 'mononoki';
font-weight: 400;
font-style: italic;
font-display: swap;
src:
local('mononoki Italic'),
local('mononoki'),
url(fonts/mononoki/mononoki-Italic.woff2) format('woff2');
}

@font-face {
font-family: 'mononoki';
font-weight: 700;
font-style: italic;
font-display: swap;
src:
local('mononoki Bold Italic'),
local('mononoki BoldItalic'),
local('mononoki'),
url(fonts/mononoki/mononoki-BoldItalic.woff2) format('woff2');
}

:root {
--font-family-base: 'Encode Sans', system-ui;
--font-stretch-base: semi-expanded;
--font-weight-base: 400;
--line-height-base: 1.5em;
--font-family-headings: Glory, 'Encode Sans', system-ui;
--font-stretch-headings: condensed;
--font-weight-headings: 100;
--line-height-headings: 1.125em;
--font-family-monospace: 'mononoki';
--font-size-root: 16px;
--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);
--color-accent: var(--color-primary, blue);
--color-active: var(--color-tertiary, red);
--color-hover: var(--color-secondary, blue);
}

@media (prefers-color-scheme: dark) {
:root {
--color-bg: var(--color-negative, black);
--color-fg: var(--color-positive, white);
}
}

:root {
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, 1.5);
transition-property: color, background-color;
transition-timing-function: ease;
transition-duration: 350ms;
}

@media only screen {
:root {
background-color: var(--color-bg);
color: var(--color-fg);
}
}

title {
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);
}

meta[name="display-title"] {
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);
}

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: calc(var(--font-weight-headings, 400) / 100 * 150);
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: calc(var(--font-weight-headings, 400) / 100 * 250);
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);
}

code {
font-family: var(--font-family-monospace), monospace;
}

pre {
font-family: var(--font-family-monospace), monospace;
}

a {
color: inherit;
text-decoration: none;
}

@media only screen {
a {
color: var(--color-accent);
text-decoration: underline;
}

a:focus {
color: var(--color-active);
}
}

::selection {
background-color: var(--color-active);
color: var(--color-fg);
}

:root {
caret-color: var(--color-active);
}

+ 20
- 0
public/theme/dark.css View File

@@ -0,0 +1,20 @@
:root {
--color-shade: #000;
--color-negative: #222;
--color-positive: #eee;
--color-primary: #C78AB3;
--color-secondary: #f90;
--color-tertiary: #d75f4b;
--color-code-number: #74f95e;
--color-code-keyword: #ff4389;
--color-code-type: #5097D2;
--color-code-instance-attribute: #76a7d2;
--color-code-function: #67c252;
--color-code-parameter: #915ec2;
--color-code-property: #ffa1c9;
--color-code-string: #eed371;
--color-code-variable: #8bc275;
--color-code-regexp: #74A72B;
--color-code-url: #0099CC;
--color-code-global: #C28050;
}

+ 20
- 0
public/theme/light.css View File

@@ -0,0 +1,20 @@
:root {
--color-shade: #fff;
--color-negative: #f8f8f8;
--color-positive: #333;
--color-primary: #ba6a9c;
--color-secondary: #f90;
--color-tertiary: #d75f4b;
--color-code-number: #72b507;
--color-code-keyword: #ee5189;
--color-code-type: #427fb1;
--color-code-instance-attribute: #76a7d2;
--color-code-function: #5a984a;
--color-code-parameter: #915ec2;
--color-code-property: #b76e8d;
--color-code-string: #b59e36;
--color-code-variable: #61864e;
--color-code-regexp: #4f7e03;
--color-code-url: #0099CC;
--color-code-global: #C28050;
}

+ 4
- 0
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>

+ 30
- 0
src/components/DefaultLayout/index.tsx View File

@@ -0,0 +1,30 @@
import Head from 'next/head';
import { FC } from 'react';

type DefaultLayoutProps = {
title?: string,
appName?: string,
}

export const DefaultLayout: FC<DefaultLayoutProps> = ({
title,
appName = 'Tesseract Web (React)',
children,
}) => {
return (
<>
<Head>
<title>
{title ? `${title} | ${appName}` : appName}
</title>
{
title
&& (
<meta name="display-title" className="block" content={title} />
)
}
</Head>
{children}
</>
)
}

+ 166
- 0
src/modules/action/components/ActionButton/index.tsx View File

@@ -0,0 +1,166 @@
import * as React from 'react';
import * as ButtonBase from '@tesseract-design/web-base-button';

export enum ActionButtonType {
SUBMIT = 'submit',
RESET = 'reset',
BUTTON = 'button',
}

export type ActionButtonProps = Omit<React.HTMLProps<HTMLButtonElement>, 'size' | 'type' | 'style'> & {
/**
* Size of the component.
*/
size?: ButtonBase.ButtonSize,
/**
* Variant of the component.
*/
variant?: ButtonBase.ButtonVariant,
/**
* Should the component display a border?
*/
border?: boolean,
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean,
/**
* Type of the component.
*/
type?: ActionButtonType,
/**
* Style of the component.
*/
style?: ButtonBase.ButtonStyle,
/**
* Does the component need to conserve space?
*/
compact?: boolean,
/**
* Complementary content of the component.
*/
subtext?: React.ReactNode,
/**
* Short complementary content displayed at the edge of the component.
*/
badge?: React.ReactNode,
/**
* Is this component part of a menu?
*/
menuItem?: boolean,
}

/**
* Component for performing an action upon activation (e.g. when clicked).
*
* This component functions as a regular button.
*/
export const ActionButton = React.forwardRef<HTMLButtonElement, ActionButtonProps>(
(
{
size = ButtonBase.ButtonSize.MEDIUM,
variant = ButtonBase.ButtonVariant.OUTLINE,
border = false,
children,
type = ActionButtonType.BUTTON,
block = false,
style = ButtonBase.ButtonStyle.DEFAULT,
disabled = false,
compact = false,
subtext,
badge,
menuItem = false,
className: _className,
as: _as,
...etcProps
}: ActionButtonProps,
ref,
) => {
const styleProps = React.useMemo<ButtonBase.ButtonBaseArgs>(() => ({
size,
block,
variant,
border,
style,
compact,
menuItem,
disabled,
}), [size, block, variant, border, style, compact, menuItem]);

return (
<button
{...etcProps}
disabled={disabled}
className={ButtonBase.Button(styleProps)}
ref={ref}
type={type ?? ActionButtonType.BUTTON}
>
<span
className={ButtonBase.Border(styleProps)}
/>
<span
className={ButtonBase.Label(styleProps)}
>
<span
className={ButtonBase.MainText()}
>
<span
className={ButtonBase.OverflowText()}
>
{children}
</span>
</span>
{
subtext
&& (
<>
{' '}
<span className={ButtonBase.Subtext()}>
<span
className={ButtonBase.OverflowText()}
>
{subtext}
</span>
</span>
</>
)
}
</span>
{
badge
&& (
<>
{' '}
<span
className={ButtonBase.BadgeContainer(styleProps)}
>
{badge}
</span>
</>
)
}
{
menuItem
&& (
<>
{' '}
<span
className={ButtonBase.IndicatorWrapper(styleProps)}
>
<svg
className={ButtonBase.Indicator()}
viewBox="0 0 24 24"
role="presentation"
>
<polyline points="9 18 15 12 9 6"/>
</svg>
</span>
</>
)
}
</button>
);
},
);

ActionButton.displayName = 'ActionButton';

+ 1
- 0
src/modules/action/index.ts View File

@@ -0,0 +1 @@
export * from './components/ActionButton';

+ 303
- 0
src/modules/base-button/index.ts View File

@@ -0,0 +1,303 @@
import { css } from '@tesseract-design/css-utils';

export enum ButtonSize {
SMALL = 'small',
MEDIUM = 'medium',
LARGE = 'large',
}

export enum ButtonVariant {
OUTLINE = 'outline',
FILLED = 'filled',
}

export enum ButtonStyle {
DEFAULT = 'default',
}

export type ButtonBaseArgs = {
size: ButtonSize,
block: boolean,
variant: ButtonVariant,
border: boolean,
disabled: boolean,
style: ButtonStyle,
compact: boolean,
menuItem: boolean,
}

const MIN_HEIGHTS: Record<ButtonSize, string> = {
[ButtonSize.SMALL]: '2.5rem',
[ButtonSize.MEDIUM]: '3rem',
[ButtonSize.LARGE]: '4rem',
};

export const Button = ({
size,
block,
variant,
disabled,
compact,
}: ButtonBaseArgs): string => css.cx(
css`
box-sizing: border-box;
vertical-align: middle;
appearance: none;
font: inherit;
font-family: var(--font-family-base, sans-serif);
text-transform: uppercase;
font-weight: bolder;
border-radius: 0.25rem;
justify-content: center;
align-items: center;
position: relative;
border: 0;
user-select: none;
text-decoration: none;
white-space: nowrap;
line-height: 1;

& > :first-child::before {
box-shadow: 0 0 0 0 var(--color-accent, blue);
transition-property: box-shadow;
transition-duration: 150ms;
transition-timing-function: linear;
}

&:disabled {
opacity: 0.5;
cursor: not-allowed;
}

&::-moz-focus-inner {
outline: 0;
border: 0;
}

&:focus > :first-child::before {
box-shadow: 0 0 0 0.375rem var(--color-accent, blue);
}

&:disabled > :first-child::before {
box-shadow: 0 0 0 0 var(--color-accent, blue) !important;
}
`,

css.dynamic({
'min-height': MIN_HEIGHTS[size],
}),

css.if (disabled) (
css`
--color-accent: var(--color-primary, blue);
opacity: 0.5;
cursor: not-allowed;
`
).else (
css`
cursor: pointer;
&:hover {
--color-accent: var(--color-hover, blue);
outline: 0;
}
&:focus {
--color-accent: var(--color-hover, blue);
outline: 0;
}
&:active {
--color-accent: var(--color-active, red);
outline: 0;
}
&:hover > :first-child::before {
box-shadow: 0 0 0 0.375rem var(--color-accent, blue);
}
`
),

css.if (block) (
css`
width: 100%;
display: flex;
`
).else (
css`
display: inline-flex;
`
),

css.if (compact) (
css`
font-stretch: condensed;
padding: 0 0.5rem;
`
).else(
css`
padding: 0 1rem;
`
),

css.if (variant === ButtonVariant.FILLED) (
css`
background-color: var(--color-accent, blue);
color: var(--color-bg, white) !important;
`
),

css.if (variant === ButtonVariant.OUTLINE) (
css`
background-color: var(--color-bg, white);
color: var(--color-accent, blue);
`
),
);

export const Border = ({
border
}: ButtonBaseArgs): string => css.cx(
css.if (border) (
css`
border-color: var(--color-accent, blue);
box-sizing: border-box;
display: inline-block;
border-width: 0.125rem;
border-style: solid;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: inherit;
pointer-events: none;
&::before {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
content: '';
border-radius: 0.125rem;
opacity: 0.5;
pointer-events: none;
}
`
),
);


export const Label = ({
compact,
menuItem,
}: ButtonBaseArgs): string => css.cx(
css`
display: block;
flex-grow: 1;
flex-basis: 0;
min-width: 0;
`,

css.if (compact || menuItem) (
css`
text-align: left;
`
).else (
css`
text-align: center;
`
),

css.if (compact) (
css`
& ~ :last-child {
margin-right: -0.5rem;
}
`
).else (
css`
& ~ :last-child {
margin-right: -1rem;
}
`
),
);

export const BadgeContainer = ({
size,
}: ButtonBaseArgs): string => css.cx(
css`
width: 2rem;
text-align: center;
flex-shrink: 0;
& + * {
margin-left: -0.5rem;
}
`,
css.nest('&:last-child')(
css.dynamic({
width: MIN_HEIGHTS[size],
})
),
);

export const OverflowText = (): string => css.cx(
css`
width: 100%;
display: block;
overflow: hidden;
text-overflow: ellipsis;
height: 1.1em;
line-height: 1;
`,
);

export const IndicatorWrapper = ({
size
}: ButtonBaseArgs): string => css.cx(
css`
flex-shrink: 0;
box-sizing: border-box;
display: grid;
place-content: center;
padding: 0 1rem;
z-index: 1;
pointer-events: none;
line-height: 1;
user-select: none;
`,
css.dynamic({
width: `calc(${MIN_HEIGHTS[size]} * 0.75)`,
height: MIN_HEIGHTS[size],
}),
);

export const Indicator = () => css.cx(
css`
width: 1.5em;
height: 1.5em;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
`,
);

export const MainText = () => css.cx(
css`
width: 100%;
`,
);

export const Subtext = () => css.cx(
css`
display: block;
height: 1.1em;
line-height: 1.1;
width: 100%;
font-size: 0.875em;
text-transform: none;
font-weight: var(--font-weight-base, normal);
`,
);

+ 310
- 0
src/modules/base-checkcontrol/index.ts View File

@@ -0,0 +1,310 @@
import { css } from '@tesseract-design/css-utils'

export enum CheckControlAppearance {
DEFAULT = 'default',
BUTTON = 'button',
SWITCH = 'switch',
}

export type CheckControlBaseArgs = {
compact: boolean,
appearance: CheckControlAppearance,
block: boolean,
type: string,
}

export const CheckStateContainer = ({
appearance,
type,
}: CheckControlBaseArgs): string => css.cx(
css`
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
&:disabled + * {
cursor: not-allowed;
}
&:first-child + * > :first-child + * + * {
align-items: flex-start;
text-align: left;
}
&:checked + * {
--color-accent: var(--color-active, Highlight);
outline: 0;
}
&:indeterminate[type="checkbox"] + * {
--color-accent: var(--color-active, Highlight);
outline: 0;
}
&:indeterminate[type="checkbox"] + * > :first-child + * > * > :first-child {
display: none;
}
&:checked + * > :first-child + * > * > :first-child + * {
display: none;
}
&:focus + * {
--color-accent: var(--color-hover, red);
outline: 0;
}
&:focus[type="checkbox"] + * {
--color-accent: var(--color-hover, red);
outline: 0;
}
&:focus + * > :first-child::before {
box-shadow: 0 0 0 0.375rem var(--color-accent, blue);
}
`,
css.nest('&:checked + * > :first-child + * > *') (
css.if (
appearance === CheckControlAppearance.DEFAULT
|| appearance === CheckControlAppearance.BUTTON
) (
css.if (type === 'checkbox') (
css`
width: 1.5em;
height: 1.5em;
`
),
css.if (type === 'radio') (
css`
width: 1em;
height: 1em;
`
),
),
css.if (appearance === CheckControlAppearance.SWITCH) (
css`
width: 1em;
height: 1em;
margin-right: 0;
margin-left: 1em;
`
),
),
css.nest('&:first-child + *') (
css`
cursor: pointer;
`,
css.if (appearance === CheckControlAppearance.BUTTON) (
css`
display: flex;
`,
),
css.if (appearance === CheckControlAppearance.SWITCH) (
css`
width: 1em;
height: 1em;
`,
),
),
css.nest('&:indeterminate[type="checkbox"] + * > :first-child + * > *') (
css.if (
appearance === CheckControlAppearance.BUTTON
|| appearance === CheckControlAppearance.DEFAULT
) (
css`
width: 1.5em;
height: 1.5em;
`
),
css.if (appearance === CheckControlAppearance.SWITCH) (
css`
width: 1em;
height: 1em;
margin-right: 0.5em;
margin-left: 0.5em;
`
)
),
css.nest('&:indeterminate[type="checkbox"] + * > :first-child + * > * > :first-child + *') (
css.if (
appearance === CheckControlAppearance.BUTTON
|| appearance === CheckControlAppearance.DEFAULT
) (
css`
display: block;
`
),
),
css.nest('&:checked + * > :first-child + * > * > :first-child') (
css.if (
appearance === CheckControlAppearance.BUTTON
|| appearance === CheckControlAppearance.DEFAULT
) (
css`
display: block;
`
),
),
);

export const ClickArea = (): string => css.cx(
css`
display: contents;
`
);

export const CheckIndicatorArea = ({
compact,
appearance,
type,
}: CheckControlBaseArgs): string => css.cx(
css`
display: inline-grid;
vertical-align: middle;
place-content: center;
position: relative;
background-color: var(--color-bg, white);
box-shadow: 0 0 0 0.125rem var(--color-bg, white);
color: var(--color-accent, blue);
overflow: hidden;
&::before {
content: '';
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
border-radius: inherit;
border-width: 0.125rem;
border-style: solid;
box-sizing: border-box;
}
`,
css.if (appearance === CheckControlAppearance.DEFAULT) (
css`
width: 1.5em;
height: 1.5em;
`,
css.if (type === 'checkbox') (
css`
border-radius: 0.25rem;
`
),
css.if (type === 'radio') (
css`
border-radius: 50%;
`
),
),
css.if (appearance === CheckControlAppearance.BUTTON) (
css`
width: 1.5em;
height: 1.5em;
`,
css.if (!compact) (
css`
margin-left: -0.25rem;
`
),
css.if (type === 'checkbox') (
css`
border-radius: 0.25rem;
`
),
css.if (type === 'radio') (
css`
border-radius: 50%;
`
),
),
css.if (appearance === CheckControlAppearance.SWITCH) (
css`
width: 2.5em;
height: 1.5em;
border-radius: 0.75em;
`
),
css.nest('& + *') (
css.dynamic({
'margin-left': compact ? '0.375rem' : '0.75rem',
})
)
);

export const CheckIndicatorWrapper = ({
appearance,
}: CheckControlBaseArgs): string => css.cx(
css`
flex-shrink: 0;
display: grid;
position: relative;
background-color: var(--color-accent, blue);
overflow: hidden;
border-radius: inherit;
`,
css.if(
appearance === CheckControlAppearance.DEFAULT
|| appearance === CheckControlAppearance.BUTTON
) (
css`
width: 0;
height: 0;
`
),
css.if(appearance === CheckControlAppearance.SWITCH) (
css`
width: 1em;
height: 1em;
margin-right: 1em;
transition-property: margin-left, margin-right;
transition-duration: 150ms;
transition-timing-function: ease-out;
`
),
);

export const CheckIndicator = ({
appearance,
}: CheckControlBaseArgs) => css.cx(
css`
fill: none;
stroke: var(--color-bg, white);
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
width: 1.5em;
height: 1.5em;
`,
css.if(appearance === CheckControlAppearance.SWITCH) (
css`
display: none;
`
)
);

export const ClickAreaWrapper = ({
block,
appearance,
}: CheckControlBaseArgs) => css.cx(
css`
vertical-align: middle;
`,
css.dynamic({
display: block ? 'block' : 'inline-block',
}),
css.if (appearance === CheckControlAppearance.DEFAULT) (
css`
padding-left: 2.25rem;
text-indent: -2.25rem;
`
),
css.if (appearance === CheckControlAppearance.SWITCH) (
css`
padding-left: 3.25rem;
text-indent: -3.25rem;
`
),
);

export const Subtext = () => css.cx(
css`
font-size: 0.875em;
`
);

+ 367
- 0
src/modules/base-textcontrol/index.ts View File

@@ -0,0 +1,367 @@
import { css } from '@tesseract-design/css-utils';

export enum TextControlSize {
SMALL = 'small',
MEDIUM = 'medium',
LARGE = 'large',
}

export enum TextControlStyle {
DEFAULT = 'default',
ALTERNATE = 'alternate',
}

export const MIN_HEIGHTS: Record<TextControlSize, string> = {
[TextControlSize.SMALL]: '2.5rem',
[TextControlSize.MEDIUM]: '3rem',
[TextControlSize.LARGE]: '4rem',
};

const LABEL_VERTICAL_PADDING_SIZES: Record<TextControlSize, string> = {
[TextControlSize.SMALL]: '0.125rem',
[TextControlSize.MEDIUM]: '0.25rem',
[TextControlSize.LARGE]: '0.375rem',
};

const INPUT_FONT_SIZES: Record<TextControlSize, string> = {
[TextControlSize.SMALL]: '0.75em',
[TextControlSize.MEDIUM]: '0.85em',
[TextControlSize.LARGE]: '1em',
};

const SECONDARY_TEXT_SIZES: Record<TextControlSize, string> = {
[TextControlSize.SMALL]: '0.6em',
[TextControlSize.MEDIUM]: '0.725em',
[TextControlSize.LARGE]: '0.85em',
};

const MULTILINE_VERTICAL_PADDING_FACTORS: Record<TextControlSize, string> = {
[TextControlSize.SMALL]: '1.25',
[TextControlSize.MEDIUM]: '1.2',
[TextControlSize.LARGE]: '1.45',
};

const ALTERNATE_VERTICAL_PADDING_FACTORS: Record<TextControlSize, string> = {
[TextControlSize.SMALL]: '1.75',
[TextControlSize.MEDIUM]: '1.35',
[TextControlSize.LARGE]: '1.25',
};

export type TextControlBaseArgs = {
block: boolean,
style: TextControlStyle,
border: boolean,
indicator: boolean,
size: TextControlSize,
resizable: boolean,
predefinedValues: boolean,
}

export const ComponentBase = ({
block,
}: TextControlBaseArgs): string => css.cx(
css`
vertical-align: middle;
position: relative;
border-radius: 0.25rem;
font-family: var(--font-family-base, sans-serif);
max-width: 100%;
&:focus-within {
--color-accent: var(--color-hover, red);
}
& > span {
border-color: var(--color-accent, blue);
box-sizing: border-box;
display: inline-block;
border-width: 0.125rem;
border-style: solid;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: inherit;
z-index: 2;
pointer-events: none;
transition-property: border-color;
}
& > span::before {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
content: '';
border-radius: 0.125rem;
opacity: 0.5;
pointer-events: none;
box-shadow: 0 0 0 0 var(--color-accent, blue);
transition-property: box-shadow;
transition-duration: 150ms;
transition-timing-function: linear;
}
&:focus-within > span::before {
box-shadow: 0 0 0 0.375rem var(--color-accent, blue);
}
`,
css.dynamic({
display: block ? 'block' : 'inline-block',
}),
);

export const LabelWrapper = ({
style,
border,
indicator,
size,
}: TextControlBaseArgs): string => css.cx(
css`
color: var(--color-accent, blue);
box-sizing: border-box;
position: absolute;
top: 0;
left: 0;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: bolder;
z-index: 1;
pointer-events: none;
transition-property: color;
line-height: 0.65;
user-select: none;
`,
css.dynamic({
'padding-bottom': LABEL_VERTICAL_PADDING_SIZES[size],
'font-size': SECONDARY_TEXT_SIZES[size],
}),
css.if (border) (
css`
background-color: var(--color-bg, white);
`
),
css.if (style === TextControlStyle.ALTERNATE) (
css.dynamic({
'padding-top': `calc(${LABEL_VERTICAL_PADDING_SIZES[size]} * 0.5)`,
}),
css.if (border) (
css`
padding-left: 0.5rem;
`,
css.dynamic({
'padding-right': indicator ? MIN_HEIGHTS[size] : '0.5rem',
}),
),
css.if (!border && indicator) (
css.dynamic({
'padding-right': MIN_HEIGHTS[size],
}),
),
),
css.if (style === TextControlStyle.DEFAULT) (
css`
padding-left: 0.5rem;
`,
css.dynamic({
'padding-top': LABEL_VERTICAL_PADDING_SIZES[size],
'padding-right': !indicator ? '0.5rem' : MIN_HEIGHTS[size],
}),
),
)

export const Input = ({
style,
size,
indicator,
border,
resizable,
predefinedValues,
}: TextControlBaseArgs): string => css.cx(
css`
appearance: none;
display: block;
box-sizing: border-box;
position: relative;
border: 0;
border-radius: inherit;
margin: 0;
font-family: inherit;
min-width: 8rem;
max-width: 100%;
width: 100%;
z-index: 1;
transition-property: background-color, color;
&:focus {
outline: 0;
color: var(--color-fg, black);
}
&:disabled {
cursor: not-allowed;
opacity: 0.5;
}
&:disabled ~ * {
opacity: 0.5;
}
`,
css.media('only screen') (
css`
background-color: var(--color-bg, white);
color: var(--color-fg, black);
`
),
css.dynamic({
'min-height': MIN_HEIGHTS[size],
'font-size': INPUT_FONT_SIZES[size],
}),
css.if (resizable) (
css`
resize: vertical;
`
),
css.if (predefinedValues) (
css`
cursor: pointer;
`
),
css.if (border) (
css`
background-color: var(--color-bg, white);
`
),
css.if (style === TextControlStyle.ALTERNATE) (
css`
padding-bottom: 0;
`,
css.dynamic({
'padding-top': resizable
? `calc(${SECONDARY_TEXT_SIZES[size]} * 2.5)`
: `calc(${SECONDARY_TEXT_SIZES[size]} * 2)`,
'line-height': `calc(${MULTILINE_VERTICAL_PADDING_FACTORS[size]} * 1.1)`,
}),
css.if (border) (
css`
padding-left: 0.5rem;
`,
css.dynamic({
'padding-right': indicator ? MIN_HEIGHTS[size] : '0.5rem',
}),
),
css.if (!border && indicator) (
css.dynamic({
'padding-right': MIN_HEIGHTS[size],
}),
)
),
css.if (style === TextControlStyle.DEFAULT) (
css`
padding-left: 1rem;
`,
css.dynamic({
'padding-right': !indicator ? '1rem' : MIN_HEIGHTS[size],
'line-height': `calc(${MULTILINE_VERTICAL_PADDING_FACTORS[size]} * 1.1)`,
}),
css.if (resizable) (
css.dynamic({
'padding-top': `calc(${SECONDARY_TEXT_SIZES[size]} * ${MULTILINE_VERTICAL_PADDING_FACTORS[size]})`,
'padding-bottom': `calc(${SECONDARY_TEXT_SIZES[size]} * ${MULTILINE_VERTICAL_PADDING_FACTORS[size]})`,
})
),
css.if (!resizable) (
css.dynamic({
'padding-bottom': `calc(${SECONDARY_TEXT_SIZES[size]} * ${MULTILINE_VERTICAL_PADDING_FACTORS[size]} * 0.5)`,
})
)
),
)

export const HintWrapper = ({
style,
size,
border,
}: TextControlBaseArgs): string => css.cx(
css`
box-sizing: border-box;
position: absolute;
left: 0;
font-size: 0.85em;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
z-index: 1;
pointer-events: none;
user-select: none;
line-height: 0;
`,
css.if (border) (
css`
background-color: var(--color-bg, white);
`
),
css.if (style === TextControlStyle.ALTERNATE) (
css`
line-height: 1.25;
`,
css.dynamic({
top: `calc(${SECONDARY_TEXT_SIZES[size]} * ${ALTERNATE_VERTICAL_PADDING_FACTORS[size]})`,
'font-size': SECONDARY_TEXT_SIZES[size],
}),
css.if (border) (
css`
padding-left: 0.5rem;
&:last-child {
padding-right: 0.5rem;
}
`,
css.dynamic({
'padding-right': MIN_HEIGHTS[size],
})
)
),
css.if (style === TextControlStyle.DEFAULT) (
css`
bottom: 0;
padding-left: 1rem;
line-height: 1.25;
&:last-child {
padding-right: 1rem;
}
`,
css.dynamic({
'padding-bottom': `calc(${LABEL_VERTICAL_PADDING_SIZES[size]} * 0.9)`,
'padding-right': MIN_HEIGHTS[size],
'font-size': SECONDARY_TEXT_SIZES[size],
})
)
)

export const Hint = (): string => css.cx(
css`
opacity: 0.5;
`
);

export const IndicatorWrapper = ({
size
}: TextControlBaseArgs): string => css.cx(
css`
color: var(--color-accent, blue);
box-sizing: border-box;
position: absolute;
bottom: 0;
right: 0;
display: grid;
place-content: center;
padding: 0 1rem;
z-index: 2;
pointer-events: none;
transition-property: color;
line-height: 1;
user-select: none;
`,
css.dynamic({
width: MIN_HEIGHTS[size],
height: MIN_HEIGHTS[size],
}),
);

+ 124
- 0
src/modules/css-utils/index.test.ts View File

@@ -0,0 +1,124 @@
import { css, CssIfStringImpl, CssStringImpl } from '.';

describe('css-utils', () => {
describe('css', () => {
it('should return CssString', () => {
const c = css`
background-color: white;
color: black;
`

expect(c).toBeInstanceOf(CssStringImpl);
expect(c.toString()).toBe('background-color:white; color:black;');
})
})

describe('css.if', () => {
it('should return CssString when the condition is true', () => {
const c = css.if(true)(
css`
background-color: white;
`
)

expect(c).toBeInstanceOf(CssIfStringImpl)
expect(c.toString()).toBe('background-color:white;')
})

it('should return empty string when the condition is false', () => {
const c = css.if(false)(
css`
background-color: white;
`
)

expect(c.toString()).toBe('')
})

it('should return CssString with .else when the condition is true', () => {
const c = css.if(false)(
css`
background-color: white;
`
).else(
css`
background-color: black;
`
)

expect(c).toBeInstanceOf(CssStringImpl)
expect(c.toString()).toBe('background-color:black;')
})

it('should return CssString with .else.if when the condition is true', () => {
const c = css.if(false)(
css`
background-color: white;
`
).else.if(true)(
css`
background-color: black;
`
)

expect(c).toBeInstanceOf(CssIfStringImpl)
expect(c.toString()).toBe('background-color:black;')
})

it('should return CssString with .else.if.else when the condition is false', () => {
const c = css.if('a'.toUpperCase() === 'C')(
css`
background-color: white;
`
).else.if('b'.toUpperCase() === 'C')(
css`
background-color: black;
`
).else(
css`
background-color: gray;
`
)

expect(c).toBeInstanceOf(CssStringImpl)
expect(c.toString()).toBe('background-color:gray;')
})
})

describe('css.nest', () => {
it('should add selector', () => {
const c = css.nest('> div')(
css`
background-color: white;
`
)

expect(c.toString()).toBe('> div{background-color:white;}')
})
})

describe('css.dynamic', () => {
it('should evaluate dynamic object', () => {
const randomNumber = Math.floor(Math.random() * 1000);

const c = css.dynamic({
'line-height': randomNumber,
})

expect(c.toString()).toBe(`line-height:${randomNumber};`)
})
})

describe('css.media', () => {
it('should accept raw queries', () => {
const c = css.media('only screen and (min-width: 720px)')(
css`
color: black;
background-color: white;
`
)

expect(c.toString()).toBe('@media only screen and (min-width: 720px){color:black; background-color:white;}')
})
})
})

+ 143
- 0
src/modules/css-utils/index.ts View File

@@ -0,0 +1,143 @@
import { css as gooberCss } from 'goober';
import { PropertiesHyphenFallback } from 'csstype';

interface CssString {
// TODO stricter type checking
toString(): string
}

export class CssStringImpl implements CssString {
private css: string

constructor(s: TemplateStringsArray) {
this.css = s.raw[0]
.trim()
.replace(/[ ][ ]+/g, ' ')
.replace(/:[ ]/g, ':')
.replace(/\n/g, '')
.replace(/;[ ]/g, ';');
}

toString() {
return this.css
}
}

interface CssIf {
(b: boolean): (...a: CssString[]) => CssIfString
}

interface CssElse {
(...c: CssString[]): CssString
if: CssIf
}

interface CssIfString extends CssString {
else: CssElse
}

const cssIf: CssIf = (b: boolean) => (...a: CssString[]) => new CssIfStringImpl(b, ...a);

export class CssIfStringImpl implements CssIfString {
readonly else: CssElse
private readonly cssStrings: CssString[]

constructor(private readonly condition: boolean, ...cssStrings: CssString[]) {
this.cssStrings = cssStrings

const elseFn = (...c: CssString[]) => {
if (this.condition) {
return {
toString: () => {
return this.cssStrings.map((c2) => c2.toString()).join('');
}
}
}
return {
toString: () => {
return c.map((cc) => cc.toString()).join('')
}
};
}
elseFn.if = cssIf

this.else = elseFn
}

toString() {
if (this.condition) {
return this.cssStrings.map((c2) => c2.toString()).join('');
}
return '';
}
}

interface CssNest {
(selector: string): (...a: CssString[]) => CssString
}

const cssNest: CssNest = (selector) => (...a) => {
return {
toString: () => `${selector}{${a.map(aa => aa.toString()).join('')}}`
}
}

interface CssDynamic {
(a: PropertiesHyphenFallback): CssString
}

const cssDynamic: CssDynamic = (a: PropertiesHyphenFallback) => {
return {
toString(): string {
return Object
.entries(a)
.map(([key, value]) => `${key}:${value.toString()};`)
.join('')
}
};
};

interface CssMedia {
(raw: string): any
}

const cssMedia: CssMedia = (arg1: string) => {
return (...body: CssString[]) => {
return {
toString(): string {
return `@media ${arg1}{${body.map(b => b.toString()).join('')}}`
}
}
}
}

const cssCompile = (...strings: CssString[]) => {
return strings
.filter((s) => ['string', 'object'].includes(typeof s))
.map((s) => {
if (typeof s === 'object') {
return gooberCss`${s.toString()}`
}

return s
})
.join(' ')
}

interface Css {
(s: TemplateStringsArray): CssString
if: CssIf
nest: CssNest
dynamic: CssDynamic
media: CssMedia
cx(...strings: CssString[]): string
}

const _css: Partial<Css> = (s: TemplateStringsArray) => new CssStringImpl(s);
_css.if = cssIf;
_css.nest = cssNest;
_css.dynamic = cssDynamic;
_css.media = cssMedia;
_css.cx = cssCompile;

export const css = _css as Css;

+ 18
- 0
src/modules/freeform/components/MaskedTextInput/MaskedTextInput.test.tsx View File

@@ -0,0 +1,18 @@
import * as React from 'react';
import {
render,
screen
} from '@testing-library/react';
import '@testing-library/jest-dom';
import {
MaskedTextInput
} from '.';

describe('MaskedTextInput', () => {
it('should render an input', () => {
render(<MaskedTextInput />);
const textbox: HTMLInputElement = screen.getByTestId('input');
expect(textbox).toBeInTheDocument();
expect(textbox.type).toBe('password');
});
});

+ 117
- 0
src/modules/freeform/components/MaskedTextInput/index.tsx View File

@@ -0,0 +1,117 @@
import * as React from 'react';
import * as TextControlBase from '@tesseract-design/web-base-textcontrol';

export type MaskedTextInputProps = Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'type' | 'style'> & {
/**
* Short textual description indicating the nature of the component's value.
*/
label?: React.ReactNode,
/**
* Short textual description as guidelines for valid input values.
*/
hint?: React.ReactNode,
/**
* Size of the component.
*/
size?: TextControlBase.TextControlSize,
/**
* Additional description, usually graphical, indicating the nature of the component's value.
*/
indicator?: React.ReactNode,
/**
* Should the component display a border?
*/
border?: boolean,
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean,
/**
* Style of the component.
*/
style?: TextControlBase.TextControlStyle,
/**
* Is the label hidden?
*/
hiddenLabel?: boolean,
}

/**
* Component for inputting textual values.
*
* This component supports multiline input and adjusts its layout accordingly.
*/
export const MaskedTextInput = React.forwardRef<HTMLInputElement, MaskedTextInputProps>(
(
{
label = '',
hint = '',
indicator = null,
size = TextControlBase.TextControlSize.MEDIUM,
border = false,
block = false,
style = TextControlBase.TextControlStyle.DEFAULT,
hiddenLabel = false,
className: _className,
placeholder: _placeholder,
as: _as,
...etcProps
}: MaskedTextInputProps,
ref,
) => {
const textInputBaseArgs = React.useMemo<TextControlBase.TextControlBaseArgs>(() => ({
block,
border,
size,
indicator: Boolean(indicator),
style,
resizable: false,
predefinedValues: false,
}), [block, border, size, indicator, style]);

return (
<div
className={TextControlBase.ComponentBase(textInputBaseArgs)}
>
<input
{...etcProps}
className={TextControlBase.Input(textInputBaseArgs)}
ref={ref}
aria-label={label}
type="password"
data-testid="input"
/>
{border && <span />}
{
label && !hiddenLabel && (
<div
className={TextControlBase.LabelWrapper(textInputBaseArgs)}
>
{label}
</div>
)
}
{hint && (
<div
className={TextControlBase.HintWrapper(textInputBaseArgs)}
>
<div
className={TextControlBase.Hint()}
>
{hint}
</div>
</div>
)}
{indicator && (
<div
className={TextControlBase.IndicatorWrapper(textInputBaseArgs)}
>
{indicator}
</div>
)}
</div>
);
},
);

MaskedTextInput.displayName = 'MaskedTextInput';

+ 17
- 0
src/modules/freeform/components/MultilineTextInput/MultilineTextInput.test.tsx View File

@@ -0,0 +1,17 @@
import * as React from 'react';
import {
render,
screen
} from '@testing-library/react';
import '@testing-library/jest-dom';
import {
MultilineTextInput
} from '.';

describe('MultilineTextInput', () => {
it('should render a textbox', () => {
render(<MultilineTextInput />);
const textbox = screen.getByRole('textbox');
expect(textbox).toBeInTheDocument();
});
});

+ 118
- 0
src/modules/freeform/components/MultilineTextInput/index.tsx View File

@@ -0,0 +1,118 @@
import * as React from 'react';
import * as TextControlBase from '@tesseract-design/web-base-textcontrol';

export type MultilineTextInputProps = Omit<React.HTMLProps<HTMLTextAreaElement>, 'size' | 'style'> & {
/**
* Short textual description indicating the nature of the component's value.
*/
label?: React.ReactNode,
/**
* Short textual description as guidelines for valid input values.
*/
hint?: React.ReactNode,
/**
* Size of the component.
*/
size?: TextControlBase.TextControlSize,
/**
* Additional description, usually graphical, indicating the nature of the component's value.
*/
indicator?: React.ReactNode,
/**
* Should the component display a border?
*/
border?: boolean,
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean,
/**
* Style of the component.
*/
style?: TextControlBase.TextControlStyle,
/**
* Is the label hidden?
*/
hiddenLabel?: boolean,
}

/**
* Component for inputting textual values.
*
* This component supports multiline input and adjusts its layout accordingly.
*/
export const MultilineTextInput = React.forwardRef<HTMLTextAreaElement, MultilineTextInputProps>(
(
{
label = '',
hint = '',
indicator = null,
size = TextControlBase.TextControlSize.MEDIUM,
border = false,
block = false,
style = TextControlBase.TextControlStyle.DEFAULT,
hiddenLabel = false,
className: _className,
placeholder: _placeholder,
as: _as,
...etcProps
}: MultilineTextInputProps,
ref,
) => {
const textInputBaseArgs = React.useMemo<TextControlBase.TextControlBaseArgs>(() => ({
block,
border,
size,
indicator: Boolean(indicator),
style,
resizable: true,
predefinedValues: false,
}), [block, border, size, indicator, style]);

return (
<div
className={TextControlBase.ComponentBase(textInputBaseArgs)}
>
<textarea
{...etcProps}
className={TextControlBase.Input(textInputBaseArgs)}
ref={ref}
aria-label={label}
style={{
height: TextControlBase.MIN_HEIGHTS[size],
}}
/>
{border && <span />}
{
label && !hiddenLabel && (
<div
className={TextControlBase.LabelWrapper(textInputBaseArgs)}
>
{label}
</div>
)
}
{hint && (
<div
className={TextControlBase.HintWrapper(textInputBaseArgs)}
>
<div
className={TextControlBase.Hint()}
>
{hint}
</div>
</div>
)}
{indicator && (
<div
className={TextControlBase.IndicatorWrapper(textInputBaseArgs)}
>
{indicator}
</div>
)}
</div>
);
}
);

MultilineTextInput.displayName = 'MultilineTextInput';

+ 17
- 0
src/modules/freeform/components/TextInput/TextInput.test.tsx View File

@@ -0,0 +1,17 @@
import * as React from 'react';
import {
render,
screen
} from '@testing-library/react';
import '@testing-library/jest-dom';
import {
TextInput
} from '.';

describe('TextInput', () => {
it('should render a textbox', () => {
render(<TextInput />);
const textbox = screen.getByRole('textbox');
expect(textbox).toBeInTheDocument();
});
});

+ 126
- 0
src/modules/freeform/components/TextInput/index.tsx View File

@@ -0,0 +1,126 @@
import * as React from 'react';
import * as TextControlBase from '@tesseract-design/web-base-textcontrol';

export enum TextInputType {
TEXT = 'text',
SEARCH = 'search',
}

export type TextInputProps = Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'type' | 'style'> & {
/**
* Short textual description indicating the nature of the component's value.
*/
label?: React.ReactNode,
/**
* Short textual description as guidelines for valid input values.
*/
hint?: React.ReactNode,
/**
* Size of the component.
*/
size?: TextControlBase.TextControlSize,
/**
* Additional description, usually graphical, indicating the nature of the component's value.
*/
indicator?: React.ReactNode,
/**
* Should the component display a border?
*/
border?: boolean,
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean,
/**
* Type of the component value.
*/
type?: TextInputType,
/**
* Style of the component.
*/
style?: TextControlBase.TextControlStyle,
/**
* Is the label hidden?
*/
hiddenLabel?: boolean,
}

/**
* Component for inputting textual values.
*
* This component supports multiline input and adjusts its layout accordingly.
*/
export const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
(
{
label = '',
hint = '',
indicator = null,
size = TextControlBase.TextControlSize.MEDIUM,
border = false,
block = false,
type = TextInputType.TEXT,
style = TextControlBase.TextControlStyle.DEFAULT,
hiddenLabel = false,
className: _className,
placeholder: _placeholder,
as: _as,
...etcProps
}: TextInputProps,
ref,
) => {
const textInputBaseArgs = React.useMemo<TextControlBase.TextControlBaseArgs>(() => ({
block,
border,
size,
indicator: Boolean(indicator),
style,
resizable: false,
predefinedValues: false,
}), [block, border, size, indicator, style]);

return (
<div
className={TextControlBase.ComponentBase(textInputBaseArgs)}
>
<input
{...etcProps}
className={TextControlBase.Input(textInputBaseArgs)}
ref={ref}
aria-label={label}
type={type}
/>
{border && <span />}
{
label && !hiddenLabel && (
<div
className={TextControlBase.LabelWrapper(textInputBaseArgs)}
>
{label}
</div>
)
}
{hint && (
<div
className={TextControlBase.HintWrapper(textInputBaseArgs)}
>
<div
className={TextControlBase.Hint()}
>
{hint}
</div>
</div>
)}
{indicator && (
<div
className={TextControlBase.IndicatorWrapper(textInputBaseArgs)}
>
{indicator}
</div>
)}
</div>
);
}
);

TextInput.displayName = 'TextInput';

+ 3
- 0
src/modules/freeform/index.ts View File

@@ -0,0 +1,3 @@
export * from './components/MultilineTextInput';
export * from './components/MaskedTextInput';
export * from './components/TextInput';

+ 66
- 0
src/modules/info/components/Badge/index.tsx View File

@@ -0,0 +1,66 @@
import * as React from 'react';
import {
css,
} from 'goober';

const BadgeBase = ({ rounded }: { rounded: boolean }) => css`
position: relative;
height: 1.5em;
min-width: 1.5em;
display: inline-grid;
vertical-align: middle;
place-content: center;
border-radius: ${rounded ? '0.75em' : '0.25rem'};
overflow: hidden;
font-stretch: var(--font-stretch-base, normal);
padding: 0 0.25rem;
box-sizing: border-box;
&::before {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: currentColor;
opacity: 0.25;
content: '';
}
`;

const Content = () => css`
position: relative;
font-size: 0.75em;
`;

export type BadgeProps = React.HTMLProps<HTMLSpanElement> & {
rounded?: boolean,
};

export const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
(
{
children,
rounded = false,
},
ref,
) => {
const badgeStyleProps = React.useMemo(() => ({
rounded,
}), [rounded]);

return (
<strong
ref={ref}
className={BadgeBase(badgeStyleProps)}
>
<span
className={Content()}
>
{children}
</span>
</strong>
)
}
)

Badge.displayName = 'Badge';

+ 1
- 0
src/modules/info/index.ts View File

@@ -0,0 +1 @@
export * from './components/Badge';

+ 189
- 0
src/modules/navigation/components/LinkButton/index.tsx View File

@@ -0,0 +1,189 @@
import * as React from 'react';
import * as ButtonBase from '@tesseract-design/web-base-button';

type LinkButtonElement = HTMLAnchorElement | HTMLSpanElement;

export type LinkButtonProps = Omit<React.HTMLProps<LinkButtonElement>, 'size' | 'style'> & {
/**
* Size of the component.
*/
size?: ButtonBase.ButtonSize,
/**
* Variant of the component.
*/
variant?: ButtonBase.ButtonVariant,
/**
* Should the component display a border?
*/
border?: boolean,
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean,
/**
* Can the component be activated?
*/
disabled?: boolean,
/**
* Style of the component.
*/
style?: ButtonBase.ButtonStyle,
/**
* Does the component need to conserve space?
*/
compact?: boolean,
/**
* Complementary content of the component.
*/
subtext?: React.ReactNode,
/**
* Short complementary content displayed at the edge of the component.
*/
badge?: React.ReactNode,
/**
* Is this component part of a menu?
*/
menuItem?: boolean,
};

/**
* Component for performing an action upon activation (e.g. when clicked).
*
* This component functions as a hyperlink.
*/
export const LinkButton = React.forwardRef<LinkButtonElement, LinkButtonProps>(
(
{
size = ButtonBase.ButtonSize.MEDIUM,
variant = ButtonBase.ButtonVariant.OUTLINE,
border = false,
children,
block = false,
disabled = false,
style = ButtonBase.ButtonStyle.DEFAULT,
onClick,
href,
target,
rel,
compact = false,
subtext,
badge,
menuItem = false,
className: _className,
as: _as,
...etcProps
}: LinkButtonProps,
ref,
) => {
const styleProps = React.useMemo<ButtonBase.ButtonBaseArgs>(() => ({
size,
block,
variant,
border,
disabled,
style,
compact,
menuItem,
}), [size, block, variant, border, disabled, style, compact, menuItem]);

const commonChildren = (
<>
<span
className={ButtonBase.Border(styleProps)}
/>
<span
className={ButtonBase.Label(styleProps)}
>
<span
className={ButtonBase.MainText()}
>
<span
className={ButtonBase.OverflowText()}
>
{children}
</span>
</span>
{
subtext
&& (
<>
{' '}
<span
className={ButtonBase.Subtext()}
>
<span
className={ButtonBase.OverflowText()}
>
{subtext}
</span>
</span>
</>
)
}
</span>
{
badge
&& (
<>
{' '}
<span
className={ButtonBase.BadgeContainer(styleProps)}
>
{badge}
</span>
</>
)
}
{
menuItem
&& (
<>
{' '}
<span
className={ButtonBase.IndicatorWrapper(styleProps)}
>
<svg
className={ButtonBase.Indicator()}
viewBox="0 0 24 24"
role="presentation"
>
<polyline points="9 18 15 12 9 6"/>
</svg>
</span>
</>
)
}
</>
);

if (disabled) {
return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
<span
{...etcProps}
className={ButtonBase.Button(styleProps)}
ref={ref as React.ForwardedRef<HTMLSpanElement>}
onClick={undefined}
>
{commonChildren}
</span>
);
}

return (
<a
{...etcProps}
className={ButtonBase.Button(styleProps)}
ref={ref as React.ForwardedRef<HTMLAnchorElement>}
href={href}
target={target}
rel={rel}
onClick={onClick}
>
{commonChildren}
</a>
);
},
);

LinkButton.displayName = 'LinkButton';

+ 1
- 0
src/modules/navigation/index.ts View File

@@ -0,0 +1 @@
export * from './components/LinkButton';

+ 277
- 0
src/modules/option/components/Checkbox/index.tsx View File

@@ -0,0 +1,277 @@
import * as React from 'react';
import * as ButtonBase from '@tesseract-design/web-base-button';
import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol';

export type CheckboxProps = Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'type' | 'style'> & {
/**
* Size of the component.
*/
size?: ButtonBase.ButtonSize,
/**
* Variant of the component.
*/
variant?: ButtonBase.ButtonVariant,
/**
* Should the component display a border?
*/
border?: boolean,
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean,
/**
* Style of the component.
*/
style?: ButtonBase.ButtonStyle,
/**
* Does the component need to conserve space?
*/
compact?: boolean,
/**
* Complementary content of the component.
*/
subtext?: React.ReactNode,
/**
* Short complementary content displayed at the edge of the component.
*/
badge?: React.ReactNode,
/**
* Appearance of the component.
*/
appearance?: CheckControlBase.CheckControlAppearance,
/**
* Does the component have indeterminate check state?
*/
indeterminate?: boolean,
}

/**
* Component for performing an action upon activation (e.g. when clicked).
*
* This component functions as a regular button.
*/
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
(
{
size = ButtonBase.ButtonSize.MEDIUM,
variant = ButtonBase.ButtonVariant.OUTLINE,
border = false,
children,
block = false,
style = ButtonBase.ButtonStyle.DEFAULT,
disabled = false,
compact = false,
subtext,
badge,
appearance = CheckControlBase.CheckControlAppearance.DEFAULT,
indeterminate = false,
className: _className,
as: _as,
...etcProps
}: CheckboxProps,
ref,
) => {
const styleProps = React.useMemo<ButtonBase.ButtonBaseArgs & CheckControlBase.CheckControlBaseArgs>(() => ({
size,
block,
variant,
border,
style,
compact,
menuItem: false,
disabled,
appearance,
type: 'checkbox',
}), [size, block, variant, border, style, compact, disabled, appearance]);
const defaultRef = React.useRef<HTMLInputElement>(null);
const theRef = (ref ?? defaultRef) as React.MutableRefObject<HTMLInputElement>;

React.useEffect(() => {
if (!(indeterminate && theRef.current)) {
return;
}
theRef.current.indeterminate = indeterminate;
}, [theRef, indeterminate]);

const checkIndicatorCommon = (
<span
className={CheckControlBase.CheckIndicatorArea(styleProps)}
>
<span
className={CheckControlBase.CheckIndicatorWrapper(styleProps)}
>
<svg
className={CheckControlBase.CheckIndicator(styleProps)}
viewBox="0 0 24 24"
role="presentation"
>
<polyline
points="20 6 9 17 4 12"
/>
</svg>
<svg
className={CheckControlBase.CheckIndicator(styleProps)}
viewBox="0 0 24 24"
role="presentation"
>
<polyline
points="20 12 4 12"
/>
</svg>
</span>
</span>
)

return (
<div
className={CheckControlBase.ClickAreaWrapper(styleProps)}
>
<label
className={CheckControlBase.ClickArea()}
>
<input
{...etcProps}
disabled={disabled}
type="checkbox"
ref={theRef}
className={CheckControlBase.CheckStateContainer(styleProps)}
/>
{
appearance === CheckControlBase.CheckControlAppearance.DEFAULT
&& (
<span>
<span />
{checkIndicatorCommon}
<span>
{children}
{
subtext
&& (
<>
<br />
<span
className={CheckControlBase.Subtext()}
>
{subtext}
</span>
</>
)
}
</span>
{
badge
&& (
<>
{' '}
<span
className={ButtonBase.BadgeContainer(styleProps)}
>
{badge}
</span>
</>
)
}
</span>
)
}
{
appearance === CheckControlBase.CheckControlAppearance.BUTTON
&& (
<span
className={ButtonBase.Button(styleProps)}
>
<span
className={ButtonBase.Border(styleProps)}
/>
{checkIndicatorCommon}
<span
className={ButtonBase.Label(styleProps)}
>
<span
className={ButtonBase.MainText()}
>
<span
className={ButtonBase.OverflowText()}
>
{children}
</span>
</span>
{
subtext
&& (
<>
{' '}
<span
className={ButtonBase.Subtext()}
>
<span
className={ButtonBase.OverflowText()}
>
{subtext}
</span>
</span>
</>
)
}
</span>
{
badge
&& (
<>
{' '}
<span
className={ButtonBase.BadgeContainer(styleProps)}
>
{badge}
</span>
</>
)
}
</span>
)
}
{
appearance === CheckControlBase.CheckControlAppearance.SWITCH
&& (
<span>
<span />
{checkIndicatorCommon}
<span>
{children}
{
subtext
&& (
<>
<br />
<span
className={CheckControlBase.Subtext()}
>
{subtext}
</span>
</>
)
}
</span>
{
badge
&& (
<>
{' '}
<span
className={ButtonBase.BadgeContainer(styleProps)}
>
{badge}
</span>
</>
)
}
</span>
)
}
</label>
</div>
);
},
);

Checkbox.displayName = 'ActionButton';

+ 17
- 0
src/modules/option/components/DropdownSelect/DropdownSelect.test.tsx View File

@@ -0,0 +1,17 @@
import * as React from 'react';
import {
render,
screen
} from '@testing-library/react';
import '@testing-library/jest-dom';
import {
DropdownSelect
} from '.';

describe('DropdownSelect', () => {
it('should render a combobox', () => {
render(<DropdownSelect />);
const combobox = screen.getByRole('combobox');
expect(combobox).toBeInTheDocument();
});
});

+ 218
- 0
src/modules/option/components/DropdownSelect/index.tsx View File

@@ -0,0 +1,218 @@
import * as React from 'react';
import {
css
} from 'goober';
import * as TextControlBase from '@tesseract-design/web-base-textcontrol';

const Indicator = () => css`
width: 1.5em;
height: 1.5em;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
`;

export interface SelectOption {
label: string,
value?: string | number | readonly string[]
children?: SelectOption[]
}

type RenderOptionsProps = {
options: SelectOption[],
// FIXME bug in eslint does not play well with React.VFC
// eslint-disable-next-line react/require-default-props
optionComponent?: React.ElementType,
// eslint-disable-next-line react/require-default-props
optgroupComponent?: React.ElementType,
// eslint-disable-next-line react/require-default-props
level?: number,
}

const RenderOptions: React.VFC<RenderOptionsProps> = ({
options,
optionComponent: Option = 'option',
optgroupComponent: Optgroup = 'optgroup',
level = 0,
}: RenderOptionsProps) => (
<>
{
options.map((o) => {
if (typeof o.value !== 'undefined') {
return (
<Option
key={`${o.label}:${o.value.toString()}`}
value={o.value}
>
{o.label}
</Option>
);
}

if (typeof o.children !== 'undefined') {
if (level === 0) {
return (
<Optgroup
key={o.label}
label={o.label}
>
<RenderOptions
options={o.children}
optionComponent={Option}
optgroupComponent={Optgroup}
/>
</Optgroup>
);
}
return (
<React.Fragment
key={o.label}
>
<Option
disabled
>
{o.label}
</Option>
<RenderOptions
options={o.children}
optionComponent={Option}
optgroupComponent={Optgroup}
/>
</React.Fragment>
);
}

return null;
})
}
</>
);

export type DropdownSelectProps = Omit<React.HTMLProps<HTMLSelectElement>, 'size' | 'style' | 'children'> & {
/**
* Short textual description indicating the nature of the component's value.
*/
label?: React.ReactNode,
/**
* Short textual description as guidelines for valid input values.
*/
hint?: React.ReactNode,
/**
* Size of the component.
*/
size?: TextControlBase.TextControlSize,
/**
* Should the component display a border?
*/
border?: boolean,
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean,
/**
* Style of the component.
*/
style?: TextControlBase.TextControlStyle,
/**
* Is the label hidden?
*/
hiddenLabel?: boolean,
/**
* Options available for the component's values.
*/
options?: SelectOption[],
/**
* Component for rendering options.
*/
renderOptions?: React.ElementType,
}

/**
* Component for inputting textual values.
*
* This component supports multiline input and adjusts its layout accordingly.
*/
export const DropdownSelect = React.forwardRef<HTMLSelectElement, DropdownSelectProps>(
(
{
label = '',
hint = '',
size = TextControlBase.TextControlSize.MEDIUM,
border = false,
block = false,
style = TextControlBase.TextControlStyle.DEFAULT,
hiddenLabel = false,
multiple: _multiple,
className: _className,
placeholder: _placeholder,
as: _as,
options = [],
renderOptions: Render = RenderOptions,
...etcProps
}: DropdownSelectProps,
ref,
) => {
const styleArgs = React.useMemo<TextControlBase.TextControlBaseArgs>(() => ({
block,
border,
size,
indicator: true,
style,
resizable: true,
predefinedValues: true,
}), [block, border, size, style]);

return (
<div
className={TextControlBase.ComponentBase(styleArgs)}
>
<select
{...etcProps}
className={TextControlBase.Input(styleArgs)}
ref={ref}
aria-label={label}
>
<Render
options={options}
/>
</select>
{border && <span />}
{label && !hiddenLabel && (
<div
className={TextControlBase.LabelWrapper(styleArgs)}
>
{label}
</div>
)}
{hint && (
<div
className={TextControlBase.HintWrapper(styleArgs)}
>
<div
className={TextControlBase.Hint()}
>
{hint}
</div>
</div>
)}
<div
className={TextControlBase.IndicatorWrapper(styleArgs)}
>
<svg
className={Indicator()}
viewBox="0 0 24 24"
role="presentation"
>
<polyline
points="6 9 12 15 18 9"
/>
</svg>
</div>
</div>
);
}
);

DropdownSelect.displayName = 'DropdownSelect';

+ 250
- 0
src/modules/option/components/RadioButton/index.tsx View File

@@ -0,0 +1,250 @@
import * as React from 'react';
import * as ButtonBase from '@tesseract-design/web-base-button';
import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol';

export type RadioButtonProps = Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'type' | 'style'> & {
/**
* Size of the component.
*/
size?: ButtonBase.ButtonSize,
/**
* Variant of the component.
*/
variant?: ButtonBase.ButtonVariant,
/**
* Should the component display a border?
*/
border?: boolean,
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean,
/**
* Style of the component.
*/
style?: ButtonBase.ButtonStyle,
/**
* Does the component need to conserve space?
*/
compact?: boolean,
/**
* Complementary content of the component.
*/
subtext?: React.ReactNode,
/**
* Short complementary content displayed at the edge of the component.
*/
badge?: React.ReactNode,
/**
* Appearance of the component.
*/
appearance?: CheckControlBase.CheckControlAppearance,
}

/**
* Component for performing an action upon activation (e.g. when clicked).
*
* This component functions as a regular button.
*/
export const RadioButton = React.forwardRef<HTMLInputElement, RadioButtonProps>(
(
{
size = ButtonBase.ButtonSize.MEDIUM,
variant = ButtonBase.ButtonVariant.OUTLINE,
border = false,
children,
block = false,
style = ButtonBase.ButtonStyle.DEFAULT,
disabled = false,
compact = false,
subtext,
badge,
appearance = CheckControlBase.CheckControlAppearance.DEFAULT,
className: _className,
as: _as,
...etcProps
}: RadioButtonProps,
ref,
) => {
const styleProps = React.useMemo<ButtonBase.ButtonBaseArgs & CheckControlBase.CheckControlBaseArgs>(() => ({
size,
block,
variant,
border,
style,
compact,
menuItem: false,
disabled,
appearance,
type: 'radio',
}), [size, block, variant, border, style, compact, disabled, appearance]);

const checkIndicatorCommon = (
<span
className={CheckControlBase.CheckIndicatorArea(styleProps)}
>
<span
className={CheckControlBase.CheckIndicatorWrapper(styleProps)}
/>
</span>
)

return (
<div
className={CheckControlBase.ClickAreaWrapper(styleProps)}
>
<label
className={CheckControlBase.ClickArea()}
>
<input
{...etcProps}
disabled={disabled}
type="radio"
ref={ref}
className={CheckControlBase.CheckStateContainer(styleProps)}
/>
{
appearance === CheckControlBase.CheckControlAppearance.DEFAULT
&& (
<span>
<span />
{checkIndicatorCommon}
<span>
{children}
{
subtext
&& (
<>
<br />
<span
className={CheckControlBase.Subtext()}
>
{subtext}
</span>
</>
)
}
</span>
{
badge
&& (
<>
{' '}
<span
className={ButtonBase.BadgeContainer(styleProps)}
>
{badge}
</span>
</>
)
}
</span>
)
}
{
appearance === CheckControlBase.CheckControlAppearance.BUTTON
&& (
<span
className={ButtonBase.Button(styleProps)}
>
<span
className={ButtonBase.Border(styleProps)}
/>
{checkIndicatorCommon}
<span
className={ButtonBase.Label(styleProps)}
>
<span
className={ButtonBase.MainText()}
>
<span
className={ButtonBase.OverflowText()}
>
{children}
</span>
</span>
{
subtext
&& (
<>
{' '}
<span
className={ButtonBase.Subtext()}
>
<span
className={ButtonBase.OverflowText()}
>
{subtext}
</span>
</span>
</>
)
}
</span>
{
badge
&& (
<>
{' '}
<span
className={ButtonBase.BadgeContainer(styleProps)}
>
{badge}
</span>
</>
)
}
</span>
)
}
{
appearance === CheckControlBase.CheckControlAppearance.SWITCH
&& (
<span>
<span />
<span
className={CheckControlBase.CheckIndicatorArea(styleProps)}
>
<span
className={CheckControlBase.CheckIndicatorWrapper(styleProps)}
/>
</span>
<span>
{children}
{
subtext
&& (
<>
<br />
<span
className={CheckControlBase.Subtext()}
>
{subtext}
</span>
</>
)
}
</span>
{
badge
&& (
<>
{' '}
<span
className={ButtonBase.BadgeContainer(styleProps)}
>
{badge}
</span>
</>
)
}
</span>
)
}
</label>
</div>
);
},
);

RadioButton.displayName = 'ActionButton';

+ 3
- 0
src/modules/option/index.ts View File

@@ -0,0 +1,3 @@
export * from './components/Checkbox';
export * from './components/DropdownSelect';
export * from './components/RadioButton';

+ 13
- 0
src/pages/_app.tsx View File

@@ -0,0 +1,13 @@
import * as goober from 'goober';
import * as gooberPrefixer from 'goober/prefixer';
import type { AppProps } from 'next/app';

import * as React from 'react';

goober.setup(React.createElement, gooberPrefixer.prefix);

const App: React.VFC<AppProps> = ({ Component, pageProps }) => {
return <Component {...pageProps} />
}

export default App

+ 48
- 0
src/pages/_document.tsx View File

@@ -0,0 +1,48 @@
import NextDocument, {Html, Head as NextHead, Main, NextScript, DocumentContext} from 'next/document';
import * as goober from 'goober';

export default class Document extends NextDocument {
static async getInitialProps(ctx: DocumentContext) {
const page = await ctx.renderPage()

const style = goober.extractCss();
const initialProps = await NextDocument.getInitialProps(ctx)

return {
...initialProps,
...page,
style,
}
}

render() {
const { style: rawStyle } = this.props as Record<string, unknown>
const style = rawStyle as string

return (
<Html>
<NextHead
className="container mx-auto px-4 block mt-16 md:mt-32"
>
<link rel="stylesheet" href="/tailwind-utilities.css" />
<link rel="stylesheet" href="/global.css" />
<link rel="stylesheet" href="/theme.css" />
<link rel="stylesheet" href="/theme/dark.css" />
{
style.length > 0
&& (
<style
id="_goober_ssr"
dangerouslySetInnerHTML={{ __html: style }}
/>
)
}
</NextHead>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}

+ 251
- 0
src/pages/categories/action/index.tsx View File

@@ -0,0 +1,251 @@
import { NextPage } from 'next';
import { ButtonSize, ButtonVariant } from '@tesseract-design/web-base-button';
import * as Action from '@tesseract-design/web-action-react';
import { DefaultLayout } from 'src/components/DefaultLayout';

const ActionPage: NextPage = () => {
return (
<DefaultLayout
title="Action"
>
<main className="mt-8 mb-16 md:mt-16 md:mb-32">
<section>
<div className="container mx-auto px-4">
<h1>
ActionButton
</h1>
<div>
<section>
<h2>Variants</h2>
<div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 my-4">
<div>
<Action.ActionButton
block
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
variant={ButtonVariant.FILLED}
block
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
border
block
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
border
variant={ButtonVariant.FILLED}
block
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
block
disabled
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
variant={ButtonVariant.FILLED}
block
disabled
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
border
block
disabled
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
border
variant={ButtonVariant.FILLED}
block
disabled
>
Button
</Action.ActionButton>
</div>
</div>
</div>
</section>
<section>
<h2>Sizes</h2>
<div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 my-4">
<div>
<Action.ActionButton
block
border
size={ButtonSize.SMALL}
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
block
border
variant={ButtonVariant.FILLED}
size={ButtonSize.SMALL}
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
block
border
size={ButtonSize.MEDIUM}
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
block
border
variant={ButtonVariant.FILLED}
size={ButtonSize.MEDIUM}
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
block
border
size={ButtonSize.LARGE}
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
block
border
variant={ButtonVariant.FILLED}
size={ButtonSize.LARGE}
>
Button
</Action.ActionButton>
</div>
</div>
</div>
</section>
<section>
<h2>Compact</h2>
<div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 my-4">
<div>
<Action.ActionButton
block
compact
border
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
block
compact
border
variant={ButtonVariant.FILLED}
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
block
compact
border
subtext={
<>
Subtext
</>
}
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
block
compact
border
variant={ButtonVariant.FILLED}
subtext={
<>
Subtext
</>
}
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
block
compact
border
size={ButtonSize.SMALL}
subtext={
<>
Subtext
</>
}
>
Button
</Action.ActionButton>
</div>
<div>
<Action.ActionButton
block
compact
border
variant={ButtonVariant.FILLED}
size={ButtonSize.SMALL}
subtext={
<>
Very Long Line of Subtext That Spans More Than The Component Width For Testing Overflow
</>
}
>
Very Long Line of Text That Spans More Than The Component Width For Testing Overflow
</Action.ActionButton>
</div>
</div>
</div>
</section>
</div>
</div>
</section>
</main>
</DefaultLayout>
)
}

export default ActionPage

+ 45
- 0
src/pages/categories/code/index.tsx View File

@@ -0,0 +1,45 @@
import { NextPage } from 'next';
import { DefaultLayout } from 'src/components/DefaultLayout';

const CodePage: NextPage = () => {
return (
<DefaultLayout
title="Code"
>
<main className="mt-8 mb-16 md:mt-16 md:mb-32">
<section>
<div className="container mx-auto px-4">
<h2>
VerifyCodeInput
</h2>
<div>
TODO
</div>
</div>
</section>
<section>
<div className="container mx-auto px-4">
<h2>
CodeBlock
</h2>
<div>
TODO
</div>
</div>
</section>
<section>
<div className="container mx-auto px-4">
<h2>
CodeInput
</h2>
<div>
TODO
</div>
</div>
</section>
</main>
</DefaultLayout>
)
}

export default CodePage;

+ 666
- 0
src/pages/categories/freeform/index.tsx View File

@@ -0,0 +1,666 @@
import { NextPage } from 'next';
import { TextControlSize, TextControlStyle } from '@tesseract-design/web-base-textcontrol';
import * as Freeform from '@tesseract-design/web-freeform-react';
import { DefaultLayout } from 'src/components/DefaultLayout';

const FreeformPage: NextPage = () => {
return (
<DefaultLayout
title="Freeform"
>
<main className="mt-8 mb-16 md:mt-16 md:mb-32">
<section>
<div className="container mx-auto px-4">
<h1>
TextInput
</h1>
<div>
<section>
<h2>
Default
</h2>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
<div>
<Freeform.TextInput
size={TextControlSize.SMALL}
label="TextInput ffgg"
hint="Type anything here&hellip; ffgg"
indicator="A"
block
/>
</div>
<div>
<Freeform.TextInput
border
size={TextControlSize.SMALL}
label="TextInput ffgg"
hint="Type anything here&hellip; ffgg"
indicator="A"
block
/>
</div>
<div>
<Freeform.TextInput
label="TextInput ffgg"
hint="Type anything here&hellip; ffgg"
indicator="A"
block
/>
</div>
<div>
<Freeform.TextInput
border
label="TextInput ffgg"
hint="Type anything here&hellip; ffgg"
indicator="A"
block
/>
</div>
<div>
<Freeform.TextInput
size={TextControlSize.LARGE}
label="TextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.TextInput
border
size={TextControlSize.LARGE}
label="TextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.TextInput
label="TextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
/>
</div>
<div>
<Freeform.TextInput
border
label="TextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
/>
</div>
</div>
<form>
<fieldset className="contents">
<legend className="sr-only">Fieldset Test</legend>
<div className="grid grid-cols-6 gap-4 my-4">
<div className="col-span-2">
<Freeform.TextInput
label="First Name"
block
border
/>
</div>
<div className="col-span-2">
<Freeform.TextInput
label="Middle Name"
block
border
/>
</div>
<div className="col-span-2">
<Freeform.TextInput
label="Last Name"
block
border
/>
</div>
<div className="col-span-3">
<Freeform.TextInput
label="Username"
block
border
/>
</div>
<div className="col-span-3">
<Freeform.TextInput
label="Title"
block
border
disabled
/>
</div>
</div>
</fieldset>
</form>
<form>
<fieldset
disabled
className="contents"
>
<legend className="sr-only">Disabled Test</legend>
<div className="grid grid-cols-6 gap-4 my-4">
<div className="col-span-2">
<Freeform.TextInput
label="First Name"
block
border
/>
</div>
<div className="col-span-2">
<Freeform.TextInput
label="Middle Name"
block
border
/>
</div>
<div className="col-span-2">
<Freeform.TextInput
label="Last Name"
block
border
/>
</div>
<div className="col-span-3">
<Freeform.TextInput
label="Username"
block
border
/>
</div>
<div className="col-span-3">
<Freeform.TextInput
label="Title"
block
border
/>
</div>
</div>
</fieldset>
</form>
<form>
<div>
Hi, my name is
{' '}
<Freeform.TextInput
border
label="Name"
size={TextControlSize.SMALL}
/>
{' '}
but you can call me
{' '}
<Freeform.TextInput
border
label="Nickname"
size={TextControlSize.SMALL}
/>
.
</div>
</form>
</div>
</section>
<section>
<h2>Alternate</h2>
<div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Freeform.TextInput
style={TextControlStyle.ALTERNATE}
size={TextControlSize.SMALL}
label="TextInput ffgg"
hint="Type anything here&hellip; ffgg"
indicator="A"
block
/>
</div>
<div>
<Freeform.TextInput
border
style={TextControlStyle.ALTERNATE}
size={TextControlSize.SMALL}
label="TextInput ffgg"
hint="Type anything here&hellip; ffgg"
indicator="A"
block
/>
</div>
<div>
<Freeform.TextInput
style={TextControlStyle.ALTERNATE}
label="TextInput ffgg"
hint="Type anything here&hellip; ffgg"
block
/>
</div>
<div>
<Freeform.TextInput
border
style={TextControlStyle.ALTERNATE}
label="TextInput ffgg"
hint="Type anything here&hellip; ffgg"
block
/>
</div>
<div>
<Freeform.TextInput
style={TextControlStyle.ALTERNATE}
size={TextControlSize.LARGE}
label="TextInput ffgg"
hint="Type anything here&hellip; ffgg"
indicator="A"
block
/>
</div>
<div>
<Freeform.TextInput
border
block
style={TextControlStyle.ALTERNATE}
size={TextControlSize.LARGE}
label="TextInput"
hint="Type anything here&hellip;"
indicator="A"
/>
</div>
<div>
<Freeform.TextInput
style={TextControlStyle.ALTERNATE}
label="TextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
/>
</div>
<div>
<Freeform.TextInput
style={TextControlStyle.ALTERNATE}
border
label="TextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
/>
</div>
</div>
</div>
</section>
</div>
</div>
</section>
<section>
<div className="container mx-auto px-4">
<h1>
MaskedTextInput
</h1>
<div>
<section>
<h2>
Default
</h2>
<div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Freeform.MaskedTextInput
size={TextControlSize.SMALL}
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MaskedTextInput
border
size={TextControlSize.SMALL}
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MaskedTextInput
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MaskedTextInput
border
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MaskedTextInput
size={TextControlSize.LARGE}
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MaskedTextInput
border
size={TextControlSize.LARGE}
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MaskedTextInput
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
/>
</div>
<div>
<Freeform.MaskedTextInput
border
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
/>
</div>
</div>
</div>
</section>
<section>
<h2>Alternate</h2>
<div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Freeform.MaskedTextInput
style={TextControlStyle.ALTERNATE}
size={TextControlSize.SMALL}
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MaskedTextInput
border
style={TextControlStyle.ALTERNATE}
size={TextControlSize.SMALL}
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MaskedTextInput
style={TextControlStyle.ALTERNATE}
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MaskedTextInput
border
style={TextControlStyle.ALTERNATE}
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MaskedTextInput
style={TextControlStyle.ALTERNATE}
size={TextControlSize.LARGE}
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MaskedTextInput
border
block
style={TextControlStyle.ALTERNATE}
size={TextControlSize.LARGE}
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
/>
</div>
<div>
<Freeform.MaskedTextInput
style={TextControlStyle.ALTERNATE}
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
/>
</div>
<div>
<Freeform.MaskedTextInput
style={TextControlStyle.ALTERNATE}
border
label="MaskedTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
/>
</div>
</div>
</div>
</section>
</div>
</div>
</section>
<section>
<div className="container mx-auto px-4">
<h1>
MultilineTextInput
</h1>
<div>
<section>
<h2>
Default
</h2>
<div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Freeform.MultilineTextInput
size={TextControlSize.SMALL}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MultilineTextInput
border
size={TextControlSize.SMALL}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MultilineTextInput
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MultilineTextInput
border
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MultilineTextInput
size={TextControlSize.LARGE}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MultilineTextInput
border
size={TextControlSize.LARGE}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MultilineTextInput
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
/>
</div>
<div>
<Freeform.MultilineTextInput
border
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
/>
</div>
</div>
</div>
</section>
<section>
<h2>Alternate</h2>
<div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Freeform.MultilineTextInput
style={TextControlStyle.ALTERNATE}
size={TextControlSize.SMALL}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MultilineTextInput
border
style={TextControlStyle.ALTERNATE}
size={TextControlSize.SMALL}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MultilineTextInput
style={TextControlStyle.ALTERNATE}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MultilineTextInput
border
style={TextControlStyle.ALTERNATE}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MultilineTextInput
style={TextControlStyle.ALTERNATE}
size={TextControlSize.LARGE}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
/>
</div>
<div>
<Freeform.MultilineTextInput
border
block
style={TextControlStyle.ALTERNATE}
size={TextControlSize.LARGE}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
/>
</div>
<div>
<Freeform.MultilineTextInput
style={TextControlStyle.ALTERNATE}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
/>
</div>
<div>
<Freeform.MultilineTextInput
style={TextControlStyle.ALTERNATE}
border
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
/>
</div>
</div>
</div>
</section>
</div>
</div>
</section>
</main>
</DefaultLayout>
);
};

export default FreeformPage;

+ 480
- 0
src/pages/categories/navigation/index.tsx View File

@@ -0,0 +1,480 @@
import { NextPage } from 'next';
import { ButtonSize, ButtonVariant } from '@tesseract-design/web-base-button';
import * as Navigation from '@tesseract-design/web-navigation-react';
import * as Info from '@tesseract-design/web-info-react';

const ActionPage: NextPage = () => {
return (
<main className="my-16 md:my-32">
<section>
<div className="container mx-auto px-4">
<h1>
Navigation
</h1>
<div>
<section>
<h2>
LinkButton
</h2>
<div>
<section>
<h3>Variants</h3>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
<div>
<Navigation.LinkButton
block
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
variant={ButtonVariant.FILLED}
block
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
border
block
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
border
variant={ButtonVariant.FILLED}
block
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
disabled
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
variant={ButtonVariant.FILLED}
block
disabled
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
border
block
disabled
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
border
variant={ButtonVariant.FILLED}
block
disabled
href="#"
>
Button
</Navigation.LinkButton>
</div>
</div>
</div>
</section>
<section>
<h3>Sizes</h3>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
<div>
<Navigation.LinkButton
block
border
size={ButtonSize.SMALL}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
border
variant={ButtonVariant.FILLED}
size={ButtonSize.SMALL}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
border
size={ButtonSize.MEDIUM}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
border
variant={ButtonVariant.FILLED}
size={ButtonSize.MEDIUM}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
border
size={ButtonSize.LARGE}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
border
variant={ButtonVariant.FILLED}
size={ButtonSize.LARGE}
href="#"
>
Button
</Navigation.LinkButton>
</div>
</div>
</div>
</section>
<section>
<h3>Compact</h3>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
<div>
<Navigation.LinkButton
block
compact
border
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
compact
border
variant={ButtonVariant.FILLED}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
border
compact
subtext={
<>
Subtext
</>
}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
compact
border
variant={ButtonVariant.FILLED}
subtext={
<>
Subtext
</>
}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
border
compact
size={ButtonSize.SMALL}
subtext={
<>
Subtext
</>
}
badge={
<Info.Badge
rounded
>
69
</Info.Badge>
}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
compact
border
variant={ButtonVariant.FILLED}
size={ButtonSize.SMALL}
subtext={
<>
Subtext
</>
}
badge={
<Info.Badge
rounded
>
69
</Info.Badge>
}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
border
compact
size={ButtonSize.LARGE}
subtext={
<>
Subtext
</>
}
badge={
<Info.Badge
rounded
>
69
</Info.Badge>
}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
compact
border
variant={ButtonVariant.FILLED}
size={ButtonSize.LARGE}
subtext={
<>
Subtext
</>
}
badge={
<Info.Badge
rounded
>
69
</Info.Badge>
}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
border
compact
menuItem
size={ButtonSize.SMALL}
subtext={
<>
Subtext
</>
}
badge={
<Info.Badge
rounded
>
69
</Info.Badge>
}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
compact
border
size={ButtonSize.SMALL}
variant={ButtonVariant.FILLED}
menuItem
subtext={
<>
Subtext
</>
}
badge={
<Info.Badge
rounded
>
69
</Info.Badge>
}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
border
compact
menuItem
subtext={
<>
Subtext
</>
}
badge={
<Info.Badge
rounded
>
69
</Info.Badge>
}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
compact
border
variant={ButtonVariant.FILLED}
menuItem
subtext={
<>
Subtext
</>
}
badge={
<Info.Badge
rounded
>
69
</Info.Badge>
}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
border
compact
menuItem
size={ButtonSize.LARGE}
subtext={
<>
Subtext
</>
}
badge={
<Info.Badge
rounded
>
69
</Info.Badge>
}
href="#"
>
Button
</Navigation.LinkButton>
</div>
<div>
<Navigation.LinkButton
block
compact
border
size={ButtonSize.LARGE}
variant={ButtonVariant.FILLED}
menuItem
subtext={
<>
Very Long Line of Subtext That Spans More Than The Component Width For Testing Overflow
</>
}
badge={
<Info.Badge
rounded
>
69
</Info.Badge>
}
href="#"
>
Very Long Line of Text That Spans More Than The Component Width For Testing Overflow
</Navigation.LinkButton>
</div>
</div>
</div>
</section>
</div>
</section>
</div>
</div>
</section>
</main>
)
}

export default ActionPage

+ 51
- 0
src/pages/categories/number/index.tsx View File

@@ -0,0 +1,51 @@
import { NextPage } from 'next';

const NumberPage: NextPage = () => {
return (
<main className="my-16 md:my-32">
<section>
<div className="container mx-auto px-4">
<h1>
Number
</h1>
<div>
<section>
<h2>
Spinner
</h2>
<div>
TODO

<input type="number" />
</div>
</section>
<section>
<h2>
Slider
</h2>
<div>
TODO

<input type="range" />
</div>
</section>
<section>
<h2>
Matrix
</h2>
<div>
TODO

<input type="range" />

<input type="range" />
</div>
</section>
</div>
</div>
</section>
</main>
)
}

export default NumberPage;

+ 953
- 0
src/pages/categories/option/index.tsx View File

@@ -0,0 +1,953 @@
import { NextPage } from 'next';
import { TextControlSize, TextControlStyle } from '@tesseract-design/web-base-textcontrol';
import * as Option from '@tesseract-design/web-option-react';
import { CheckControlAppearance } from '@tesseract-design/web-base-checkcontrol';
import { ButtonSize, ButtonVariant } from '@tesseract-design/web-base-button';

type Props = {
options: Option.SelectOption[],
}

const OptionPage: NextPage<Props> = ({
options = [
{
label: 'Fruits',
children: [
{
label: 'Mango',
value: 'mango',
},
{
label: 'Strawberry',
value: 'strawberry',
},
{
label: 'Avocado',
value: 'avocado',
},
{
label: 'Grapes',
value: 'grapes',
},
],
},
{
label: 'Non-fruits',
children: [
{
label: 'Chocolate',
value: 'chocolate',
},
{
label: 'Milk',
value: 'milk',
},
{
label: 'Coffee',
value: 'coffee',
}
],
},
],
}) => {
return (
<main className="my-16 md:my-32">
<section>
<div className="container mx-auto px-4">
<h1>
Option
</h1>
<div>
<section>
<h2>
DropdownSelect
</h2>
<div>
<section>
<h3>
Default
</h3>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
<div>
<Option.DropdownSelect
size={TextControlSize.SMALL}
label="Select"
hint="Type anything here&hellip;"
block
options={options}
/>
</div>
<div>
<Option.DropdownSelect
border
size={TextControlSize.SMALL}
label="Select"
hint="Type anything here&hellip;"
block
options={options}
/>
</div>
<div>
<Option.DropdownSelect
label="Select"
hint="Type anything here&hellip;"
block
options={options}
/>
</div>
<div>
<Option.DropdownSelect
border
label="Select"
hint="Type anything here&hellip;"
block
options={options}
/>
</div>
<div>
<Option.DropdownSelect
size={TextControlSize.LARGE}
label="Select"
hint="Type anything here&hellip;"
block
options={options}
/>
</div>
<div>
<Option.DropdownSelect
border
size={TextControlSize.LARGE}
label="Select"
hint="Type anything here&hellip;"
block
options={options}
/>
</div>
<div>
<Option.DropdownSelect
label="Select"
hint="Type anything here&hellip;"
block
disabled
options={options}
/>
</div>
<div>
<Option.DropdownSelect
border
label="Select"
hint="Type anything here&hellip;"
block
disabled
options={options}
/>
</div>
</div>
<form>
<fieldset disabled className="contents">
<legend className="sr-only">Disabled Test</legend>
<div className="grid grid-cols-6 gap-4 my-4">
<div className="col-span-2">
<Option.DropdownSelect
label="First Name"
block
border
/>
</div>
<div className="col-span-2">
<Option.DropdownSelect
label="Middle Name"
block
border
/>
</div>
<div className="col-span-2">
<Option.DropdownSelect
label="Last Name"
block
border
/>
</div>
<div className="col-span-3">
<Option.DropdownSelect
label="Username"
block
border
/>
</div>
<div className="col-span-3">
<Option.DropdownSelect
label="Title"
block
border
/>
</div>
</div>
</fieldset>
</form>
<form>
<div>
Hi, my name is
{' '}
<Option.DropdownSelect
border
label="Name"
size={TextControlSize.SMALL}
/>
{' '}
but you can call me
{' '}
<Option.DropdownSelect
border
label="Nickname"
size={TextControlSize.SMALL}
/>
.
</div>
</form>
</div>
</section>
<section>
<h3>Alternate</h3>
<div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Option.DropdownSelect
style={TextControlStyle.ALTERNATE}
size={TextControlSize.SMALL}
label="Select"
hint="Type anything here&hellip;"
block
options={options}
/>
</div>
<div>
<Option.DropdownSelect
border
style={TextControlStyle.ALTERNATE}
size={TextControlSize.SMALL}
label="Select"
hint="Type anything here&hellip;"
block
options={options}
/>
</div>
<div>
<Option.DropdownSelect
style={TextControlStyle.ALTERNATE}
label="Select"
hint="Type anything here&hellip;"
block
options={options}
/>
</div>
<div>
<Option.DropdownSelect
border
style={TextControlStyle.ALTERNATE}
label="Select"
hint="Type anything here&hellip;"
block
options={options}
/>
</div>
<div>
<Option.DropdownSelect
style={TextControlStyle.ALTERNATE}
size={TextControlSize.LARGE}
label="Select"
hint="Type anything here&hellip;"
block
options={options}
/>
</div>
<div>
<Option.DropdownSelect
border
block
style={TextControlStyle.ALTERNATE}
size={TextControlSize.LARGE}
label="Select"
hint="Type anything here&hellip;"
options={options}
/>
</div>
<div>
<Option.DropdownSelect
style={TextControlStyle.ALTERNATE}
label="Select"
hint="Type anything here&hellip;"
block
disabled
options={options}
/>
</div>
<div>
<Option.DropdownSelect
style={TextControlStyle.ALTERNATE}
border
label="Select"
hint="Type anything here&hellip;"
block
disabled
options={options}
/>
</div>
</div>
</div>
</section>
</div>
</section>
<section>
<h2>
RadioButton
</h2>
<div>
<section>
<h3>Default</h3>
<div>
<div className="grid gap-4 my-4">
<div>
<Option.RadioButton
block
subtext="Subtext"
name="RadioButton"
>
RadioButton
</Option.RadioButton>
</div>
</div>
</div>
</section>
<section>
<h3>Switch</h3>
<div>
<div className="grid gap-4 my-4">
<div>
<Option.RadioButton
block
subtext="Subtext"
name="RadioButton"
appearance={CheckControlAppearance.SWITCH}
>
RadioButton
</Option.RadioButton>
</div>
</div>
</div>
</section>
<section>
<h3>Button</h3>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
<div>
<Option.RadioButton
block
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
variant={ButtonVariant.FILLED}
block
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
border
block
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
border
variant={ButtonVariant.FILLED}
block
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
block
disabled
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
variant={ButtonVariant.FILLED}
block
disabled
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
border
block
disabled
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
border
variant={ButtonVariant.FILLED}
block
disabled
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
</div>
</div>
</section>
<section>
<h3>Sizes</h3>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
<div>
<Option.RadioButton
block
border
size={ButtonSize.SMALL}
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
block
border
variant={ButtonVariant.FILLED}
size={ButtonSize.SMALL}
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
block
border
size={ButtonSize.MEDIUM}
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
block
border
variant={ButtonVariant.FILLED}
size={ButtonSize.MEDIUM}
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
block
border
size={ButtonSize.LARGE}
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
block
border
variant={ButtonVariant.FILLED}
size={ButtonSize.LARGE}
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
</div>
</div>
</section>
<section>
<h3>Compact</h3>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
<div>
<Option.RadioButton
block
compact
border
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
block
compact
border
variant={ButtonVariant.FILLED}
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
block
compact
border
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
subtext={
<>
Subtext
</>
}
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
block
compact
border
variant={ButtonVariant.FILLED}
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
subtext={
<>
Subtext
</>
}
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
block
compact
border
size={ButtonSize.SMALL}
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
subtext={
<>
Subtext
</>
}
>
Button
</Option.RadioButton>
</div>
<div>
<Option.RadioButton
block
compact
border
variant={ButtonVariant.FILLED}
size={ButtonSize.SMALL}
appearance={CheckControlAppearance.BUTTON}
name="RadioButton"
subtext={
<>
Subtext
</>
}
>
Button
</Option.RadioButton>
</div>
</div>
</div>
</section>
</div>
</section>
<section>
<h2>
Checkbox
</h2>
<div>
<section>
<h3>Default</h3>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
<div>
<Option.Checkbox
block
subtext="Subtext"
>
Checkbox
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
block
indeterminate
subtext="Subtext"
>
Checkbox
</Option.Checkbox>
</div>
</div>
</div>
</section>
<section>
<h3>Switch</h3>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
<div>
<Option.Checkbox
block
subtext="Subtext"
appearance={CheckControlAppearance.SWITCH}
>
Checkbox
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
block
indeterminate
subtext="Subtext"
appearance={CheckControlAppearance.SWITCH}
>
Checkbox
</Option.Checkbox>
</div>
</div>
</div>
</section>
<section>
<h3>Button</h3>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
<div>
<Option.Checkbox
block
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
variant={ButtonVariant.FILLED}
block
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
border
block
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
border
variant={ButtonVariant.FILLED}
block
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
block
disabled
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
variant={ButtonVariant.FILLED}
block
disabled
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
border
block
disabled
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
border
variant={ButtonVariant.FILLED}
block
disabled
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
border
block
appearance={CheckControlAppearance.BUTTON}
indeterminate
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
border
variant={ButtonVariant.FILLED}
block
appearance={CheckControlAppearance.BUTTON}
indeterminate
>
Button
</Option.Checkbox>
</div>
</div>
</div>
</section>
<section>
<h3>Sizes</h3>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
<div>
<Option.Checkbox
block
border
size={ButtonSize.SMALL}
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
block
border
variant={ButtonVariant.FILLED}
size={ButtonSize.SMALL}
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
block
border
size={ButtonSize.MEDIUM}
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
block
border
variant={ButtonVariant.FILLED}
size={ButtonSize.MEDIUM}
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
block
border
size={ButtonSize.LARGE}
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
block
border
variant={ButtonVariant.FILLED}
size={ButtonSize.LARGE}
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
</div>
</div>
</section>
<section>
<h3>Compact</h3>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
<div>
<Option.Checkbox
block
compact
border
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
block
compact
border
variant={ButtonVariant.FILLED}
appearance={CheckControlAppearance.BUTTON}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
block
compact
border
appearance={CheckControlAppearance.BUTTON}
subtext={
<>
Subtext
</>
}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
block
compact
border
variant={ButtonVariant.FILLED}
appearance={CheckControlAppearance.BUTTON}
subtext={
<>
Subtext
</>
}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
block
compact
border
size={ButtonSize.SMALL}
appearance={CheckControlAppearance.BUTTON}
subtext={
<>
Subtext
</>
}
>
Button
</Option.Checkbox>
</div>
<div>
<Option.Checkbox
block
compact
border
variant={ButtonVariant.FILLED}
size={ButtonSize.SMALL}
appearance={CheckControlAppearance.BUTTON}
subtext={
<>
Subtext
</>
}
>
Button
</Option.Checkbox>
</div>
</div>
</div>
</section>
</div>
</section>
<section>
<h2>
TagList
</h2>
<div>
TODO
</div>
</section>
<section>
<h2>
ComboBox
</h2>
<div>
TODO input with datalist
</div>
</section>
</div>
</div>
</section>
</main>
)
}

export default OptionPage

+ 92
- 0
src/pages/examples/registration-form.tsx View File

@@ -0,0 +1,92 @@
import { NextPage } from 'next';
import * as Freeform from '@tesseract-design/web-freeform-react';
import * as Action from '@tesseract-design/web-action-react';
import { ButtonVariant } from '@tesseract-design/web-base-button';

const RegistrationFormPage: NextPage = () => {
return (
<main className="my-16 md:my-32">
<div className="container mx-auto px-4">
<h1>
Registration Form
</h1>
<p>
Input your data in order to gain access to the functions of this service.
</p>
<form>
<fieldset className="contents">
<legend className="sr-only">
Register
</legend>
<div className="grid sm:grid-cols-6 gap-4">
<div className="sm:col-span-2">
<Freeform.TextInput
block
border
label="First Name"
name="firstName"
/>
</div>
<div className="sm:col-span-2">
<Freeform.TextInput
block
border
label="Middle Name"
name="middleName"
/>
</div>
<div className="sm:col-span-2">
<Freeform.TextInput
block
border
label="Last Name"
name="lastName"
/>
</div>
<div className="sm:col-span-3">
<Freeform.TextInput
block
border
label="Email"
name="email"
/>
</div>
<div className="sm:col-span-3">
<Freeform.TextInput
block
border
label="Website"
name="website"
/>
</div>
<div className="sm:col-span-6 text-center">
<div
className="sm:flex items-center justify-end space-y-4 sm:space-y-0 sm:space-x-4"
>
<small>
By registering, you agree to the <a href="#">Terms</a> of this service.
</small>
{' '}
<div>
<Action.ActionButton
variant={ButtonVariant.FILLED}
block
border
type={Action.ActionButtonType.SUBMIT}
menuItem
subtext="Registration requires activation"
>
Submit
</Action.ActionButton>
</div>
</div>
</div>
</div>
</fieldset>
</form>
</div>
</main>
)
}

export default RegistrationFormPage;

+ 111
- 0
src/pages/index.tsx View File

@@ -0,0 +1,111 @@
import type { NextPage } from 'next'
import Link from 'next/link';
import * as Navigation from '@tesseract-design/web-navigation-react';
import { DefaultLayout } from 'src/components/DefaultLayout';

type Page = {
id: string,
href: string,
label: string,
}

type Props = {
componentPages?: Page[],
examplePages?: Page[],
}

const createPageLink = (p: Page) => (
<div
key={p.id}
>
<Link
href={p.href}
passHref
>
<Navigation.LinkButton
block
border
menuItem
>
{p.label}
</Navigation.LinkButton>
</Link>
</div>
)

const IndexPage: NextPage<Props> = ({
componentPages = [
{
id: 'action',
href: '/categories/action',
label: 'Action',
},
{
id: 'code',
href: '/categories/code',
label: 'Code',
},
{
id: 'freeform',
href: '/categories/freeform',
label: 'Freeform',
},
{
id: 'navigation',
href: '/categories/navigation',
label: 'Navigation',
},
{
id: 'number',
href: '/categories/number',
label: 'Number',
},
{
id: 'option',
href: '/categories/option',
label: 'Option',
},
],
examplePages = [
{
id: 'registration-form',
href: '/examples/registration-form',
label: 'Registration Form',
}
],
}) => {
return (
<DefaultLayout
title="Kitchen Sink"
>
<main className="mt-8 mb-16 md:mt-16 md:mb-32">
<section>
<div className="container mx-auto px-4">
<h1>
Components
</h1>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
{componentPages.map(createPageLink)}
</div>
</div>
</div>
</section>
<section>
<div className="container mx-auto px-4">
<h1>
Examples
</h1>
<div>
<div className="grid md:grid-cols-2 gap-4 my-4">
{examplePages.map(createPageLink)}
</div>
</div>
</div>
</section>
</main>
</DefaultLayout>
)
}

export default IndexPage

+ 35
- 0
tsconfig.json View File

@@ -0,0 +1,35 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": ".",
"paths": {
"@tesseract-design/web-action-react": ["./src/modules/action"],
"@tesseract-design/web-freeform-react": ["./src/modules/freeform"],
"@tesseract-design/web-info-react": ["./src/modules/info"],
"@tesseract-design/web-navigation-react": ["./src/modules/navigation"],
"@tesseract-design/web-option-react": ["./src/modules/option"],
"@tesseract-design/web-base-button": ["./src/modules/base-button"],
"@tesseract-design/web-base-checkcontrol": ["./src/modules/base-checkcontrol"],
"@tesseract-design/web-base-textcontrol": ["./src/modules/base-textcontrol"],
"@tesseract-design/css-utils": ["./src/modules/css-utils"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": ["node_modules", "./src/packages"]
}

+ 3
- 0
tsconfig.test.json View File

@@ -0,0 +1,3 @@
{
"extends": "./tsconfig.json"
}

+ 5563
- 0
yarn.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save