@@ -0,0 +1,3 @@ | |||
{ | |||
"extends": "next/core-web-vitals" | |||
} |
@@ -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/ |
@@ -0,0 +1 @@ | |||
@tesseract-design:registry=http://localhost:4873/ |
@@ -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. |
@@ -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', | |||
}, | |||
}, | |||
}; |
@@ -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. |
@@ -0,0 +1,4 @@ | |||
/** @type {import('next').NextConfig} */ | |||
module.exports = { | |||
reactStrictMode: true, | |||
} |
@@ -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" | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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); | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -0,0 +1,4 @@ | |||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none" | |||
xmlns="http://www.w3.org/2000/svg"> | |||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/> | |||
</svg> |
@@ -0,0 +1,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} | |||
</> | |||
) | |||
} |
@@ -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'; |
@@ -0,0 +1 @@ | |||
export * from './components/ActionButton'; |
@@ -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); | |||
`, | |||
); |
@@ -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; | |||
` | |||
); |
@@ -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], | |||
}), | |||
); |
@@ -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;}') | |||
}) | |||
}) | |||
}) |
@@ -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; |
@@ -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'); | |||
}); | |||
}); |
@@ -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'; |
@@ -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(); | |||
}); | |||
}); |
@@ -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'; |
@@ -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(); | |||
}); | |||
}); |
@@ -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'; |
@@ -0,0 +1,3 @@ | |||
export * from './components/MultilineTextInput'; | |||
export * from './components/MaskedTextInput'; | |||
export * from './components/TextInput'; |
@@ -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'; |
@@ -0,0 +1 @@ | |||
export * from './components/Badge'; |
@@ -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'; |
@@ -0,0 +1 @@ | |||
export * from './components/LinkButton'; |
@@ -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'; |
@@ -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(); | |||
}); | |||
}); |
@@ -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'; |
@@ -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'; |
@@ -0,0 +1,3 @@ | |||
export * from './components/Checkbox'; | |||
export * from './components/DropdownSelect'; | |||
export * from './components/RadioButton'; |
@@ -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 |
@@ -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> | |||
) | |||
} | |||
} |
@@ -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 |
@@ -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; |
@@ -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… ffgg" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.TextInput | |||
border | |||
size={TextControlSize.SMALL} | |||
label="TextInput ffgg" | |||
hint="Type anything here… ffgg" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.TextInput | |||
label="TextInput ffgg" | |||
hint="Type anything here… ffgg" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.TextInput | |||
border | |||
label="TextInput ffgg" | |||
hint="Type anything here… ffgg" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.TextInput | |||
size={TextControlSize.LARGE} | |||
label="TextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.TextInput | |||
border | |||
size={TextControlSize.LARGE} | |||
label="TextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.TextInput | |||
label="TextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
disabled | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.TextInput | |||
border | |||
label="TextInput" | |||
hint="Type anything here…" | |||
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… ffgg" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.TextInput | |||
border | |||
style={TextControlStyle.ALTERNATE} | |||
size={TextControlSize.SMALL} | |||
label="TextInput ffgg" | |||
hint="Type anything here… ffgg" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.TextInput | |||
style={TextControlStyle.ALTERNATE} | |||
label="TextInput ffgg" | |||
hint="Type anything here… ffgg" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.TextInput | |||
border | |||
style={TextControlStyle.ALTERNATE} | |||
label="TextInput ffgg" | |||
hint="Type anything here… ffgg" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.TextInput | |||
style={TextControlStyle.ALTERNATE} | |||
size={TextControlSize.LARGE} | |||
label="TextInput ffgg" | |||
hint="Type anything here… ffgg" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.TextInput | |||
border | |||
block | |||
style={TextControlStyle.ALTERNATE} | |||
size={TextControlSize.LARGE} | |||
label="TextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.TextInput | |||
style={TextControlStyle.ALTERNATE} | |||
label="TextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
disabled | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.TextInput | |||
style={TextControlStyle.ALTERNATE} | |||
border | |||
label="TextInput" | |||
hint="Type anything here…" | |||
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…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MaskedTextInput | |||
border | |||
size={TextControlSize.SMALL} | |||
label="MaskedTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MaskedTextInput | |||
label="MaskedTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MaskedTextInput | |||
border | |||
label="MaskedTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MaskedTextInput | |||
size={TextControlSize.LARGE} | |||
label="MaskedTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MaskedTextInput | |||
border | |||
size={TextControlSize.LARGE} | |||
label="MaskedTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MaskedTextInput | |||
label="MaskedTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
disabled | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MaskedTextInput | |||
border | |||
label="MaskedTextInput" | |||
hint="Type anything here…" | |||
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…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MaskedTextInput | |||
border | |||
style={TextControlStyle.ALTERNATE} | |||
size={TextControlSize.SMALL} | |||
label="MaskedTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MaskedTextInput | |||
style={TextControlStyle.ALTERNATE} | |||
label="MaskedTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MaskedTextInput | |||
border | |||
style={TextControlStyle.ALTERNATE} | |||
label="MaskedTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MaskedTextInput | |||
style={TextControlStyle.ALTERNATE} | |||
size={TextControlSize.LARGE} | |||
label="MaskedTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MaskedTextInput | |||
border | |||
block | |||
style={TextControlStyle.ALTERNATE} | |||
size={TextControlSize.LARGE} | |||
label="MaskedTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MaskedTextInput | |||
style={TextControlStyle.ALTERNATE} | |||
label="MaskedTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
disabled | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MaskedTextInput | |||
style={TextControlStyle.ALTERNATE} | |||
border | |||
label="MaskedTextInput" | |||
hint="Type anything here…" | |||
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…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MultilineTextInput | |||
border | |||
size={TextControlSize.SMALL} | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MultilineTextInput | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MultilineTextInput | |||
border | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MultilineTextInput | |||
size={TextControlSize.LARGE} | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MultilineTextInput | |||
border | |||
size={TextControlSize.LARGE} | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MultilineTextInput | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
disabled | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MultilineTextInput | |||
border | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
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…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MultilineTextInput | |||
border | |||
style={TextControlStyle.ALTERNATE} | |||
size={TextControlSize.SMALL} | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MultilineTextInput | |||
style={TextControlStyle.ALTERNATE} | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MultilineTextInput | |||
border | |||
style={TextControlStyle.ALTERNATE} | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MultilineTextInput | |||
style={TextControlStyle.ALTERNATE} | |||
size={TextControlSize.LARGE} | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MultilineTextInput | |||
border | |||
block | |||
style={TextControlStyle.ALTERNATE} | |||
size={TextControlSize.LARGE} | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MultilineTextInput | |||
style={TextControlStyle.ALTERNATE} | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
disabled | |||
/> | |||
</div> | |||
<div> | |||
<Freeform.MultilineTextInput | |||
style={TextControlStyle.ALTERNATE} | |||
border | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
disabled | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
</section> | |||
</div> | |||
</div> | |||
</section> | |||
</main> | |||
</DefaultLayout> | |||
); | |||
}; | |||
export default FreeformPage; |
@@ -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 |
@@ -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; |
@@ -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…" | |||
block | |||
options={options} | |||
/> | |||
</div> | |||
<div> | |||
<Option.DropdownSelect | |||
border | |||
size={TextControlSize.SMALL} | |||
label="Select" | |||
hint="Type anything here…" | |||
block | |||
options={options} | |||
/> | |||
</div> | |||
<div> | |||
<Option.DropdownSelect | |||
label="Select" | |||
hint="Type anything here…" | |||
block | |||
options={options} | |||
/> | |||
</div> | |||
<div> | |||
<Option.DropdownSelect | |||
border | |||
label="Select" | |||
hint="Type anything here…" | |||
block | |||
options={options} | |||
/> | |||
</div> | |||
<div> | |||
<Option.DropdownSelect | |||
size={TextControlSize.LARGE} | |||
label="Select" | |||
hint="Type anything here…" | |||
block | |||
options={options} | |||
/> | |||
</div> | |||
<div> | |||
<Option.DropdownSelect | |||
border | |||
size={TextControlSize.LARGE} | |||
label="Select" | |||
hint="Type anything here…" | |||
block | |||
options={options} | |||
/> | |||
</div> | |||
<div> | |||
<Option.DropdownSelect | |||
label="Select" | |||
hint="Type anything here…" | |||
block | |||
disabled | |||
options={options} | |||
/> | |||
</div> | |||
<div> | |||
<Option.DropdownSelect | |||
border | |||
label="Select" | |||
hint="Type anything here…" | |||
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…" | |||
block | |||
options={options} | |||
/> | |||
</div> | |||
<div> | |||
<Option.DropdownSelect | |||
border | |||
style={TextControlStyle.ALTERNATE} | |||
size={TextControlSize.SMALL} | |||
label="Select" | |||
hint="Type anything here…" | |||
block | |||
options={options} | |||
/> | |||
</div> | |||
<div> | |||
<Option.DropdownSelect | |||
style={TextControlStyle.ALTERNATE} | |||
label="Select" | |||
hint="Type anything here…" | |||
block | |||
options={options} | |||
/> | |||
</div> | |||
<div> | |||
<Option.DropdownSelect | |||
border | |||
style={TextControlStyle.ALTERNATE} | |||
label="Select" | |||
hint="Type anything here…" | |||
block | |||
options={options} | |||
/> | |||
</div> | |||
<div> | |||
<Option.DropdownSelect | |||
style={TextControlStyle.ALTERNATE} | |||
size={TextControlSize.LARGE} | |||
label="Select" | |||
hint="Type anything here…" | |||
block | |||
options={options} | |||
/> | |||
</div> | |||
<div> | |||
<Option.DropdownSelect | |||
border | |||
block | |||
style={TextControlStyle.ALTERNATE} | |||
size={TextControlSize.LARGE} | |||
label="Select" | |||
hint="Type anything here…" | |||
options={options} | |||
/> | |||
</div> | |||
<div> | |||
<Option.DropdownSelect | |||
style={TextControlStyle.ALTERNATE} | |||
label="Select" | |||
hint="Type anything here…" | |||
block | |||
disabled | |||
options={options} | |||
/> | |||
</div> | |||
<div> | |||
<Option.DropdownSelect | |||
style={TextControlStyle.ALTERNATE} | |||
border | |||
label="Select" | |||
hint="Type anything here…" | |||
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 |
@@ -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; |
@@ -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 |
@@ -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"] | |||
} |
@@ -0,0 +1,3 @@ | |||
{ | |||
"extends": "./tsconfig.json" | |||
} |