@iabbb/bds-react 0.39.0-alpha → 0.39.0-alpha-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{Button → dist/Button}/package.json +1 -1
- package/{CallToAction → dist/CallToAction}/package.json +1 -1
- package/{ErrorSummary → dist/ErrorSummary}/package.json +1 -1
- package/{FieldTextInput → dist/FieldTextInput}/package.json +1 -1
- package/{Pagination → dist/Pagination}/package.json +1 -1
- package/{Typography → dist/Typography}/package.json +1 -1
- package/package.json +7 -2
- package/rollup.config.mjs +92 -0
- package/src/Button/Button.tsx +59 -0
- package/src/Button/index.ts +2 -0
- package/src/CallToAction/CallToAction.tsx +22 -0
- package/src/CallToAction/index.ts +2 -0
- package/src/ErrorSummary/ErrorSummary.tsx +120 -0
- package/src/ErrorSummary/index.ts +2 -0
- package/src/ErrorSummary/utils.ts +42 -0
- package/src/FieldTextInput/FieldTextInput.tsx +63 -0
- package/src/FieldTextInput/index.ts +1 -0
- package/src/Pagination/Pagination.tsx +115 -0
- package/src/Pagination/index.ts +1 -0
- package/src/Typography/Typography.tsx +35 -0
- package/src/Typography/index.ts +1 -0
- package/src/index.ts +6 -0
- package/tsconfig.json +8 -0
- /package/{Button → dist/Button}/Button.d.ts +0 -0
- /package/{Button → dist/Button}/index.cjs +0 -0
- /package/{Button → dist/Button}/index.cjs.map +0 -0
- /package/{Button → dist/Button}/index.d.ts +0 -0
- /package/{Button → dist/Button}/index.mjs +0 -0
- /package/{Button → dist/Button}/index.mjs.map +0 -0
- /package/{CallToAction → dist/CallToAction}/CallToAction.d.ts +0 -0
- /package/{CallToAction → dist/CallToAction}/index.cjs +0 -0
- /package/{CallToAction → dist/CallToAction}/index.cjs.map +0 -0
- /package/{CallToAction → dist/CallToAction}/index.d.ts +0 -0
- /package/{CallToAction → dist/CallToAction}/index.mjs +0 -0
- /package/{CallToAction → dist/CallToAction}/index.mjs.map +0 -0
- /package/{ErrorSummary → dist/ErrorSummary}/ErrorSummary.d.ts +0 -0
- /package/{ErrorSummary → dist/ErrorSummary}/index.cjs +0 -0
- /package/{ErrorSummary → dist/ErrorSummary}/index.cjs.map +0 -0
- /package/{ErrorSummary → dist/ErrorSummary}/index.d.ts +0 -0
- /package/{ErrorSummary → dist/ErrorSummary}/index.mjs +0 -0
- /package/{ErrorSummary → dist/ErrorSummary}/index.mjs.map +0 -0
- /package/{ErrorSummary → dist/ErrorSummary}/utils.d.ts +0 -0
- /package/{FieldTextInput → dist/FieldTextInput}/FieldTextInput.d.ts +0 -0
- /package/{FieldTextInput → dist/FieldTextInput}/index.cjs +0 -0
- /package/{FieldTextInput → dist/FieldTextInput}/index.cjs.map +0 -0
- /package/{FieldTextInput → dist/FieldTextInput}/index.d.ts +0 -0
- /package/{FieldTextInput → dist/FieldTextInput}/index.mjs +0 -0
- /package/{FieldTextInput → dist/FieldTextInput}/index.mjs.map +0 -0
- /package/{Pagination → dist/Pagination}/Pagination.d.ts +0 -0
- /package/{Pagination → dist/Pagination}/index.cjs +0 -0
- /package/{Pagination → dist/Pagination}/index.cjs.map +0 -0
- /package/{Pagination → dist/Pagination}/index.d.ts +0 -0
- /package/{Pagination → dist/Pagination}/index.mjs +0 -0
- /package/{Pagination → dist/Pagination}/index.mjs.map +0 -0
- /package/{Typography → dist/Typography}/Typography.d.ts +0 -0
- /package/{Typography → dist/Typography}/index.cjs +0 -0
- /package/{Typography → dist/Typography}/index.cjs.map +0 -0
- /package/{Typography → dist/Typography}/index.d.ts +0 -0
- /package/{Typography → dist/Typography}/index.mjs +0 -0
- /package/{Typography → dist/Typography}/index.mjs.map +0 -0
- /package/{index.cjs → dist/index.cjs} +0 -0
- /package/{index.cjs.map → dist/index.cjs.map} +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{index.mjs → dist/index.mjs} +0 -0
- /package/{index.mjs.map → dist/index.mjs.map} +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iabbb/bds-react",
|
|
3
|
-
"version": "0.39.0-alpha",
|
|
4
|
-
"main": "index.js",
|
|
3
|
+
"version": "0.39.0-alpha-1",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"exports": {
|
|
6
|
+
"./*": "./dist/*",
|
|
7
|
+
"./package.json": "./package.json",
|
|
8
|
+
"./README.md": "./README.md"
|
|
9
|
+
},
|
|
5
10
|
"scripts": {
|
|
6
11
|
"build": "cross-env NODE_ENV=production rollup -c",
|
|
7
12
|
"dev": "rollup -c -w"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import babel from '@rollup/plugin-babel';
|
|
2
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
3
|
+
import resolve from '@rollup/plugin-node-resolve';
|
|
4
|
+
import typescript from '@rollup/plugin-typescript';
|
|
5
|
+
import { globSync } from 'glob';
|
|
6
|
+
import del from 'rollup-plugin-delete';
|
|
7
|
+
import generatePackageJson from 'rollup-plugin-generate-package-json';
|
|
8
|
+
import external from 'rollup-plugin-peer-deps-external';
|
|
9
|
+
import summary from 'rollup-plugin-summary';
|
|
10
|
+
|
|
11
|
+
import packageJson from './package.json' assert { type: 'json' };
|
|
12
|
+
|
|
13
|
+
const plugins = [
|
|
14
|
+
external(),
|
|
15
|
+
resolve({
|
|
16
|
+
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
|
17
|
+
}),
|
|
18
|
+
babel({
|
|
19
|
+
babelHelpers: 'bundled',
|
|
20
|
+
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
|
21
|
+
presets: ['@babel/env', '@babel/preset-react', '@babel/preset-typescript'],
|
|
22
|
+
}),
|
|
23
|
+
commonjs(),
|
|
24
|
+
summary(),
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const getFolders = (dir) => {
|
|
28
|
+
return globSync(`${dir}/*/`).map((x) => x.replace('src\\', ''));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const folderBuilds = getFolders('./src').map((folder) => {
|
|
32
|
+
return {
|
|
33
|
+
input: [`src/${folder}/index.ts`],
|
|
34
|
+
output: [
|
|
35
|
+
{
|
|
36
|
+
file: `dist/${folder}/index.mjs`,
|
|
37
|
+
format: 'esm',
|
|
38
|
+
sourcemap: true,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
file: `dist/${folder}/index.cjs`,
|
|
42
|
+
format: 'cjs',
|
|
43
|
+
sourcemap: true,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
plugins: [
|
|
47
|
+
...plugins,
|
|
48
|
+
generatePackageJson({
|
|
49
|
+
baseContents: {
|
|
50
|
+
name: `${packageJson.name}/${folder}`,
|
|
51
|
+
private: true,
|
|
52
|
+
main: './index.cjs', // --> points to cjs format entry point of individual component
|
|
53
|
+
module: './index.mjs', // --> points to esm format entry point of individual component
|
|
54
|
+
types: './index.d.ts', // --> points to types definition file of individual component
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
],
|
|
58
|
+
external: ['react'],
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export default [
|
|
63
|
+
{
|
|
64
|
+
input: ['src/index.ts'],
|
|
65
|
+
output: [
|
|
66
|
+
{
|
|
67
|
+
file: 'dist/index.mjs',
|
|
68
|
+
format: 'esm',
|
|
69
|
+
sourcemap: true,
|
|
70
|
+
exports: 'named',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
file: 'dist/index.cjs',
|
|
74
|
+
format: 'cjs',
|
|
75
|
+
sourcemap: true,
|
|
76
|
+
exports: 'named',
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
plugins: [
|
|
80
|
+
del({ targets: 'dist/*' }),
|
|
81
|
+
...plugins,
|
|
82
|
+
typescript({
|
|
83
|
+
declaration: true,
|
|
84
|
+
declarationDir: `./dist`,
|
|
85
|
+
emitDeclarationOnly: true,
|
|
86
|
+
noForceEmit: true,
|
|
87
|
+
}),
|
|
88
|
+
],
|
|
89
|
+
external: ['react'],
|
|
90
|
+
},
|
|
91
|
+
...folderBuilds,
|
|
92
|
+
];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
const DEBOUNCE_TIMEOUT_IN_SECONDS = 1;
|
|
4
|
+
|
|
5
|
+
export interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> {
|
|
6
|
+
preventDoubleClick?: boolean;
|
|
7
|
+
variant?: 'cancel' | 'featured' | 'quote' | 'search' | 'standard' | 'unstyled';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
11
|
+
({ children, className, onClick, preventDoubleClick = false, variant = 'standard', ...props }, ref) => {
|
|
12
|
+
const debounceClicks = React.useRef(false);
|
|
13
|
+
|
|
14
|
+
const handleClick: React.MouseEventHandler<HTMLButtonElement> = (event) => {
|
|
15
|
+
// 👇 button is not configured to ignore double clicks
|
|
16
|
+
if (!preventDoubleClick) {
|
|
17
|
+
if (onClick) {
|
|
18
|
+
onClick(event);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 👇 button has been clicked recently, and subsequent clicks are prevented
|
|
25
|
+
if (debounceClicks.current) {
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (onClick) {
|
|
31
|
+
onClick(event);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 👇 block from double clicks
|
|
35
|
+
debounceClicks.current = true;
|
|
36
|
+
|
|
37
|
+
// 👇 and remove the block after a given amount of seconds
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
debounceClicks.current = false;
|
|
40
|
+
}, DEBOUNCE_TIMEOUT_IN_SECONDS * 1000);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<button
|
|
45
|
+
className={[variant === 'unstyled' ? 'bds-button-unstyled' : 'bds-button', className]
|
|
46
|
+
.filter((x) => x)
|
|
47
|
+
.join(' ')}
|
|
48
|
+
data-type={variant !== 'standard' && variant !== 'unstyled' ? variant : null}
|
|
49
|
+
onClick={handleClick}
|
|
50
|
+
ref={ref}
|
|
51
|
+
{...props}
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</button>
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
export default Button;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface CallToActionProps extends React.ComponentPropsWithoutRef<'a'> {
|
|
4
|
+
variant?: 'featured' | 'quote' | 'standard';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const CallToAction = React.forwardRef<HTMLAnchorElement, CallToActionProps>(
|
|
8
|
+
({ children, className, variant = 'standard', ...props }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<a
|
|
11
|
+
className={['bds-cta', className].filter((x) => x).join(' ')}
|
|
12
|
+
data-type={variant !== 'standard' ? variant : null}
|
|
13
|
+
ref={ref}
|
|
14
|
+
{...props}
|
|
15
|
+
>
|
|
16
|
+
{children}
|
|
17
|
+
</a>
|
|
18
|
+
);
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export default CallToAction;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { getAssociatedLegendOrLabel, getFragmentFromUrl } from './utils';
|
|
4
|
+
|
|
5
|
+
declare global {
|
|
6
|
+
namespace JSX {
|
|
7
|
+
interface IntrinsicElements {
|
|
8
|
+
'bds-error-summary': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const FormErrorKey = '_form';
|
|
14
|
+
|
|
15
|
+
const FINAL_FORM_ERROR = 'FINAL_FORM/form-error';
|
|
16
|
+
|
|
17
|
+
export type ErrorSummaryProps = {
|
|
18
|
+
errors: Record<string, Array<string> | string> | null;
|
|
19
|
+
mapNameToId?: (name: string) => string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default function BdsErrorSummary({
|
|
23
|
+
className,
|
|
24
|
+
errors,
|
|
25
|
+
mapNameToId = (name) => name,
|
|
26
|
+
...props
|
|
27
|
+
}: ErrorSummaryProps & React.ComponentPropsWithoutRef<'div'>) {
|
|
28
|
+
const headingId = React.useId();
|
|
29
|
+
const groupRef = React.useRef<HTMLElement>(null);
|
|
30
|
+
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
if (!errors || Object.keys(errors).length === 0) return;
|
|
33
|
+
if (!groupRef.current) return;
|
|
34
|
+
|
|
35
|
+
groupRef.current.focus();
|
|
36
|
+
}, [errors]);
|
|
37
|
+
|
|
38
|
+
if (!errors || Object.keys(errors).length === 0) return null;
|
|
39
|
+
|
|
40
|
+
const handleLinkClick = (e) => {
|
|
41
|
+
const inputId = getFragmentFromUrl(e.currentTarget.href);
|
|
42
|
+
|
|
43
|
+
if (!inputId) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const input = document.getElementById(inputId);
|
|
48
|
+
|
|
49
|
+
if (!input) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const legendOrLabel = getAssociatedLegendOrLabel(input);
|
|
54
|
+
|
|
55
|
+
if (!legendOrLabel) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
|
|
61
|
+
legendOrLabel.scrollIntoView();
|
|
62
|
+
input.focus({ preventScroll: true });
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<bds-error-summary
|
|
67
|
+
className={['stack', className].filter((x) => x).join(' ')}
|
|
68
|
+
role="group"
|
|
69
|
+
aria-labelledby={headingId}
|
|
70
|
+
ref={groupRef}
|
|
71
|
+
tabIndex={-1}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
<h2 className="bds-h5" id={headingId}>
|
|
75
|
+
<svg
|
|
76
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
77
|
+
viewBox="0 0 512 512"
|
|
78
|
+
aria-hidden="true"
|
|
79
|
+
height="1em"
|
|
80
|
+
width="1em"
|
|
81
|
+
fill="currentColor"
|
|
82
|
+
>
|
|
83
|
+
<path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7.2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8.2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24v112c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224c0-17.7-14.3-32-32-32s-32 14.3-32 32 14.3 32 32 32 32-14.3 32-32z" />
|
|
84
|
+
</svg>
|
|
85
|
+
Issue
|
|
86
|
+
</h2>
|
|
87
|
+
<ul>
|
|
88
|
+
{Object.keys(errors).map((errorKey) => {
|
|
89
|
+
const message = errors[errorKey];
|
|
90
|
+
const isFormError = [FINAL_FORM_ERROR, FormErrorKey].includes(errorKey);
|
|
91
|
+
|
|
92
|
+
if (isFormError) {
|
|
93
|
+
return <li key={errorKey} dangerouslySetInnerHTML={{ __html: message }} />;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const isArrayField = Array.isArray(message);
|
|
97
|
+
|
|
98
|
+
const messages = isArrayField ? message : [message];
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<React.Fragment key={errorKey}>
|
|
102
|
+
{messages.map((fieldMessage, index) => {
|
|
103
|
+
const inputId = `${mapNameToId(errorKey)}${isArrayField ? `[${index}]` : ''}`;
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<li key={inputId}>
|
|
107
|
+
<a href={`#${inputId}`} onClick={handleLinkClick}>
|
|
108
|
+
{fieldMessage}
|
|
109
|
+
{messages.length > 1 ? ` (${index + 1} of ${messages.length})` : undefined}
|
|
110
|
+
</a>
|
|
111
|
+
</li>
|
|
112
|
+
);
|
|
113
|
+
})}
|
|
114
|
+
</React.Fragment>
|
|
115
|
+
);
|
|
116
|
+
})}
|
|
117
|
+
</ul>
|
|
118
|
+
</bds-error-summary>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function getFragmentFromUrl(url: string) {
|
|
2
|
+
return url.includes('#') ? url.split('#').pop() : undefined;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function getAssociatedLegendOrLabel(input: HTMLElement) {
|
|
6
|
+
const fieldset = input.closest('fieldset');
|
|
7
|
+
|
|
8
|
+
if (fieldset) {
|
|
9
|
+
const legends = fieldset.getElementsByTagName('legend');
|
|
10
|
+
|
|
11
|
+
if (legends.length) {
|
|
12
|
+
const candidateLegend = legends[0];
|
|
13
|
+
|
|
14
|
+
// If the input type is radio or checkbox, always use the legend if there
|
|
15
|
+
// is one.
|
|
16
|
+
if (input instanceof HTMLInputElement && (input.type === 'checkbox' || input.type === 'radio')) {
|
|
17
|
+
return candidateLegend;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// For other input types, only scroll to the fieldset’s legend (instead of
|
|
21
|
+
// the label associated with the input) if the input would end up in the
|
|
22
|
+
// top half of the screen.
|
|
23
|
+
//
|
|
24
|
+
// This should avoid situations where the input either ends up off the
|
|
25
|
+
// screen, or obscured by a software keyboard.
|
|
26
|
+
const legendTop = candidateLegend.getBoundingClientRect().top;
|
|
27
|
+
const inputRect = input.getBoundingClientRect();
|
|
28
|
+
|
|
29
|
+
// If the browser doesn't support Element.getBoundingClientRect().height
|
|
30
|
+
// or window.innerHeight (like IE8), bail and just link to the label.
|
|
31
|
+
if (inputRect.height && window.innerHeight) {
|
|
32
|
+
const inputBottom = inputRect.top + inputRect.height;
|
|
33
|
+
|
|
34
|
+
if (inputBottom - legendTop < window.innerHeight / 2) {
|
|
35
|
+
return candidateLegend;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return document.querySelector(`label[for='${input.getAttribute('id')}']`) ?? input.closest('label');
|
|
42
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type FieldTextInputProps<C> = {
|
|
4
|
+
as?: C;
|
|
5
|
+
error?: string;
|
|
6
|
+
hint?: string;
|
|
7
|
+
id?: string;
|
|
8
|
+
isOptional?: boolean;
|
|
9
|
+
label: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default function FieldTextInput<C extends React.ElementType>({
|
|
13
|
+
as,
|
|
14
|
+
error,
|
|
15
|
+
hint,
|
|
16
|
+
id,
|
|
17
|
+
isOptional = false,
|
|
18
|
+
label,
|
|
19
|
+
...props
|
|
20
|
+
}: FieldTextInputProps<C> & React.ComponentPropsWithoutRef<C>) {
|
|
21
|
+
id = id ?? props.name;
|
|
22
|
+
|
|
23
|
+
const errorId = React.useId();
|
|
24
|
+
const hintId = React.useId();
|
|
25
|
+
|
|
26
|
+
const InputComponent = as ?? 'input';
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="bds-text-input stack">
|
|
30
|
+
<label htmlFor={id}>
|
|
31
|
+
{label}
|
|
32
|
+
{isOptional && ' (optional)'}
|
|
33
|
+
</label>
|
|
34
|
+
{hint && (
|
|
35
|
+
<span className="bds-hint" id={hintId}>
|
|
36
|
+
{hint}
|
|
37
|
+
</span>
|
|
38
|
+
)}
|
|
39
|
+
{error && (
|
|
40
|
+
<span className="bds-error" id={errorId}>
|
|
41
|
+
<svg
|
|
42
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
43
|
+
viewBox="0 0 512 512"
|
|
44
|
+
aria-hidden="true"
|
|
45
|
+
height="1em"
|
|
46
|
+
width="1em"
|
|
47
|
+
fill="currentColor"
|
|
48
|
+
>
|
|
49
|
+
<path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7.2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8.2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24v112c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224c0-17.7-14.3-32-32-32s-32 14.3-32 32 14.3 32 32 32 32-14.3 32-32z" />
|
|
50
|
+
</svg>
|
|
51
|
+
{error}
|
|
52
|
+
</span>
|
|
53
|
+
)}
|
|
54
|
+
<InputComponent
|
|
55
|
+
aria-invalid={error ? true : undefined}
|
|
56
|
+
aria-describedby={error && hint ? `${hintId} ${errorId}` : error ? errorId : hint ? hintId : undefined}
|
|
57
|
+
aria-required={isOptional ? undefined : true}
|
|
58
|
+
id={id}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './FieldTextInput';
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type PaginationProps = {
|
|
4
|
+
buildPageUrl: (page: number) => string;
|
|
5
|
+
currentPage: number;
|
|
6
|
+
onPageClick?: (page: number) => void;
|
|
7
|
+
totalPages: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function usePages(currentPage: number, totalPages: number) {
|
|
11
|
+
const pages = [1, currentPage - 1, currentPage, currentPage + 1, totalPages].filter(
|
|
12
|
+
(x) => x >= 1 && x <= totalPages,
|
|
13
|
+
);
|
|
14
|
+
return [...new Set(pages)];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function Pagination({
|
|
18
|
+
buildPageUrl,
|
|
19
|
+
className,
|
|
20
|
+
currentPage,
|
|
21
|
+
onPageClick,
|
|
22
|
+
totalPages,
|
|
23
|
+
...props
|
|
24
|
+
}: PaginationProps & React.ComponentPropsWithoutRef<'nav'>) {
|
|
25
|
+
const pages = usePages(currentPage, totalPages);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<nav aria-label="pagination" className={['bds-pagination', className].filter((x) => x).join(' ')} {...props}>
|
|
29
|
+
{currentPage !== 1 && (
|
|
30
|
+
<>
|
|
31
|
+
<a href={buildPageUrl(1)} className="bds-first-page">
|
|
32
|
+
<svg
|
|
33
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
34
|
+
aria-hidden="true"
|
|
35
|
+
focusable="false"
|
|
36
|
+
height="1em"
|
|
37
|
+
width="100%"
|
|
38
|
+
viewBox="0 63.95 512 384.1"
|
|
39
|
+
>
|
|
40
|
+
<path d="M459.5 440.6c9.5 7.9 22.8 9.7 34.1 4.4s18.4-16.6 18.4-29V96c0-12.4-7.2-23.7-18.4-29s-24.5-3.6-34.1 4.4L288 214.3v83.4l171.5 142.9zM256 352V96c0-12.4-7.2-23.7-18.4-29s-24.5-3.6-34.1 4.4l-192 160C4.2 237.5 0 246.5 0 256s4.2 18.5 11.5 24.6l192 160c9.5 7.9 22.8 9.7 34.1 4.4s18.4-16.6 18.4-29v-64z" />
|
|
41
|
+
</svg>
|
|
42
|
+
First
|
|
43
|
+
</a>
|
|
44
|
+
<a aria-label="previous" href={buildPageUrl(currentPage - 1)} rel="prev">
|
|
45
|
+
<svg
|
|
46
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
47
|
+
aria-hidden="true"
|
|
48
|
+
focusable="false"
|
|
49
|
+
width="100%"
|
|
50
|
+
height="1em"
|
|
51
|
+
viewBox="0.02 95.9 192.08 320.17"
|
|
52
|
+
>
|
|
53
|
+
<path d="M9.4 278.6c-12.5-12.5-12.5-32.8 0-45.3l128-128c9.2-9.2 22.9-11.9 34.9-6.9s19.8 16.6 19.8 29.6v256c0 12.9-7.8 24.6-19.8 29.6s-25.7 2.2-34.9-6.9l-128-128z" />
|
|
54
|
+
</svg>
|
|
55
|
+
Prev.
|
|
56
|
+
</a>
|
|
57
|
+
</>
|
|
58
|
+
)}
|
|
59
|
+
<ul>
|
|
60
|
+
{pages.map((page, index) => {
|
|
61
|
+
const handlePageClick = () => {
|
|
62
|
+
if (!onPageClick) return;
|
|
63
|
+
|
|
64
|
+
onPageClick(page);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<React.Fragment key={page}>
|
|
69
|
+
<li>
|
|
70
|
+
<a
|
|
71
|
+
aria-current={page === currentPage ? 'page' : undefined}
|
|
72
|
+
href={buildPageUrl(page)}
|
|
73
|
+
onClick={handlePageClick}
|
|
74
|
+
>
|
|
75
|
+
<span className="visually-hidden">Page</span> {page}
|
|
76
|
+
</a>
|
|
77
|
+
</li>
|
|
78
|
+
{pages[index + 1] > page + 1 ? <li data-overflow="">...</li> : null}
|
|
79
|
+
</React.Fragment>
|
|
80
|
+
);
|
|
81
|
+
})}
|
|
82
|
+
</ul>
|
|
83
|
+
{currentPage !== totalPages && (
|
|
84
|
+
<>
|
|
85
|
+
<a href={buildPageUrl(currentPage + 1)} rel="next">
|
|
86
|
+
Next
|
|
87
|
+
<svg
|
|
88
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
89
|
+
aria-hidden="true"
|
|
90
|
+
focusable="false"
|
|
91
|
+
viewBox="63.9 95.9 192.1 320.17"
|
|
92
|
+
width="100%"
|
|
93
|
+
height="1em"
|
|
94
|
+
>
|
|
95
|
+
<path d="M246.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-9.2-9.2-22.9-11.9-34.9-6.9S63.9 115 63.9 128v256c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l128-128z" />
|
|
96
|
+
</svg>
|
|
97
|
+
</a>
|
|
98
|
+
<a href={buildPageUrl(totalPages)} className="bds-last-page">
|
|
99
|
+
Last
|
|
100
|
+
<svg
|
|
101
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
102
|
+
aria-hidden="true"
|
|
103
|
+
focusable="false"
|
|
104
|
+
width="100%"
|
|
105
|
+
height="1em"
|
|
106
|
+
viewBox="0 63.95 512 384.1"
|
|
107
|
+
>
|
|
108
|
+
<path d="M52.5 440.6c-9.5 7.9-22.8 9.7-34.1 4.4S0 428.4 0 416V96c0-12.4 7.2-23.7 18.4-29s24.5-3.6 34.1 4.4L224 214.3v83.4L52.5 440.6zM256 352V96c0-12.4 7.2-23.7 18.4-29s24.5-3.6 34.1 4.4l192 160c7.3 6.1 11.5 15.1 11.5 24.6s-4.2 18.5-11.5 24.6l-192 160c-9.5 7.9-22.8 9.7-34.1 4.4S256 428.4 256 416v-64z" />
|
|
109
|
+
</svg>
|
|
110
|
+
</a>
|
|
111
|
+
</>
|
|
112
|
+
)}
|
|
113
|
+
</nav>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Pagination';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
const componentMap = {
|
|
4
|
+
h1: 'h1',
|
|
5
|
+
h2: 'h2',
|
|
6
|
+
h3: 'h3',
|
|
7
|
+
h4: 'h4',
|
|
8
|
+
h5: 'h5',
|
|
9
|
+
body: 'p',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const classMap = {
|
|
13
|
+
h1: 'bds-h1',
|
|
14
|
+
h2: 'bds-h2',
|
|
15
|
+
h3: 'bds-h3',
|
|
16
|
+
h4: 'bds-h4',
|
|
17
|
+
h5: 'bds-h5',
|
|
18
|
+
body: 'bds-body',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type TypographyProps = {
|
|
22
|
+
component?: React.ElementType;
|
|
23
|
+
variant?: 'body' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5';
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const Typography = React.forwardRef<HTMLOrSVGElement, React.HTMLAttributes<HTMLOrSVGElement> & TypographyProps>(
|
|
27
|
+
({ className, component, variant = 'body', ...props }, ref) => {
|
|
28
|
+
const Component = component ?? (componentMap[variant] as React.ElementType);
|
|
29
|
+
return (
|
|
30
|
+
<Component className={[classMap[variant], className].filter((x) => x).join(' ')} ref={ref} {...props} />
|
|
31
|
+
);
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export default Typography;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Typography';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as Button } from './Button';
|
|
2
|
+
export { default as CallToAction } from './CallToAction';
|
|
3
|
+
export { default as ErrorSummary } from './ErrorSummary';
|
|
4
|
+
export { default as FieldTextInput } from './FieldTextInput';
|
|
5
|
+
export { default as Pagination } from './Pagination';
|
|
6
|
+
export { default as Typography } from './Typography';
|
package/tsconfig.json
ADDED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|