Skip to content

Commit

Permalink
[add] Nav Dropdown component & Offcanvas hiding
Browse files Browse the repository at this point in the history
[optimize] update Usage section of Read Me document
  • Loading branch information
TechQuery committed Jan 19, 2024
1 parent 2dc2013 commit be76d9e
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 39 deletions.
63 changes: 48 additions & 15 deletions ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,70 @@

## Usage

### Installation

```shell
npm install boot-cell iterable-observer @nuintun/qrcode
npm install dom-renderer web-cell boot-cell
npm install parcel @parcel/config-default @parcel/transformer-typescript-tsc -D
```

#### `package.json`

```json
{
"scripts": {
"start": "parcel source/index.html --open",
"build": "parcel build source/index.html --public-url ."
}
}
```

#### `tsconfig.json`

```json
{
"compilerOptions": {
"target": "ES6",
"module": "ES2020",
"moduleResolution": "Node",
"useDefineForClassFields": true,
"jsx": "react-jsx",
"jsxImportSource": "dom-renderer"
}
}
```

`index.html`
#### `.parcelrc`

```json
{
"extends": "@parcel/config-default",
"transformers": {
"*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"]
}
}
```

### `source/index.html`

```html
<link
rel="stylesheet"
href="https://unpkg.com/[email protected]/dist/dialog-polyfill.css"
/>
<link
rel="stylesheet"
href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css"
/>
<link
rel="stylesheet"
href="https://unpkg.com/[email protected].2/font/bootstrap-icons.css"
href="https://unpkg.com/[email protected].3/font/bootstrap-icons.css"
/>
<link
rel="stylesheet"
href="https://unpkg.com/@fortawesome/[email protected]/css/all.min.css"
/>
<script
crossorigin
src="https://polyfill.app/api/polyfill?features=es.array.flat,es.object.from-entries,regenerator-runtime,intersection-observer,resize-observer"
></script>
<script src="https://unpkg.com/[email protected]/dist/dialog-polyfill.js"></script>
<script src="https://unpkg.com/[email protected]/dist/share-min.js"></script>
<script src="https://unpkg.com/@webcomponents/[email protected]/custom-elements-es5-adapter.js"></script>
<script src="https://unpkg.com/@webcomponents/[email protected]/webcomponents-bundle.js"></script>
<script src="https://polyfill.web-cell.dev/feature/ECMAScript.js"></script>
<script src="https://polyfill.web-cell.dev/feature/WebComponents.js"></script>
<script src="https://polyfill.web-cell.dev/feature/ElementInternals.js"></script>
<script src="https://polyfill.web-cell.dev/feature/Dialog.js"></script>
<script src="https://polyfill.web-cell.dev/feature/Share.js"></script>
```

## Components
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "boot-cell",
"version": "2.0.0-beta.4",
"version": "2.0.0-beta.6",
"license": "LGPL-3.0",
"author": "[email protected]",
"description": "Web Components UI library based on WebCell v3, BootStrap v5, BootStrap Icon v1 & FontAwesome v6",
Expand Down
10 changes: 5 additions & 5 deletions source/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface ButtonProps
export const Button: FC<ButtonProps> = ({
className,
href,
variant = 'primary',
variant,
active,
children,
...props
Expand All @@ -30,14 +30,14 @@ export const Button: FC<ButtonProps> = ({
role="button"
className={classNames(Class, { disabled, active })}
tabIndex={disabled ? -1 : tabIndex}
ariaDisabled={disabled + ''}
ariaPressed={active + ''}
{...props}
ariaDisabled={disabled?.toString()}
ariaPressed={active?.toString()}
{...{ href, ...props }}
>
{children}
</a>
) : (
<button className={Class} {...props} ariaPressed={active + ''}>
<button className={Class} {...props} ariaPressed={active?.toString()}>
{children}
</button>
);
Expand Down
23 changes: 19 additions & 4 deletions source/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export const DropdownItem: FC<WebCellProps<HTMLAnchorElement>> = ({
);

export interface DropdownButtonProps extends WebCellProps, ButtonProps {
boxClass?: string;
buttonClass?: string;
caption: JsxChildren;
}

Expand All @@ -57,6 +59,14 @@ export interface DropdownButtonProps extends WebCellProps, ButtonProps {
export class DropdownButton extends HTMLElement {
declare props: DropdownButtonProps;

@attribute
@observable
accessor boxClass: string;

@attribute
@observable
accessor buttonClass: string;

@attribute
@observable
accessor variant: ButtonProps['variant'];
Expand All @@ -68,18 +78,23 @@ export class DropdownButton extends HTMLElement {
@observable
accessor caption: JsxChildren;

@attribute
@observable
accessor disabled = false;

@attribute
@observable
accessor show = false;

renderContent() {
const { variant, size, caption, show } = this;
const { boxClass, buttonClass, variant, size, caption } = this,
{ disabled, show } = this;

return (
<Dropdown className={classNames({ show })}>
<Dropdown className={classNames(boxClass, { show })}>
<DropdownToggle
className={classNames({ show })}
{...{ variant, size }}
className={classNames(buttonClass, { show })}
{...{ variant, size, disabled }}
onClick={() => (this.show = !show)}
>
{caption}
Expand Down
27 changes: 25 additions & 2 deletions source/Nav.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { JsxProps } from 'dom-renderer';
import { JsxChildren } from 'dom-renderer';
import { FC, WebCell, WebCellProps, component } from 'web-cell';

import { ButtonProps } from './Button';
import { DropdownButton } from './Dropdown';
import { OffcanvasNavbar } from './Navbar';

export interface NavLinkProps extends JsxProps<HTMLAnchorElement> {
export interface NavLinkProps extends WebCellProps<HTMLAnchorElement> {
active?: boolean;
}

Expand All @@ -18,6 +20,27 @@ export const NavLink: FC<NavLinkProps> = ({
</a>
);

export interface NavDropdownProps
extends Omit<NavLinkProps, 'title' | 'type'>,
Pick<ButtonProps, 'disabled' | 'onClick'> {
title: JsxChildren;
}

export const NavDropdown: FC<NavDropdownProps> = ({
title,
children,
...props
}) => (
<DropdownButton
boxClass="nav-item"
buttonClass="nav-link"
caption={title}
{...props}
>
{children}
</DropdownButton>
);

export interface Nav extends WebCell {}

@component({
Expand Down
40 changes: 37 additions & 3 deletions source/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { JsxProps, VNode } from 'dom-renderer';
import { observable } from 'mobx';
import { FC, WebCellProps, attribute, component, observer } from 'web-cell';
import { uniqueID } from 'web-utility';
import {
FC,
WebCell,
WebCellProps,
attribute,
component,
observer
} from 'web-cell';
import { delegate, uniqueID } from 'web-utility';

import { Container, ContainerProps } from './Grid';
import {
Expand Down Expand Up @@ -73,12 +80,14 @@ export interface OffcanvasNavbarProps
brand?: VNode;
}

export interface OffcanvasNavbar extends WebCell {}

@component({
tagName: 'offcanvas-navbar',
mode: 'open'
})
@observer
export class OffcanvasNavbar extends HTMLElement {
export class OffcanvasNavbar extends HTMLElement implements WebCell {
declare props: OffcanvasNavbarProps;

@attribute
Expand Down Expand Up @@ -124,6 +133,30 @@ export class OffcanvasNavbar extends HTMLElement {
@observable
accessor closeButton = true;

connectedCallback() {
globalThis.addEventListener?.('keyup', this.close, true);

this.addEventListener('click', this.handleLink);
}

disconnectedCallback() {
globalThis.removeEventListener?.('keyup', this.close, true);

this.addEventListener('click', this.handleLink);
}

close = (event?: KeyboardEvent | MouseEvent) => {
if (
event instanceof KeyboardEvent &&
!['Escape', 'Enter'].includes(event.key)
)
return;

this.open = false;
};

handleLink = delegate('a[href].nav-link', this.close);

renderContent() {
const { variant, bg, expand, fixed, sticky, fluid, brand } = this,
{ title, titleId, offcanvasId, open, closeButton } = this;
Expand All @@ -141,6 +174,7 @@ export class OffcanvasNavbar extends HTMLElement {
id={offcanvasId}
aria-labelledby={titleId}
show={open}
onHide={this.close}
>
<OffcanvasHeader
closeButton={closeButton}
Expand Down
26 changes: 17 additions & 9 deletions source/Offcanvas.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { JsxProps } from 'dom-renderer';
import { FC } from 'web-cell';
import { FC, WebCellProps } from 'web-cell';
import { uniqueID } from 'web-utility';

import { CloseButton } from './Button';

export const OffcanvasTitle: FC<JsxProps<HTMLHeadingElement>> = ({
export const OffcanvasTitle: FC<WebCellProps<HTMLHeadingElement>> = ({
className = '',
children,
...props
Expand All @@ -14,7 +13,7 @@ export const OffcanvasTitle: FC<JsxProps<HTMLHeadingElement>> = ({
</h5>
);

export interface OffcanvasHeaderProps extends JsxProps<HTMLDivElement> {
export interface OffcanvasHeaderProps extends WebCellProps<HTMLDivElement> {
closeButton?: boolean;
onHide?: () => any;
}
Expand All @@ -33,7 +32,7 @@ export const OffcanvasHeader: FC<OffcanvasHeaderProps> = ({
</div>
);

export const OffcanvasBody: FC<JsxProps<HTMLDivElement>> = ({
export const OffcanvasBody: FC<WebCellProps<HTMLDivElement>> = ({
className = '',
children,
...props
Expand All @@ -43,7 +42,8 @@ export const OffcanvasBody: FC<JsxProps<HTMLDivElement>> = ({
</div>
);

export interface OffcanvasProps extends JsxProps<HTMLDivElement> {
export interface OffcanvasProps
extends Omit<OffcanvasHeaderProps, 'closeButton'> {
backdrop?: boolean | 'static';
show?: boolean;
}
Expand All @@ -52,6 +52,7 @@ export const Offcanvas: FC<OffcanvasProps> = ({
className = '',
backdrop = true,
show,
onHide,
children,
...props
}) => (
Expand All @@ -66,7 +67,8 @@ export const Offcanvas: FC<OffcanvasProps> = ({
>
{children}
</div>
{show && <div className="offcanvas-backdrop show" />}

{show && <div className="offcanvas-backdrop show" onClick={onHide} />}
</>
);

Expand All @@ -77,14 +79,20 @@ export interface OffcanvasBoxProps
}

export const OffcanvasBox: FC<OffcanvasBoxProps> = ({
style,
title,
titleId = uniqueID(),
closeButton,
onHide,
children,
...props
}) => (
<Offcanvas {...props} aria-labelledby={titleId}>
<OffcanvasHeader closeButton={closeButton}>
<Offcanvas
style={{ maxWidth: '75vw', ...style }}
{...{ ...props, onHide }}
aria-labelledby={titleId}
>
<OffcanvasHeader {...{ closeButton, onHide }}>
<OffcanvasTitle id={titleId}>{title}</OffcanvasTitle>
</OffcanvasHeader>
<OffcanvasBody>{children}</OffcanvasBody>
Expand Down

0 comments on commit be76d9e

Please sign in to comment.