Skip to content

Commit be76d9e

Browse files
committed
[add] Nav Dropdown component & Offcanvas hiding
[optimize] update Usage section of Read Me document
1 parent 2dc2013 commit be76d9e

File tree

7 files changed

+152
-39
lines changed

7 files changed

+152
-39
lines changed

ReadMe.md

+48-15
Original file line numberDiff line numberDiff line change
@@ -14,37 +14,70 @@
1414

1515
## Usage
1616

17+
### Installation
18+
1719
```shell
18-
npm install boot-cell iterable-observer @nuintun/qrcode
20+
npm install dom-renderer web-cell boot-cell
21+
npm install parcel @parcel/config-default @parcel/transformer-typescript-tsc -D
22+
```
23+
24+
#### `package.json`
25+
26+
```json
27+
{
28+
"scripts": {
29+
"start": "parcel source/index.html --open",
30+
"build": "parcel build source/index.html --public-url ."
31+
}
32+
}
33+
```
34+
35+
#### `tsconfig.json`
36+
37+
```json
38+
{
39+
"compilerOptions": {
40+
"target": "ES6",
41+
"module": "ES2020",
42+
"moduleResolution": "Node",
43+
"useDefineForClassFields": true,
44+
"jsx": "react-jsx",
45+
"jsxImportSource": "dom-renderer"
46+
}
47+
}
1948
```
2049

21-
`index.html`
50+
#### `.parcelrc`
51+
52+
```json
53+
{
54+
"extends": "@parcel/config-default",
55+
"transformers": {
56+
"*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"]
57+
}
58+
}
59+
```
60+
61+
### `source/index.html`
2262

2363
```html
24-
<link
25-
rel="stylesheet"
26-
href="https://unpkg.com/[email protected]/dist/dialog-polyfill.css"
27-
/>
2864
<link
2965
rel="stylesheet"
3066
href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css"
3167
/>
3268
<link
3369
rel="stylesheet"
34-
href="https://unpkg.com/[email protected].2/font/bootstrap-icons.css"
70+
href="https://unpkg.com/[email protected].3/font/bootstrap-icons.css"
3571
/>
3672
<link
3773
rel="stylesheet"
3874
href="https://unpkg.com/@fortawesome/[email protected]/css/all.min.css"
3975
/>
40-
<script
41-
crossorigin
42-
src="https://polyfill.app/api/polyfill?features=es.array.flat,es.object.from-entries,regenerator-runtime,intersection-observer,resize-observer"
43-
></script>
44-
<script src="https://unpkg.com/[email protected]/dist/dialog-polyfill.js"></script>
45-
<script src="https://unpkg.com/[email protected]/dist/share-min.js"></script>
46-
<script src="https://unpkg.com/@webcomponents/[email protected]/custom-elements-es5-adapter.js"></script>
47-
<script src="https://unpkg.com/@webcomponents/[email protected]/webcomponents-bundle.js"></script>
76+
<script src="https://polyfill.web-cell.dev/feature/ECMAScript.js"></script>
77+
<script src="https://polyfill.web-cell.dev/feature/WebComponents.js"></script>
78+
<script src="https://polyfill.web-cell.dev/feature/ElementInternals.js"></script>
79+
<script src="https://polyfill.web-cell.dev/feature/Dialog.js"></script>
80+
<script src="https://polyfill.web-cell.dev/feature/Share.js"></script>
4881
```
4982

5083
## Components

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "boot-cell",
3-
"version": "2.0.0-beta.4",
3+
"version": "2.0.0-beta.6",
44
"license": "LGPL-3.0",
55
"author": "[email protected]",
66
"description": "Web Components UI library based on WebCell v3, BootStrap v5, BootStrap Icon v1 & FontAwesome v6",

source/Button.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface ButtonProps
1717
export const Button: FC<ButtonProps> = ({
1818
className,
1919
href,
20-
variant = 'primary',
20+
variant,
2121
active,
2222
children,
2323
...props
@@ -30,14 +30,14 @@ export const Button: FC<ButtonProps> = ({
3030
role="button"
3131
className={classNames(Class, { disabled, active })}
3232
tabIndex={disabled ? -1 : tabIndex}
33-
ariaDisabled={disabled + ''}
34-
ariaPressed={active + ''}
35-
{...props}
33+
ariaDisabled={disabled?.toString()}
34+
ariaPressed={active?.toString()}
35+
{...{ href, ...props }}
3636
>
3737
{children}
3838
</a>
3939
) : (
40-
<button className={Class} {...props} ariaPressed={active + ''}>
40+
<button className={Class} {...props} ariaPressed={active?.toString()}>
4141
{children}
4242
</button>
4343
);

source/Dropdown.tsx

+19-4
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ export const DropdownItem: FC<WebCellProps<HTMLAnchorElement>> = ({
4646
);
4747

4848
export interface DropdownButtonProps extends WebCellProps, ButtonProps {
49+
boxClass?: string;
50+
buttonClass?: string;
4951
caption: JsxChildren;
5052
}
5153

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

62+
@attribute
63+
@observable
64+
accessor boxClass: string;
65+
66+
@attribute
67+
@observable
68+
accessor buttonClass: string;
69+
6070
@attribute
6171
@observable
6272
accessor variant: ButtonProps['variant'];
@@ -68,18 +78,23 @@ export class DropdownButton extends HTMLElement {
6878
@observable
6979
accessor caption: JsxChildren;
7080

81+
@attribute
82+
@observable
83+
accessor disabled = false;
84+
7185
@attribute
7286
@observable
7387
accessor show = false;
7488

7589
renderContent() {
76-
const { variant, size, caption, show } = this;
90+
const { boxClass, buttonClass, variant, size, caption } = this,
91+
{ disabled, show } = this;
7792

7893
return (
79-
<Dropdown className={classNames({ show })}>
94+
<Dropdown className={classNames(boxClass, { show })}>
8095
<DropdownToggle
81-
className={classNames({ show })}
82-
{...{ variant, size }}
96+
className={classNames(buttonClass, { show })}
97+
{...{ variant, size, disabled }}
8398
onClick={() => (this.show = !show)}
8499
>
85100
{caption}

source/Nav.tsx

+25-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { JsxProps } from 'dom-renderer';
1+
import { JsxChildren } from 'dom-renderer';
22
import { FC, WebCell, WebCellProps, component } from 'web-cell';
33

4+
import { ButtonProps } from './Button';
5+
import { DropdownButton } from './Dropdown';
46
import { OffcanvasNavbar } from './Navbar';
57

6-
export interface NavLinkProps extends JsxProps<HTMLAnchorElement> {
8+
export interface NavLinkProps extends WebCellProps<HTMLAnchorElement> {
79
active?: boolean;
810
}
911

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

23+
export interface NavDropdownProps
24+
extends Omit<NavLinkProps, 'title' | 'type'>,
25+
Pick<ButtonProps, 'disabled' | 'onClick'> {
26+
title: JsxChildren;
27+
}
28+
29+
export const NavDropdown: FC<NavDropdownProps> = ({
30+
title,
31+
children,
32+
...props
33+
}) => (
34+
<DropdownButton
35+
boxClass="nav-item"
36+
buttonClass="nav-link"
37+
caption={title}
38+
{...props}
39+
>
40+
{children}
41+
</DropdownButton>
42+
);
43+
2144
export interface Nav extends WebCell {}
2245

2346
@component({

source/Navbar.tsx

+37-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import { JsxProps, VNode } from 'dom-renderer';
22
import { observable } from 'mobx';
3-
import { FC, WebCellProps, attribute, component, observer } from 'web-cell';
4-
import { uniqueID } from 'web-utility';
3+
import {
4+
FC,
5+
WebCell,
6+
WebCellProps,
7+
attribute,
8+
component,
9+
observer
10+
} from 'web-cell';
11+
import { delegate, uniqueID } from 'web-utility';
512

613
import { Container, ContainerProps } from './Grid';
714
import {
@@ -73,12 +80,14 @@ export interface OffcanvasNavbarProps
7380
brand?: VNode;
7481
}
7582

83+
export interface OffcanvasNavbar extends WebCell {}
84+
7685
@component({
7786
tagName: 'offcanvas-navbar',
7887
mode: 'open'
7988
})
8089
@observer
81-
export class OffcanvasNavbar extends HTMLElement {
90+
export class OffcanvasNavbar extends HTMLElement implements WebCell {
8291
declare props: OffcanvasNavbarProps;
8392

8493
@attribute
@@ -124,6 +133,30 @@ export class OffcanvasNavbar extends HTMLElement {
124133
@observable
125134
accessor closeButton = true;
126135

136+
connectedCallback() {
137+
globalThis.addEventListener?.('keyup', this.close, true);
138+
139+
this.addEventListener('click', this.handleLink);
140+
}
141+
142+
disconnectedCallback() {
143+
globalThis.removeEventListener?.('keyup', this.close, true);
144+
145+
this.addEventListener('click', this.handleLink);
146+
}
147+
148+
close = (event?: KeyboardEvent | MouseEvent) => {
149+
if (
150+
event instanceof KeyboardEvent &&
151+
!['Escape', 'Enter'].includes(event.key)
152+
)
153+
return;
154+
155+
this.open = false;
156+
};
157+
158+
handleLink = delegate('a[href].nav-link', this.close);
159+
127160
renderContent() {
128161
const { variant, bg, expand, fixed, sticky, fluid, brand } = this,
129162
{ title, titleId, offcanvasId, open, closeButton } = this;
@@ -141,6 +174,7 @@ export class OffcanvasNavbar extends HTMLElement {
141174
id={offcanvasId}
142175
aria-labelledby={titleId}
143176
show={open}
177+
onHide={this.close}
144178
>
145179
<OffcanvasHeader
146180
closeButton={closeButton}

source/Offcanvas.tsx

+17-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { JsxProps } from 'dom-renderer';
2-
import { FC } from 'web-cell';
1+
import { FC, WebCellProps } from 'web-cell';
32
import { uniqueID } from 'web-utility';
43

54
import { CloseButton } from './Button';
65

7-
export const OffcanvasTitle: FC<JsxProps<HTMLHeadingElement>> = ({
6+
export const OffcanvasTitle: FC<WebCellProps<HTMLHeadingElement>> = ({
87
className = '',
98
children,
109
...props
@@ -14,7 +13,7 @@ export const OffcanvasTitle: FC<JsxProps<HTMLHeadingElement>> = ({
1413
</h5>
1514
);
1615

17-
export interface OffcanvasHeaderProps extends JsxProps<HTMLDivElement> {
16+
export interface OffcanvasHeaderProps extends WebCellProps<HTMLDivElement> {
1817
closeButton?: boolean;
1918
onHide?: () => any;
2019
}
@@ -33,7 +32,7 @@ export const OffcanvasHeader: FC<OffcanvasHeaderProps> = ({
3332
</div>
3433
);
3534

36-
export const OffcanvasBody: FC<JsxProps<HTMLDivElement>> = ({
35+
export const OffcanvasBody: FC<WebCellProps<HTMLDivElement>> = ({
3736
className = '',
3837
children,
3938
...props
@@ -43,7 +42,8 @@ export const OffcanvasBody: FC<JsxProps<HTMLDivElement>> = ({
4342
</div>
4443
);
4544

46-
export interface OffcanvasProps extends JsxProps<HTMLDivElement> {
45+
export interface OffcanvasProps
46+
extends Omit<OffcanvasHeaderProps, 'closeButton'> {
4747
backdrop?: boolean | 'static';
4848
show?: boolean;
4949
}
@@ -52,6 +52,7 @@ export const Offcanvas: FC<OffcanvasProps> = ({
5252
className = '',
5353
backdrop = true,
5454
show,
55+
onHide,
5556
children,
5657
...props
5758
}) => (
@@ -66,7 +67,8 @@ export const Offcanvas: FC<OffcanvasProps> = ({
6667
>
6768
{children}
6869
</div>
69-
{show && <div className="offcanvas-backdrop show" />}
70+
71+
{show && <div className="offcanvas-backdrop show" onClick={onHide} />}
7072
</>
7173
);
7274

@@ -77,14 +79,20 @@ export interface OffcanvasBoxProps
7779
}
7880

7981
export const OffcanvasBox: FC<OffcanvasBoxProps> = ({
82+
style,
8083
title,
8184
titleId = uniqueID(),
8285
closeButton,
86+
onHide,
8387
children,
8488
...props
8589
}) => (
86-
<Offcanvas {...props} aria-labelledby={titleId}>
87-
<OffcanvasHeader closeButton={closeButton}>
90+
<Offcanvas
91+
style={{ maxWidth: '75vw', ...style }}
92+
{...{ ...props, onHide }}
93+
aria-labelledby={titleId}
94+
>
95+
<OffcanvasHeader {...{ closeButton, onHide }}>
8896
<OffcanvasTitle id={titleId}>{title}</OffcanvasTitle>
8997
</OffcanvasHeader>
9098
<OffcanvasBody>{children}</OffcanvasBody>

0 commit comments

Comments
 (0)