@cloudflare/component-page 4.2.511 → 5.1.0

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/src/Page.tsx CHANGED
@@ -1,13 +1,231 @@
1
- import { createStyledComponent } from '@cloudflare/style-container';
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { useHistory } from 'react-router-dom';
3
+ import {
4
+ createStyledComponent,
5
+ createComponent
6
+ } from '@cloudflare/style-container';
7
+ import { Main, Header, Div } from '@cloudflare/elements';
8
+ import { Label } from '@cloudflare/component-label';
9
+ import { Trans } from '@cloudflare/intl-react';
10
+ import { Heading, Section } from './Heading';
2
11
 
3
- const Page = createStyledComponent(
4
- () => ({
5
- py: 4,
6
- minHeight: 411
12
+ type PageWidth = 'narrow' | 'wide' | 'unbounded';
13
+
14
+ type PageHeaderProps = {
15
+ title?: React.ReactNode;
16
+ subtitle?: React.ReactNode;
17
+ description?: React.ReactNode;
18
+ control?: React.ReactNode;
19
+ children?: React.ReactNode;
20
+ centerHeader?: boolean;
21
+ beta?: boolean;
22
+ };
23
+
24
+ type Props = PageHeaderProps & {
25
+ type?: PageWidth;
26
+ className?: string;
27
+ testId?: string;
28
+ sidebar?: React.ReactNode;
29
+ sidebarPosition?: 'outside' | 'inside' | 'left';
30
+ autofocus?: boolean;
31
+ };
32
+
33
+ const maxWidthByType = {
34
+ narrow: '64em',
35
+ wide: '79em',
36
+ unbounded: '100%'
37
+ };
38
+
39
+ const PageDescription = createComponent(
40
+ ({ theme }) => ({
41
+ fontSize: theme.fontSizes[4],
42
+ marginBottom: theme.space[0],
43
+ lineHeight: 1.25,
44
+ color: theme.colors.gray[3],
45
+ fontWeight: 400
7
46
  }),
8
- 'main'
47
+ 'p'
9
48
  );
10
49
 
50
+ PageDescription.displayName = 'PageDescription';
51
+
52
+ type ControlWrapperProps = {
53
+ control?: React.ReactNode;
54
+ children: React.ReactNode;
55
+ };
56
+
57
+ const ControlWrapper = ({ control, children }: ControlWrapperProps) => {
58
+ return control ? (
59
+ <Div display={['block', 'flex']} justifyContent="space-between">
60
+ {children}
61
+ {control}
62
+ </Div>
63
+ ) : (
64
+ <>{children}</>
65
+ );
66
+ };
67
+
68
+ const maxPageTitles = 2;
69
+
70
+ const PageHeader = ({
71
+ title,
72
+ subtitle,
73
+ description,
74
+ centerHeader,
75
+ control,
76
+ children,
77
+ beta
78
+ }: PageHeaderProps) => {
79
+ const headerVisible = !!(title || subtitle || description);
80
+ const titlesCount = Math.min(
81
+ [title, subtitle, description].filter(Boolean).length,
82
+ 2
83
+ );
84
+
85
+ return (
86
+ <>
87
+ <ControlWrapper control={control}>
88
+ <Header
89
+ mb={headerVisible ? 3 : 0}
90
+ textAlign={centerHeader ? 'center' : undefined}
91
+ >
92
+ {title && (
93
+ <Heading>
94
+ {title}
95
+ {beta && (
96
+ <Label hue="orange" ml={2} verticalAlign="middle">
97
+ <Trans _="Beta" id="common.beta" />
98
+ </Label>
99
+ )}
100
+ </Heading>
101
+ )}
102
+ {subtitle && (
103
+ <Heading level={title ? 2 : 1} offset={title ? 0 : 1}>
104
+ {subtitle}
105
+ </Heading>
106
+ )}
107
+ {description &&
108
+ (subtitle ? (
109
+ <PageDescription>{description}</PageDescription>
110
+ ) : (
111
+ <Heading level={2} fontSize={4} color="gray.3">
112
+ {description}
113
+ </Heading>
114
+ ))}
115
+ </Header>
116
+ </ControlWrapper>
117
+ <Section level={titlesCount} offset={maxPageTitles - titlesCount}>
118
+ {children}
119
+ </Section>
120
+ </>
121
+ );
122
+ };
123
+
124
+ PageHeader.displayName = 'PageHeader';
125
+
126
+ // firstPage is used when dealing with focus. When navigating the dash, focus
127
+ // jumps to the page content, but not when the dash is initially loaded.
128
+ let firstPage = '';
129
+
130
+ // firstLoad is used to ensure focus is handled correctly if the user navigates
131
+ // back to the first page that was loaded.
132
+ let firstLoad = true;
133
+
134
+ // Workaround for a bug where elements don't focus correctly.
135
+ const maxFocusAttempts = 10;
136
+ const focus = (el: HTMLElement | null, attempt: number = 0) => {
137
+ el?.focus();
138
+ if (
139
+ typeof document !== 'undefined' &&
140
+ document.activeElement !== el &&
141
+ attempt < maxFocusAttempts
142
+ ) {
143
+ setTimeout(() => focus(el, attempt + 1), 10);
144
+ }
145
+ };
146
+
147
+ const Page = ({
148
+ title,
149
+ subtitle,
150
+ description,
151
+ centerHeader,
152
+ beta,
153
+ testId,
154
+ className,
155
+ sidebar,
156
+ type = 'wide',
157
+ sidebarPosition = 'inside',
158
+ autofocus = true,
159
+ control,
160
+ children
161
+ }: Props) => {
162
+ const history = useHistory();
163
+ const skipTargetRef = useRef<HTMLAnchorElement>(null);
164
+ const path = history?.location?.pathname;
165
+
166
+ useEffect(() => {
167
+ // If autofocus is enabled, then focus will move to the title block when
168
+ // the page is navigated to (but not when the dash is first loaded)
169
+ if (autofocus) {
170
+ if (!firstPage) {
171
+ firstPage = path;
172
+ } else if (firstPage !== path || !firstLoad) {
173
+ focus(skipTargetRef.current);
174
+ firstLoad = false;
175
+ }
176
+ }
177
+ }, [path]);
178
+
179
+ const sidebarInside = sidebarPosition === 'inside';
180
+
181
+ return (
182
+ <Main
183
+ data-testid={testId}
184
+ className={className}
185
+ display={sidebar && sidebarInside ? undefined : 'flex'}
186
+ py={4}
187
+ >
188
+ <Div
189
+ ml="auto"
190
+ mr={sidebar && !sidebarInside ? 0 : 'auto'}
191
+ display={sidebar ? ['block', 'block', 'flex'] : undefined}
192
+ width={type === 'unbounded' ? '100%' : '90%'}
193
+ maxWidth={maxWidthByType[type] || maxWidthByType.narrow}
194
+ >
195
+ <Div
196
+ width={sidebar && sidebarInside ? [1, 1, 2 / 3] : undefined}
197
+ pr={sidebar && sidebarInside ? [0, 0, 3] : undefined}
198
+ mt={0}
199
+ >
200
+ <a id="skipTarget" ref={skipTargetRef} tabIndex={-1} />
201
+
202
+ <PageHeader
203
+ title={title}
204
+ subtitle={subtitle}
205
+ description={description}
206
+ centerHeader={centerHeader}
207
+ control={control}
208
+ children={children}
209
+ beta={beta}
210
+ />
211
+ </Div>
212
+ {sidebar && sidebarInside && (
213
+ <Div width={[1, 1, 1 / 3]} pl={[0, 0, 3]} pt={[4, 4, 0]}>
214
+ {sidebar}
215
+ </Div>
216
+ )}
217
+ </Div>
218
+ {!sidebarInside && sidebar}
219
+ </Main>
220
+ );
221
+ };
222
+
11
223
  Page.displayName = 'Page';
12
224
 
13
- export default Page;
225
+ export default createStyledComponent(
226
+ () => ({
227
+ py: 4,
228
+ minHeight: 411
229
+ }),
230
+ Page
231
+ );
package/src/index.ts CHANGED
@@ -1,7 +1,4 @@
1
- import PageImport from './Page';
2
- import PageHeader from './PageHeader';
3
- import PageContent from './PageContent';
1
+ import Page from './Page';
2
+ import { Heading, Section } from './Heading';
4
3
 
5
- export const Page = PageImport;
6
-
7
- export { PageHeader, PageContent };
4
+ export { Page, Heading, Section };
@@ -1,10 +0,0 @@
1
- import React from 'react';
2
- declare type PageWidth = 'narrow' | 'wide' | 'unbounded';
3
- declare const _default: React.ComponentType<{
4
- children?: any;
5
- className?: string | undefined;
6
- testId?: string | undefined;
7
- type?: PageWidth | undefined;
8
- innerRef?: React.Ref<never> | undefined;
9
- }>;
10
- export default _default;
@@ -1,8 +0,0 @@
1
- import React from 'react';
2
- declare type Props = {
3
- title: React.ReactNode;
4
- subtitle?: React.ReactNode;
5
- beta?: boolean;
6
- };
7
- declare const PageHeader: React.FC<Props>;
8
- export default PageHeader;
package/es/PageContent.js DELETED
@@ -1,29 +0,0 @@
1
- import React from 'react';
2
- import { createComponent } from '@cloudflare/style-container';
3
- const maxWidthByType = {
4
- narrow: '64em',
5
- wide: '79em',
6
- unbounded: '100%'
7
- };
8
-
9
- const styles = ({
10
- type = 'wide'
11
- }) => ({
12
- marginLeft: 'auto',
13
- marginRight: 'auto',
14
- width: type === 'unbounded' ? '100%' : '90%',
15
- maxWidth: maxWidthByType[type] || maxWidthByType.narrow
16
- });
17
-
18
- const PageContent = ({
19
- className,
20
- children,
21
- testId
22
- }) => {
23
- return /*#__PURE__*/React.createElement("div", {
24
- className: className,
25
- "data-testid": testId
26
- }, children);
27
- };
28
-
29
- export default createComponent(styles, PageContent);
package/es/PageHeader.js DELETED
@@ -1,50 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { createComponent } from '@cloudflare/style-container';
4
- import { Label } from '@cloudflare/component-label';
5
- import { Trans } from '@cloudflare/intl-react';
6
- const PageHeading = createComponent(({
7
- theme
8
- }) => ({
9
- fontSize: theme.fontSizes[6],
10
- marginTop: theme.space[0],
11
- marginBottom: theme.space[0],
12
- lineHeight: 1.25,
13
- fontWeight: 600
14
- }), 'h1');
15
- const PageSubheading = createComponent(({
16
- theme
17
- }) => ({
18
- fontSize: theme.fontSizes[4],
19
- marginTop: theme.space[0],
20
- marginBottom: theme.space[0],
21
- lineHeight: 1.25,
22
- color: theme.colors.gray[3],
23
- fontWeight: 400
24
- }), 'h2');
25
- const Beta = createComponent(({
26
- theme
27
- }) => ({
28
- marginLeft: theme.space[2],
29
- verticalAlign: 'middle'
30
- }), Label);
31
-
32
- const PageHeader = ({
33
- title,
34
- subtitle,
35
- beta
36
- }) => {
37
- return /*#__PURE__*/React.createElement("header", null, /*#__PURE__*/React.createElement(PageHeading, null, title, beta && /*#__PURE__*/React.createElement(Beta, {
38
- hue: "orange"
39
- }, /*#__PURE__*/React.createElement(Trans, {
40
- _: "Beta",
41
- id: "common.beta"
42
- }))), subtitle && /*#__PURE__*/React.createElement(PageSubheading, null, subtitle));
43
- };
44
-
45
- PageHeader.propTypes = {
46
- title: PropTypes.node.isRequired,
47
- subtitle: PropTypes.node,
48
- beta: PropTypes.bool
49
- };
50
- export default PageHeader;
@@ -1,43 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
-
8
- var _react = _interopRequireDefault(require("react"));
9
-
10
- var _styleContainer = require("@cloudflare/style-container");
11
-
12
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
-
14
- var maxWidthByType = {
15
- narrow: '64em',
16
- wide: '79em',
17
- unbounded: '100%'
18
- };
19
-
20
- var styles = function styles(_ref) {
21
- var _ref$type = _ref.type,
22
- type = _ref$type === void 0 ? 'wide' : _ref$type;
23
- return {
24
- marginLeft: 'auto',
25
- marginRight: 'auto',
26
- width: type === 'unbounded' ? '100%' : '90%',
27
- maxWidth: maxWidthByType[type] || maxWidthByType.narrow
28
- };
29
- };
30
-
31
- var PageContent = function PageContent(_ref2) {
32
- var className = _ref2.className,
33
- children = _ref2.children,
34
- testId = _ref2.testId;
35
- return /*#__PURE__*/_react.default.createElement("div", {
36
- className: className,
37
- "data-testid": testId
38
- }, children);
39
- };
40
-
41
- var _default = (0, _styleContainer.createComponent)(styles, PageContent);
42
-
43
- exports.default = _default;
package/lib/PageHeader.js DELETED
@@ -1,67 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
-
8
- var _react = _interopRequireDefault(require("react"));
9
-
10
- var _propTypes = _interopRequireDefault(require("prop-types"));
11
-
12
- var _styleContainer = require("@cloudflare/style-container");
13
-
14
- var _componentLabel = require("@cloudflare/component-label");
15
-
16
- var _intlReact = require("@cloudflare/intl-react");
17
-
18
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19
-
20
- var PageHeading = (0, _styleContainer.createComponent)(function (_ref) {
21
- var theme = _ref.theme;
22
- return {
23
- fontSize: theme.fontSizes[6],
24
- marginTop: theme.space[0],
25
- marginBottom: theme.space[0],
26
- lineHeight: 1.25,
27
- fontWeight: 600
28
- };
29
- }, 'h1');
30
- var PageSubheading = (0, _styleContainer.createComponent)(function (_ref2) {
31
- var theme = _ref2.theme;
32
- return {
33
- fontSize: theme.fontSizes[4],
34
- marginTop: theme.space[0],
35
- marginBottom: theme.space[0],
36
- lineHeight: 1.25,
37
- color: theme.colors.gray[3],
38
- fontWeight: 400
39
- };
40
- }, 'h2');
41
- var Beta = (0, _styleContainer.createComponent)(function (_ref3) {
42
- var theme = _ref3.theme;
43
- return {
44
- marginLeft: theme.space[2],
45
- verticalAlign: 'middle'
46
- };
47
- }, _componentLabel.Label);
48
-
49
- var PageHeader = function PageHeader(_ref4) {
50
- var title = _ref4.title,
51
- subtitle = _ref4.subtitle,
52
- beta = _ref4.beta;
53
- return /*#__PURE__*/_react.default.createElement("header", null, /*#__PURE__*/_react.default.createElement(PageHeading, null, title, beta && /*#__PURE__*/_react.default.createElement(Beta, {
54
- hue: "orange"
55
- }, /*#__PURE__*/_react.default.createElement(_intlReact.Trans, {
56
- _: "Beta",
57
- id: "common.beta"
58
- }))), subtitle && /*#__PURE__*/_react.default.createElement(PageSubheading, null, subtitle));
59
- };
60
-
61
- PageHeader.propTypes = {
62
- title: _propTypes.default.node.isRequired,
63
- subtitle: _propTypes.default.node,
64
- beta: _propTypes.default.bool
65
- };
66
- var _default = PageHeader;
67
- exports.default = _default;
@@ -1,34 +0,0 @@
1
- import React from 'react';
2
- import { createComponent } from '@cloudflare/style-container';
3
-
4
- type PageWidth = 'narrow' | 'wide' | 'unbounded';
5
-
6
- const maxWidthByType = {
7
- narrow: '64em',
8
- wide: '79em',
9
- unbounded: '100%'
10
- };
11
-
12
- const styles = ({ type = 'wide' }: { type?: PageWidth }) => ({
13
- marginLeft: 'auto',
14
- marginRight: 'auto',
15
- width: type === 'unbounded' ? '100%' : '90%',
16
- maxWidth: maxWidthByType[type] || maxWidthByType.narrow
17
- });
18
-
19
- type Props = {
20
- children?: any;
21
- className?: string;
22
- testId?: string;
23
- type?: PageWidth;
24
- };
25
-
26
- const PageContent: React.FC<Props> = ({ className, children, testId }) => {
27
- return (
28
- <div className={className} data-testid={testId}>
29
- {children}
30
- </div>
31
- );
32
- };
33
-
34
- export default createComponent(styles, PageContent);
@@ -1,66 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { createComponent } from '@cloudflare/style-container';
4
- import { Label } from '@cloudflare/component-label';
5
- import { Trans } from '@cloudflare/intl-react';
6
-
7
- const PageHeading = createComponent(
8
- ({ theme }) => ({
9
- fontSize: theme.fontSizes[6],
10
- marginTop: theme.space[0],
11
- marginBottom: theme.space[0],
12
- lineHeight: 1.25,
13
- fontWeight: 600
14
- }),
15
- 'h1'
16
- );
17
-
18
- const PageSubheading = createComponent(
19
- ({ theme }) => ({
20
- fontSize: theme.fontSizes[4],
21
- marginTop: theme.space[0],
22
- marginBottom: theme.space[0],
23
- lineHeight: 1.25,
24
- color: theme.colors.gray[3],
25
- fontWeight: 400
26
- }),
27
- 'h2'
28
- );
29
-
30
- const Beta = createComponent(
31
- ({ theme }) => ({
32
- marginLeft: theme.space[2],
33
- verticalAlign: 'middle'
34
- }),
35
- Label
36
- );
37
-
38
- type Props = {
39
- title: React.ReactNode;
40
- subtitle?: React.ReactNode;
41
- beta?: boolean;
42
- };
43
-
44
- const PageHeader: React.FC<Props> = ({ title, subtitle, beta }) => {
45
- return (
46
- <header>
47
- <PageHeading>
48
- {title}
49
- {beta && (
50
- <Beta hue="orange">
51
- <Trans _="Beta" id="common.beta" />
52
- </Beta>
53
- )}
54
- </PageHeading>
55
- {subtitle && <PageSubheading>{subtitle}</PageSubheading>}
56
- </header>
57
- );
58
- };
59
-
60
- PageHeader.propTypes = {
61
- title: PropTypes.node.isRequired,
62
- subtitle: PropTypes.node,
63
- beta: PropTypes.bool
64
- };
65
-
66
- export default PageHeader;