@nichoth/components v0.16.15
components
A collection of UI components made with preact and tonic.
install
npm i -S @nichoth/componentsuse
I recommend using this with vite + ESM, because it is easy. These are preact and tonic components; you will need to install preact or tonic.
The preact version is recommended, because some of the animations do not work well in the tonic version. In particular the radio-group, and hamburger components do not work well.
preact + htm
Use preact with tagged template literals.
!IMPORTANT
This is the preferred way to consume these, withhtm+ preact.
Import components from @nichoth/components/htm/*.
import { html } from 'htm/preact'
import { render, FunctionComponent } from 'preact'
import { TextInput } from '@nichoth/components/htm/text-input'
import '@nichoth/components/text-input.css'
const Example:FunctionComponent<{}> = function () {
return html`
<div>
<h3>Text Input</h3>
<${TextInput}
displayName="htm text input"
required=${true}
minLength=${3}
maxLength=${7}
name=${'htm-text-input'}
><//>
</div>
`
}
render(html`<${Example}><//>`, document.getElementById('root')!)preact + JSX
Use preact with JSX
import { render, FunctionComponent } from 'preact'
import { TextInput } from '@nichoth/components/preact/text-input'
import '@nichoth/components/text-input.css'
const Example:FunctionComponent = function () {
return <div>
<h3>Text Input</h3>
<form className="example-form">
<TextInput name="text" displayName="Input test" />
</form>
</div>
}
render(<Example />, document.getElementById('root')!)tonic
Use web components with Tonic
import Tonic from '@nichoth/tonic'
import { SpinningButton } from '@nichoth/components/tonic/spinning-button'
import '@nichoth/components/text-input.css'
export class TonicExample extends Tonic {
state = {
isSpinning: false,
}
render () {
return this.html`<div>
<spinning-button
isSpinning=${this.state.isSpinning}
data-event="click-the-button"
>
click here
</spinning-button>
</div>`
}
}
Tonic.add(SpinningButton)
Tonic.add(TonicExample)globals
We use these CSS variables
:root {
--transition-time: 0.2s;
--button-outine-color: black;
--button-primary-outline: #0077ff;
--button-outine-primary-bg-hover: rgb(0 255 255 / 28%);
--button-outline-disabled-ol: #0077ff5c;
--text-input-error-border: red;
--text-input-error-text: red;
--hamburger-color: black;
--fade-in-time: 0.2s; /* for the mobile nav fade-in animation */
}Define them in your application to customize.
API
css
Import the css variables in addition to the per-component css.
With Vite as bundler:
import '@nichoth/components/variables.css'Switch
See ./src/htm/switch
css
Affected by the transition variable.
:root {
--transition-time: 0.2s;
}example
import { html } from 'htm/preact'
import { Switch } from '@nichoth/components/htm/switch'
import '@nichoth/components/switch.css'
function Example () {
return html`<div id="switch">
<form
onSubmit=${ev => {
ev.preventDefault()
const el = ev.target.elements['test-switch']
console.log('el.checked', el.checked)
}}>
<${Switch} name="test-switch" />
<button type="submit">submit</button>
</form>
</div>`checkbox
Pass in the checkbox label as child text.
Optionally Pass in a signal as the checked value, to use as a controlled input. Or do not pass in a checkedState for a non-controlled input.
const Checkbox:FunctionComponent<{
checkedState?:Signal<boolean>;
} & JSX.HTMLAttributes<HTMLCheckbox>> = function (props)example
import { html } from 'htm/preact'
import { Checkbox } from '@nichoth/components/htm/checkbox'
import '@nichoth/components/checkbox.css'
function Example () {
// note we pass in the checkbox label text as a child
return html`<form onSubmit=${ev => {
ev.preventDefault()
const testbox = ev.target.elements.testbox
console.log('testbox value', testbox.checked)
}}>
<fieldset>
<legend>checkbox demo</legend>
${/* Can pass in `checkedState` here */}
<${Checkbox} name="testbox">Testing checkbox<//>
</fieldset>
<button type="submit">submit</button>
</form>`
}css
CSS selectors:
label.checkboxand
input.checkboxAccordion
Available in preact/htm only.
Accordion CSS
It is affected by the --accordion-transition-time, and --x-transition-time CSS variables
:root {
--accordion-transition-time: 0.4s;
--x-transition-time: 0.2s;
}example
import { html } from 'htm/preact'
import { Accordion } from '@nichoth/components/htm/accordion'
import '@nichoth/components/accordion.css'
function Example () {
return html`<${Accordion}>
<summary>Trying accordion example</summary>
<p>This is the nested paragraph element in the accordion demo</p>
<//>`
}Results in UI like this:
closed:

open:

Button
Show resolving state with a spinner in the button. Either pass in a signal to
use as resolving state, or just return a promise from the click event handler.
Button example
import { FunctionComponent } from 'preact'
import { html } from 'htm/preact'
import { useSignal } from '@preact/signals'
import { Button } from '@nichoth/components/preact/button'
const isResolving = useSignal(false)
const MyComponent:FunctionComponent = function () {
return html`<Button
isSpinning={resolving} ${/*<- note we are passing a signal, not boolean*/}
onClick={clicker}
>
example
</Button>`
}Button Outline
If you return a promise from the onClick event handler, then the button will spin until the promise resolves.
Takes an optional signal for isSpinning. To control the spinning state of the button, eg for a form submit, pass in a signal and update its value.
Looks for a few css variables:
:root {
--button-outine-color: black;
--button-primary-outline: #0077ff;
--button-outine-primary-bg-hover: rgb(0 255 255 / 28%);
--button-outline-disabled-ol: #0077ff5c;
}interface ButtonProps extends JSX.HTMLAttributes<HTMLButtonElement> {
isSpinning?: Signal<boolean>,
className?: string,
onClick?: (ev:MouseEvent) => Promise<any>
}example
import { ButtonOutine } from '@nichoth/components/preact/button-outline'
<ButtonOutline
type="submit"
onClick={ev => {
ev.preventDefault()
console.log('click')
// if you return a promise, then the button
// will spin until it resolves
return sleep(2000)
}}
>
example
</ButtonOutline>ButtonLink
A link that looks like a button. Use with htm.
import { html } from 'htm/preact'
import { FunctionComponent } from 'preact'
/**
* A link that looks like a button
*
* @param {HTMLAttributes<HTMLAnchorElement>} props
* @returns {FunctionComponent}
*/
const ButtonLink:FunctionComponent<
HTMLAttributes<HTMLAnchorElement>
> = function (props) {
const className = [props.class, 'btn-link'].join(' ').trim()
return html`<a href=${props.href} class=${className}>${props.children}</a>`
}ButtonLink example
import { html } from 'htm/preact'
import { ButtonLink } from '@nichoth/components/htm/button-link'
import '@nichoth/components/button.css'
// ...
return html`<div>
<ButtonLink href="#" class="my-button">button text</ButtonLink>
</div>`CopyBtn
import { CopyBtn } from '@nichoth/components/preact/copy-btn'
<CopyBtn payload="copying things">copy</CopyBtn>CopyIconBtn
import { CopyIconBtn } from '@nicohoth/components/preact/copy-icon-btn'
// ...
<span>
Copy this text
<CopyIconBtn payload="copy this text" />
</span>Editable Field
interface Props extends JSX.HTMLAttributes<HTMLInputElement> {
onSave:(value:string) => Promise<any>
name:string
}
const EditableField:FunctionComponent<Props> = function EditableField (props)import { EditableField } from '@nichoth/components/preact/editable-field'
// ...
<EditableField
name="editable-field"
value="edit this"
onSave={saver}
/>Text Input
This looks for 2 css variables:
:root {
--text-input-error-border: red;
--text-input-error-text: red;
}Pass in an attribute title; this determines the invalid hint text that is shown below the input.
interface InputProps extends JSX.HTMLAttributes<HTMLInputElement> {
displayName: string;
name: string;
className?: string;
}
const TextInput:FunctionComponent<InputProps> = function (props:InputProps)example
import { TextInput } from '@nichoth/components/preact/text-input'
// ...
function MyElement () {
return html`<form className="example-form">
<${TextInput}
displayName="htm text input"
title="At least 3 characters, but less than 7"
required=${true}
minLength=${3}
maxLength=${7}
name=${'htm-text-input-example'}
><//>
</form>`
}PencilBtn
import { PencilBtn } from '@nichoth/components/preact/pencil-btn'
<PencilBtn onClick={(ev) => {
// we are passed a `click` event
ev.preventDefault()
console.log('click')
}} />RadioGroup
interface Props {
id?:string
name:string
legend:string
options:string[]
required:boolean
}
const RadioGroup:FunctionComponent<Props> = function (props)import { RadioGroup } from '@nichoth/components/preact/radio-group'
<RadioGroup
name="test-radio"
legend="testing radio group"
options={['aaa', 'bbb', 'ccc']}
required={true}
/>NumberInput
interface Props {
name:string;
min:number;
max:number;
value:Signal<number>;
onIncrease?:(ev:MouseEvent)=>any;
onDecrease?:(ev:MouseEvent)=>any;
onChange?:(ev:JSX.TargetedEvent)=>any;
}
const NumberInput:FunctionComponent<Props> = function NumberInput (props)import { NumberInput } from '@nichoth/components/preact/number-input'
const count = useSignal(3)
// ...
<NumberInput
min={0}
max={7}
name="test-input"
value={count}
onIncrease={() => {
console.log('increase')
}}
onDecrease={() => {
console.log('decrease')
}}
/>ReactiveForm
A form element that uses HTML attributes to check validity, and enables or
disables the submit button as appropriate.
type Props = {
onInput?:(event:InputEvent)=>any;
onSubmit:(event:SubmitEvent)=>any;
controls?:boolean;
buttonText?:string;
} & Readonly<Attributes & {
children?: ComponentChildren
}>
const ReactiveForm:FunctionComponent<Props> = function (props:Props)import { ReactiveForm } from '@nichoth/components/preact/reactive-form'
<ReactiveForm
onSubmit={async (ev:SubmitEvent) => {
ev.preventDefault()
const text = ((ev.target as HTMLFormElement)
.elements
.namedItem('text') as HTMLInputElement)
await sleep(2000)
console.log('resolved...', text.value)
}}
>
<TextInput
required={true}
displayName="text input"
name="text"
/>
</ReactiveForm>example
import { render } from 'preact'
import { useSignal } from '@preact/signals'
import HamburgerWrapper from '@nichoth/components/preact/hamburger'
import MobileNav from '@nichoth/components/preact/mobile-nav-menu'
import { CopyBtn, CopyIconBtn } from '@nichoth/components/preact/copy-btn'
import '@nichoth/components/variables.css'
import '@nichoth/components/copy-btn.css'
import '@nichoth/components/hamburger.css'
import '@nichoth/components/mobile-nav-menu.css'
import '@nichoth/components/z-index.css'
const App = function App () {
const isOpen = useSignal(false)
function mobileNavHandler (ev) {
ev.preventDefault()
isOpen.value = !isOpen.value
}
return <div class="app">
<HamburgerWrapper isOpen={isOpen} onClick={mobileNavHandler} />
<MobileNav isOpen={isOpen}>
<a href="/baloney">baloney</a>
<a href="/test">testing</a>
<//>
<CopyBtn payload="hurray">copy something</CopyBtn>
<p>Copy this <CopyIconBtn payload="Copy this" /></p>
</div>
}
const el = document.getElementById('root')
if (el) render(<App />, el)css
We look for a css variable --hamburger-color, or by default use a black color.
/* in your css file */
:root {
--hamburger-color: #FAFAFA;
}see also
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago