Skip to content

Commit

Permalink
[refactor] rewrite Button & Form components (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
TechQuery authored Jan 13, 2024
1 parent 3b5be3b commit d6e6ab0
Show file tree
Hide file tree
Showing 14 changed files with 283 additions and 150 deletions.
Empty file modified .husky/pre-commit
100644 → 100755
Empty file.
Empty file modified .husky/pre-push
100644 → 100755
Empty file.
4 changes: 3 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
.parcelrc
.parcel-cache/
.prettierrc.yml
v1/
test/
jest.config.js
jest.config.ts
docs/
.husky/
.github/
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
"dependencies": {
"@swc/helpers": "^0.5.3",
"classnames": "^2.5.1",
"dom-renderer": "^2.0.4",
"dom-renderer": "^2.0.5",
"mobx": "^6.12.0",
"regenerator-runtime": "^0.14.1",
"web-cell": "^3.0.0-rc.4",
"web-cell": "^3.0.0-rc.6",
"web-utility": "^4.1.3"
},
"peerDependencies": {
Expand Down
18 changes: 9 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 78 additions & 0 deletions source/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import classNames from 'classnames';
import { JsxChildren, VNode } from 'dom-renderer';
import { FC, WebCellProps } from 'web-cell';

import { FormControlProps } from './Form';
import { Icon, IconProps } from './Icon';
import { Color } from './type';

export interface ButtonProps
extends WebCellProps<HTMLButtonElement>,
Omit<WebCellProps<HTMLAnchorElement>, 'type'>,
Pick<FormControlProps<'input'>, 'size'> {
variant?: Color | `outline-${Color}` | 'link';
active?: boolean;
}

export const Button: FC<ButtonProps> = ({
className,
href,
variant = 'primary',
active,
children,
...props
}) => {
const { disabled, tabIndex } = props,
Class = classNames('btn', variant && `btn-${variant}`, className);

return href ? (
<a
role="button"
className={classNames(Class, { disabled, active })}
tabIndex={disabled ? -1 : tabIndex}
ariaDisabled={disabled + ''}
ariaPressed={active + ''}
{...props}
>
{children}
</a>
) : (
<button className={Class} {...props} ariaPressed={active + ''}>
{children}
</button>
);
};

export function isButton(node: JsxChildren): node is VNode {
const { selector, props } = node as VNode;

return /^(a|input|button)/.test(selector) && props?.className?.btn;
}

export type IconButtonProps = IconProps & ButtonProps;

export const IconButton: FC<IconButtonProps> = ({
className,
name,
...button
}) => (
<Button
className={classNames('p-1', className)}
style={{ lineHeight: '0.8' }}
{...button}
>
<Icon name={name} />
</Button>
);

export const CloseButton: FC<WebCellProps<HTMLButtonElement>> = ({
className = '',
...props
}) => (
<button
className={`btn-close ${className}`}
type="button"
ariaLabel="Close"
{...props}
/>
);
143 changes: 143 additions & 0 deletions source/Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import classNames from 'classnames';
import { VNode } from 'dom-renderer';
import { FC, WebCellProps } from 'web-cell';
import { uniqueID } from 'web-utility';

export type FormGroupProps = WebCellProps<HTMLDivElement>;

export const FormGroup: FC<FormGroupProps> = ({ children, ...props }) => (
<div {...props}>{children}</div>
);

export type FormLabelProps = WebCellProps<HTMLLabelElement>;

export const FormLabel: FC<FormLabelProps> = ({
className = '',
children,
...props
}) => (
<label className={`form-label ${className}`} {...props}>
{children}
</label>
);

export interface FloatingLabelProps extends FormLabelProps {
label: string;
}

export const FloatingLabel: FC<FloatingLabelProps> = ({
className = '',
style,
label,
children,
...props
}) => (
<div className={`form-floating ${className}`} style={style}>
{children}
<label {...props}>{label}</label>
</div>
);

export type FormControlTag = 'input' | 'textarea' | 'select';

export type FormControlProps<T extends FormControlTag> = WebCellProps &
Omit<JSX.IntrinsicElements[T], 'size'> & {
as?: T;
htmlSize?: number;
size?: 'sm' | 'lg';
plaintext?: boolean;
};

export const FormControl = <T extends FormControlTag = 'input'>({
as: Tag,
className = '',
htmlSize,
size,
plaintext,
...props
}: FormControlProps<T>) => (
<Tag
className={classNames(
'form-control',
size && `form-control-${size}`,
(props as FormControlProps<'input'>).readOnly &&
plaintext &&
`form-control-plaintext`,
(props as FormControlProps<'input'>).type === 'color' &&
`form-control-color`,
className
)}
{...props}
size={htmlSize}
/>
);

export interface FormCheckProps extends WebCellProps<HTMLInputElement> {
type: 'radio' | 'checkbox' | 'switch';
inline?: boolean;
reverse?: boolean;
label?: VNode;
}

export const FormCheck: FC<FormCheckProps> = ({
id = uniqueID(),
className = '',
style,
title,
type,
inline,
reverse,
label,
...props
}) => (
<div
className={classNames(
label && 'form-check',
inline && 'form-check-inline',
reverse && 'form-check-reverse',
type === 'switch' && 'form-switch',
className
)}
style={style}
>
<input
className="form-check-input"
type={type === 'switch' ? 'checkbox' : type}
role={type === 'switch' ? 'switch' : undefined}
id={id}
{...props}
/>
{label && (
<label className="form-check-label" htmlFor={id} title={title}>
{label}
</label>
)}
</div>
);

export type FormFieldProps<T extends FormControlTag> = FormGroupProps &
FormLabelProps &
FormControlProps<T> & {
label?: string;
labelFloat?: boolean;
};

export const FormField = <T extends FormControlTag = 'input'>({
className,
label,
labelFloat,
...props
}: FormFieldProps<T>) => {
label ||= props.title || (props as FormControlProps<'input'>).placeholder;

const field = <FormControl {...(props as FormControlProps<T>)} />;

return labelFloat ? (
<FloatingLabel {...{ className, label }}>{field}</FloatingLabel>
) : (
<FormGroup className={className}>
<FormLabel>{label}</FormLabel>
{field}
</FormGroup>
);
};
8 changes: 4 additions & 4 deletions v1/Reminder/Icon.tsx → source/Icon.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { WebCellProps, createCell } from 'web-cell';
import classNames from 'classnames';
import { WebCellProps } from 'web-cell';

import { TextColors } from '../utility/constant';
import { Color } from './type';

export interface IconProps extends WebCellProps {
name: string;
color?: TextColors;
color?: Color;
size?: number;
}

Expand All @@ -15,7 +15,7 @@ export function Icon({
color,
name,
size,
defaultSlot,
children,
...rest
}: IconProps) {
return (
Expand Down
37 changes: 36 additions & 1 deletion source/Nav.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { JsxProps } from 'dom-renderer';
import { FC } from 'web-cell';
import { FC, WebCell, WebCellProps, component } from 'web-cell';

import { OffcanvasNavbar } from './Navbar';

export interface NavLinkProps extends JsxProps<HTMLAnchorElement> {
active?: boolean;
Expand All @@ -20,3 +22,36 @@ export const NavLink: FC<NavLinkProps> = ({
</a>
</li>
);

export interface Nav extends WebCell {}

@component({
tagName: 'bs-nav',
mode: 'open'
})
export class Nav extends HTMLElement {
declare props: WebCellProps;

connectedCallback() {
const navBar = this.closest<OffcanvasNavbar>(
'offcanvas-navbar, .navbar'
);

if (!navBar) return this.classList.add('nav');

const expand =
navBar.expand ||
navBar.className.match(/navbar-expand(-(\S+))?/)?.[2];

this.classList.add(
'navbar-nav',
'align-items-center',
expand && 'flex-column',
expand && `flex-${expand}-row`
);
}

render() {
return <slot />;
}
}
4 changes: 2 additions & 2 deletions source/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { JsxProps, VNode } from 'dom-renderer';
import { observable } from 'mobx';
import { FC, attribute, component, observer } from 'web-cell';
import { FC, WebCellProps, attribute, component, observer } from 'web-cell';
import { uniqueID } from 'web-utility';

import { Container, ContainerProps } from './Grid';
Expand Down Expand Up @@ -38,7 +38,7 @@ export const NavbarToggle: FC<NavbarToggleProps> = ({
</button>
);

export interface NavbarProps extends JsxProps<HTMLDivElement> {
export interface NavbarProps extends WebCellProps {
variant?: 'light' | 'dark';
bg?: BackgroundColor;
expand?: boolean | Size;
Expand Down
Loading

0 comments on commit d6e6ab0

Please sign in to comment.