@ndlib/component-library 0.0.15 → 0.0.17
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/dist/components/elements/BrandingBar/index.d.ts +2 -1
- package/dist/components/elements/Button/Button.stories.d.ts +2 -0
- package/dist/components/elements/Button/Button.stories.js +7 -0
- package/dist/components/elements/Button/Button.test.js +10 -0
- package/dist/components/elements/Button/index.d.ts +2 -0
- package/dist/components/elements/Button/index.js +39 -11
- package/dist/components/elements/List/index.js +3 -3
- package/dist/components/elements/Markdown/Markdown.stories.d.ts +3 -0
- package/dist/components/elements/Markdown/Markdown.stories.js +36 -1
- package/dist/components/elements/Markdown/Markdown.test.js +31 -1
- package/dist/components/elements/Markdown/index.d.ts +13 -0
- package/dist/components/elements/Markdown/index.js +30 -5
- package/dist/components/elements/Spinner/Spinner.stories.d.ts +7 -0
- package/dist/components/elements/Spinner/Spinner.stories.js +29 -0
- package/dist/components/elements/Spinner/Spinner.test.d.ts +1 -0
- package/dist/components/elements/Spinner/Spinner.test.js +9 -0
- package/dist/components/elements/Spinner/index.d.ts +14 -0
- package/dist/components/elements/Spinner/index.js +50 -0
- package/dist/utils/hooks/useStorage.js +7 -1
- package/package.json +6 -3
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
export declare const BrandingBar: () => import("react").JSX.Element;
|
|
@@ -7,6 +7,7 @@ import { GROUP_TYPE, Group } from '../Group';
|
|
|
7
7
|
import { HEADING_SIZE, Heading } from '../text/Heading';
|
|
8
8
|
import { Column } from '../layout/Column';
|
|
9
9
|
import { COLOR } from '../../../theme/colors';
|
|
10
|
+
import { Row } from '../layout/Row';
|
|
10
11
|
const meta = {
|
|
11
12
|
title: 'Elements/Button',
|
|
12
13
|
component: Button,
|
|
@@ -70,3 +71,9 @@ export const RightIcon = {
|
|
|
70
71
|
children: 'Select',
|
|
71
72
|
},
|
|
72
73
|
};
|
|
74
|
+
export const Loading = {
|
|
75
|
+
render: () => (_jsxs(Column, { children: [_jsx(Row, { children: _jsx(Button, Object.assign({ loading: true }, { children: "Click Me" })) }), _jsx(Row, Object.assign({ sx: { mt: 1 } }, { children: _jsx(Button, Object.assign({ type: BUTTON_TYPE.OUTLINE, loading: true }, { children: "Click Me" })) })), _jsx(Row, Object.assign({ sx: { mt: 1 } }, { children: _jsx(Button, Object.assign({ type: BUTTON_TYPE.TEXT, loading: true }, { children: "Click Me" })) }))] })),
|
|
76
|
+
};
|
|
77
|
+
export const Disabled = {
|
|
78
|
+
render: () => (_jsxs(Column, { children: [_jsx(Row, { children: _jsx(Button, Object.assign({ disabled: true }, { children: "Click Me" })) }), _jsx(Row, Object.assign({ sx: { mt: 1 } }, { children: _jsx(Button, Object.assign({ type: BUTTON_TYPE.OUTLINE, disabled: true }, { children: "Click Me" })) })), _jsx(Row, Object.assign({ sx: { mt: 1 } }, { children: _jsx(Button, Object.assign({ type: BUTTON_TYPE.TEXT, disabled: true }, { children: "Click Me" })) }))] })),
|
|
79
|
+
};
|
|
@@ -26,4 +26,14 @@ describe('Button', () => {
|
|
|
26
26
|
fireEvent.click(getByRole('button'));
|
|
27
27
|
expect(mockClickHandler).toHaveBeenCalledTimes(1);
|
|
28
28
|
});
|
|
29
|
+
it('does not respond to click handler when disabled', () => {
|
|
30
|
+
const { getByRole } = render(_jsx(Button, { onClick: mockClickHandler, disabled: true }));
|
|
31
|
+
fireEvent.click(getByRole('button'));
|
|
32
|
+
expect(mockClickHandler).not.toHaveBeenCalled();
|
|
33
|
+
});
|
|
34
|
+
it('does not respond to click handler when loading', () => {
|
|
35
|
+
const { getByRole } = render(_jsx(Button, { onClick: mockClickHandler, loading: true }));
|
|
36
|
+
fireEvent.click(getByRole('button'));
|
|
37
|
+
expect(mockClickHandler).not.toHaveBeenCalled();
|
|
38
|
+
});
|
|
29
39
|
});
|
|
@@ -22,4 +22,6 @@ export declare const Button: React.ForwardRefExoticComponent<{
|
|
|
22
22
|
leftIcon?: React.FC<any> | undefined;
|
|
23
23
|
rightIcon?: React.FC<any> | undefined;
|
|
24
24
|
accessibleLabel?: string | undefined;
|
|
25
|
+
loading?: boolean | undefined;
|
|
26
|
+
disabled?: boolean | undefined;
|
|
25
27
|
} & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -17,6 +17,7 @@ import { COLOR, colors } from '../../../theme/colors';
|
|
|
17
17
|
import { TYPOGRAPHY_TYPE, getIconSize, getTypographyStyles, } from '../../../theme/typography';
|
|
18
18
|
import { Icon } from '../Icon';
|
|
19
19
|
import { useEnvironment } from '../../providers/env';
|
|
20
|
+
import { SPINNER_SIZE, Spinner } from '../Spinner';
|
|
20
21
|
export var BUTTON_SIZE;
|
|
21
22
|
(function (BUTTON_SIZE) {
|
|
22
23
|
BUTTON_SIZE["SM"] = "sm";
|
|
@@ -51,7 +52,8 @@ export var BUTTON_TYPE;
|
|
|
51
52
|
// ARROW
|
|
52
53
|
})(BUTTON_TYPE || (BUTTON_TYPE = {}));
|
|
53
54
|
export const Button = React.forwardRef((_a, ref) => {
|
|
54
|
-
var { size: sizeParam, type: typeParam, color, primaryIcon, leftIcon, rightIcon, children, sx } = _a, rest = __rest(_a, ["size", "type", "color", "primaryIcon", "leftIcon", "rightIcon", "children", "sx"]);
|
|
55
|
+
var { size: sizeParam, type: typeParam, color, primaryIcon, leftIcon, rightIcon, disabled: disabledParam, children, sx, loading } = _a, rest = __rest(_a, ["size", "type", "color", "primaryIcon", "leftIcon", "rightIcon", "disabled", "children", "sx", "loading"]);
|
|
56
|
+
const disabled = disabledParam || loading;
|
|
55
57
|
const theme = useTheme();
|
|
56
58
|
const [hover, setHover] = useState(false);
|
|
57
59
|
const { flagInDevelopment } = useEnvironment();
|
|
@@ -60,6 +62,7 @@ export const Button = React.forwardRef((_a, ref) => {
|
|
|
60
62
|
let textColor = COLOR.TEXT;
|
|
61
63
|
let hoverTextColor = undefined;
|
|
62
64
|
let borderColor = COLOR.TRANSPARENT;
|
|
65
|
+
let cursor = 'pointer';
|
|
63
66
|
let hoverShadow = theme.boxShadow.NORMAL;
|
|
64
67
|
let hoverDecoration = undefined;
|
|
65
68
|
let hoverTransform = 'scale(1.03)';
|
|
@@ -108,25 +111,50 @@ export const Button = React.forwardRef((_a, ref) => {
|
|
|
108
111
|
hoverTextColor = textColor;
|
|
109
112
|
borderColor = textColor;
|
|
110
113
|
}
|
|
114
|
+
if (disabled) {
|
|
115
|
+
cursor = 'not-allowed';
|
|
116
|
+
if (bg === COLOR.PRIMARY) {
|
|
117
|
+
bg = hoverBg = COLOR.LIGHT_GRAY;
|
|
118
|
+
}
|
|
119
|
+
if (bg === COLOR.BACKGROUND || bg === COLOR.TRANSPARENT) {
|
|
120
|
+
textColor = COLOR.LIGHT_GRAY;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
bg = COLOR.LIGHT_GRAY;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
111
126
|
const typography = typographyMap[size];
|
|
112
127
|
const typographyStyles = getTypographyStyles(typography);
|
|
113
|
-
|
|
128
|
+
let body = children;
|
|
129
|
+
if (primaryIcon) {
|
|
130
|
+
body = (_jsx(Icon, { icon: primaryIcon, size: getIconSize(typographyStyles.fontSize), color: hover ? hoverTextColor : textColor }));
|
|
131
|
+
}
|
|
132
|
+
if (loading) {
|
|
133
|
+
body = (_jsxs("div", Object.assign({ sx: {
|
|
134
|
+
position: 'relative',
|
|
135
|
+
display: 'flex',
|
|
136
|
+
alignItems: 'center',
|
|
137
|
+
justifyContent: 'center',
|
|
138
|
+
} }, { children: [_jsx(Spinner, { size: SPINNER_SIZE.SM, color: textColor, sx: { position: 'absolute' } }), _jsx("div", Object.assign({ sx: { visibility: 'hidden' } }, { children: body }))] })));
|
|
139
|
+
}
|
|
140
|
+
return (_jsxs("button", Object.assign({ ref: ref, disabled: disabled, onMouseEnter: () => {
|
|
114
141
|
setHover(true);
|
|
115
142
|
}, onMouseLeave: () => {
|
|
116
143
|
setHover(false);
|
|
117
|
-
}, sx: Object.assign(Object.assign({ bg: bg || colors.primary, color: textColor, px: paddingX, width: width, height: height, border: 'solid 1px', borderRadius: '4px', borderColor, display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'center', ':hover':
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
144
|
+
}, sx: Object.assign(Object.assign({ cursor, bg: bg || colors.primary, color: textColor, px: paddingX, width: width, height: height, border: 'solid 1px', borderRadius: '4px', borderColor, display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'center', ':hover': !disabled
|
|
145
|
+
? {
|
|
146
|
+
bg: hoverBg,
|
|
147
|
+
color: hoverTextColor,
|
|
148
|
+
boxShadow: hoverShadow,
|
|
149
|
+
transform: hoverTransform,
|
|
150
|
+
textDecoration: hoverDecoration,
|
|
151
|
+
}
|
|
152
|
+
: undefined }, typographyStyles), sx) }, rest, { children: [leftIcon && (_jsx(Icon, { icon: leftIcon, size: typographyStyles.fontSize, sx: { mr: iconMargin[size] }, color: hover ? hoverTextColor : textColor })), _jsx("div", Object.assign({ css: {
|
|
125
153
|
flexGrow: 1,
|
|
126
154
|
justifyContent: 'flex-start',
|
|
127
155
|
textOverflow: 'ellipsis',
|
|
128
156
|
whiteSpace: 'nowrap',
|
|
129
157
|
overflow: 'hidden',
|
|
130
158
|
textAlign: 'start',
|
|
131
|
-
} }, { children:
|
|
159
|
+
} }, { children: body })), rightIcon && (_jsx(Icon, { icon: rightIcon, size: typographyStyles.fontSize, sx: { ml: iconMargin[size] }, color: hover ? hoverTextColor : textColor }))] })));
|
|
132
160
|
});
|
|
@@ -17,7 +17,7 @@ import CircleOutlinedIcon from '@mui/icons-material/CircleOutlined';
|
|
|
17
17
|
import SquareIcon from '@mui/icons-material/Square';
|
|
18
18
|
import SquareOutlinedIcon from '@mui/icons-material/SquareOutlined';
|
|
19
19
|
import { COLOR, Label, TYPOGRAPHY_TYPE } from '../../..';
|
|
20
|
-
import { getTypographyStyles } from '../../../theme/typography';
|
|
20
|
+
import { FONT_SIZE, getTypographyStyles } from '../../../theme/typography';
|
|
21
21
|
import { Icon } from '../Icon';
|
|
22
22
|
export var LIST_SIZE;
|
|
23
23
|
(function (LIST_SIZE) {
|
|
@@ -66,8 +66,8 @@ export const ListItem = (_a) => {
|
|
|
66
66
|
var { index, sx, children } = _a, rest = __rest(_a, ["index", "sx", "children"]);
|
|
67
67
|
const { ordered, icon, iconColor, size, depth } = useListConfig();
|
|
68
68
|
const typographyStyles = getTypographyStyles(sizeTypographyMap[size]);
|
|
69
|
-
return (_jsxs("li", Object.assign({ sx: Object.assign(Object.assign({ depth, display: 'flex', mt: index === 0 && depth === 0 ? 0 : marginSizeMap[size], ml:
|
|
70
|
-
marginTop: '
|
|
69
|
+
return (_jsxs("li", Object.assign({ sx: Object.assign(Object.assign({ depth, display: 'flex', mt: index === 0 && depth === 0 ? 0 : marginSizeMap[size], ml: 1 }, typographyStyles), sx) }, rest, { children: [ordered ? (_jsxs(Label, Object.assign({ standalone: true, sx: { mr: 2 } }, { children: [index + 1, "."] }))) : (_jsx(Icon, { icon: icon, color: iconColor, size: FONT_SIZE.XS, css: {
|
|
70
|
+
marginTop: '7px',
|
|
71
71
|
}, sx: {
|
|
72
72
|
mr: 3,
|
|
73
73
|
alignItems: 'flex-start',
|
|
@@ -4,3 +4,6 @@ declare const meta: Meta<typeof Markdown>;
|
|
|
4
4
|
export default meta;
|
|
5
5
|
type Story = StoryObj<typeof Markdown>;
|
|
6
6
|
export declare const Default: Story;
|
|
7
|
+
export declare const WithHtml: Story;
|
|
8
|
+
export declare const CustomHtmlSanitize: Story;
|
|
9
|
+
export declare const NoHtml: Story;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Markdown } from '.';
|
|
2
|
+
import { DEFAULT_ALLOWED_ATTRIBUTES, DEFAULT_ALLOWED_TAGS, Markdown } from '.';
|
|
3
3
|
const meta = {
|
|
4
4
|
title: 'Elements/Markdown',
|
|
5
5
|
component: Markdown,
|
|
@@ -13,6 +13,7 @@ litora tellus ligula porttitor metus.
|
|
|
13
13
|
`;
|
|
14
14
|
export default meta;
|
|
15
15
|
const startingContent = `
|
|
16
|
+
|
|
16
17
|
# Inline styles
|
|
17
18
|
This paragraph has __bold content__ and **more bold content** and _italic content_ and *more italic content*
|
|
18
19
|
|
|
@@ -51,3 +52,37 @@ export const Default = {
|
|
|
51
52
|
content: startingContent,
|
|
52
53
|
},
|
|
53
54
|
};
|
|
55
|
+
const htmlContent = `
|
|
56
|
+
## HTML Content
|
|
57
|
+
<p style='color: red;'>hello world </p>
|
|
58
|
+
<img
|
|
59
|
+
style="width: 300px"
|
|
60
|
+
src="https://s3.amazonaws.com/resources.library.nd.edu/images/website/search.banner.fall.jpg"
|
|
61
|
+
></img>
|
|
62
|
+
<a href="https://google.com" class="embedly-card" data-card-width="100px" data-card-controls="0">Embedded content: https://google.com</a>
|
|
63
|
+
<iframe src="https://www.facebook.com/plugins/video.php?href=https%3A%2F%2Fwww.facebook.com%2FNDLibraries%2Fvideos%2F1673794309311939%2F&show_text=0&width=560" width="560" height="315" style="border:none;overflow:hidden" scrolling="no" frameborder="0" ></iframe>
|
|
64
|
+
`;
|
|
65
|
+
export const WithHtml = {
|
|
66
|
+
render: (args) => _jsx(Markdown, Object.assign({}, args)),
|
|
67
|
+
args: {
|
|
68
|
+
content: htmlContent,
|
|
69
|
+
enableHtml: true,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
export const CustomHtmlSanitize = {
|
|
73
|
+
render: (args) => _jsx(Markdown, Object.assign({}, args)),
|
|
74
|
+
args: {
|
|
75
|
+
enableHtml: true,
|
|
76
|
+
sanitizeHtmlOptions: {
|
|
77
|
+
allowedAttributes: Object.assign(Object.assign({}, DEFAULT_ALLOWED_ATTRIBUTES), { p: ['style'], img: ['src', 'style'] }),
|
|
78
|
+
allowedTags: [...DEFAULT_ALLOWED_TAGS, 'img'],
|
|
79
|
+
},
|
|
80
|
+
content: htmlContent,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
export const NoHtml = {
|
|
84
|
+
render: (args) => _jsx(Markdown, Object.assign({}, args)),
|
|
85
|
+
args: {
|
|
86
|
+
content: htmlContent,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { render } from '../../../utils/test';
|
|
3
|
-
import { Markdown } from '.';
|
|
3
|
+
import { DEFAULT_ALLOWED_TAGS, Markdown } from '.';
|
|
4
4
|
const testMarkdown = `
|
|
5
5
|
# Inline styles
|
|
6
6
|
This paragraph has __bold content__ and **more bold content** and _italic content_ and *more italic content*
|
|
@@ -26,6 +26,15 @@ This paragraph has __bold content__ and **more bold content** and _italic conten
|
|
|
26
26
|
2. List item 2
|
|
27
27
|
3. List item 3
|
|
28
28
|
`;
|
|
29
|
+
const htmlMarkdown = `
|
|
30
|
+
## HTML Content
|
|
31
|
+
<p style='color: red;'>hello world</p>
|
|
32
|
+
<p style='color: red;'> </p>
|
|
33
|
+
<script></script>
|
|
34
|
+
<a href="https://google.com" class="embedly-card" data-card-width="100px" data-card-controls="0">Embedded content: https://google.com</a>
|
|
35
|
+
<iframe src="https://www.facebook.com/plugins/video.php?href=https%3A%2F%2Fwww.facebook.com%2FNDLibraries%2Fvideos%2F1673794309311939%2F&show_text=0&width=560" width="560" height="315" style="border:none;overflow:hidden" scrolling="no" frameborder="0"></iframe>
|
|
36
|
+
<img href="https://google.com"></img>
|
|
37
|
+
`;
|
|
29
38
|
describe('Markdown', () => {
|
|
30
39
|
it('renders alert content', () => {
|
|
31
40
|
const { getAllByRole, getByRole } = render(_jsx(Markdown, { content: testMarkdown }));
|
|
@@ -55,4 +64,25 @@ describe('Markdown', () => {
|
|
|
55
64
|
})).toBeInTheDocument();
|
|
56
65
|
expect(getAllByRole('list')).toHaveLength(3);
|
|
57
66
|
});
|
|
67
|
+
it('renders allowed html when included', () => {
|
|
68
|
+
const { container, getByText, getByRole } = render(_jsx(Markdown, { content: htmlMarkdown, enableHtml: true }));
|
|
69
|
+
expect(getByText('hello world')).toBeDefined();
|
|
70
|
+
expect(getByRole('link')).toBeDefined();
|
|
71
|
+
expect(() => getByText(' ')).toThrow();
|
|
72
|
+
expect(container.getElementsByTagName('script')).toHaveLength(0);
|
|
73
|
+
expect(container.getElementsByTagName('img')).toHaveLength(0);
|
|
74
|
+
expect(container.getElementsByTagName('iframe')).toHaveLength(1);
|
|
75
|
+
});
|
|
76
|
+
it('does not render html when disabled', () => {
|
|
77
|
+
const { container, getByText } = render(_jsx(Markdown, { content: htmlMarkdown }));
|
|
78
|
+
expect(() => getByText('hello world')).toThrow();
|
|
79
|
+
expect(container.getElementsByTagName('script')).toHaveLength(0);
|
|
80
|
+
expect(container.getElementsByTagName('iframe')).toHaveLength(0);
|
|
81
|
+
});
|
|
82
|
+
it('supports customizing html sanitization', () => {
|
|
83
|
+
const { container } = render(_jsx(Markdown, { content: htmlMarkdown, enableHtml: true, sanitizeHtmlOptions: {
|
|
84
|
+
allowedTags: DEFAULT_ALLOWED_TAGS.concat(['img']),
|
|
85
|
+
} }));
|
|
86
|
+
expect(container.getElementsByTagName('img')).toHaveLength(1);
|
|
87
|
+
});
|
|
58
88
|
});
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { StyledElementProps } from '../../../theme';
|
|
3
|
+
import sanitizeHtml from 'sanitize-html';
|
|
4
|
+
export declare const DEFAULT_ALLOWED_TAGS: string[];
|
|
5
|
+
export declare const DEFAULT_ALLOWED_ATTRIBUTES: {
|
|
6
|
+
iframe: string[];
|
|
7
|
+
a: sanitizeHtml.AllowedAttribute[];
|
|
8
|
+
};
|
|
9
|
+
export declare const DEFAULT_ALLOWED_IFRAME_DOMAINS: string[];
|
|
3
10
|
type MarkdownProps = StyledElementProps<HTMLDivElement, {
|
|
4
11
|
content: string;
|
|
12
|
+
enableHtml?: boolean;
|
|
13
|
+
sanitizeHtmlOptions?: {
|
|
14
|
+
allowedTags?: string[];
|
|
15
|
+
allowedIframeDomains?: string[];
|
|
16
|
+
allowedAttributes?: sanitizeHtml.IOptions['allowedAttributes'];
|
|
17
|
+
};
|
|
5
18
|
}>;
|
|
6
19
|
export declare const Markdown: React.FC<MarkdownProps>;
|
|
7
20
|
export {};
|
|
@@ -11,11 +11,26 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
13
|
import { ReactMarkdown } from 'react-markdown/lib/react-markdown';
|
|
14
|
+
import rehypeRaw from 'rehype-raw';
|
|
14
15
|
import { List, ListItem } from '../List';
|
|
15
16
|
import { Paragraph } from '../text/Paragraph';
|
|
16
17
|
import { HEADING_SIZE, Heading } from '../text/Heading';
|
|
17
18
|
import { Bold, Italic } from '../text/Inline';
|
|
18
19
|
import { firstChildAltSelector } from '../../../utils/misc';
|
|
20
|
+
import sanitizeHtml from 'sanitize-html';
|
|
21
|
+
export const DEFAULT_ALLOWED_TAGS = sanitizeHtml.defaults.allowedTags.concat([
|
|
22
|
+
'iframe',
|
|
23
|
+
]);
|
|
24
|
+
export const DEFAULT_ALLOWED_ATTRIBUTES = Object.assign(Object.assign({}, sanitizeHtml.defaults.allowedAttributes), { iframe: ['*'], a: sanitizeHtml.defaults.allowedAttributes.a.concat([
|
|
25
|
+
'class',
|
|
26
|
+
'data-card-width',
|
|
27
|
+
'data-card-controls',
|
|
28
|
+
]) });
|
|
29
|
+
export const DEFAULT_ALLOWED_IFRAME_DOMAINS = [
|
|
30
|
+
'facebook.com',
|
|
31
|
+
'youtube.com',
|
|
32
|
+
'nd.edu',
|
|
33
|
+
];
|
|
19
34
|
const headingStyles = {
|
|
20
35
|
mt: 4,
|
|
21
36
|
[firstChildAltSelector]: {
|
|
@@ -29,8 +44,17 @@ const paragraphStyles = {
|
|
|
29
44
|
},
|
|
30
45
|
};
|
|
31
46
|
export const Markdown = (_a) => {
|
|
32
|
-
var { content } = _a, rest = __rest(_a, ["content"]);
|
|
33
|
-
|
|
47
|
+
var { content, enableHtml, sanitizeHtmlOptions } = _a, rest = __rest(_a, ["content", "enableHtml", "sanitizeHtmlOptions"]);
|
|
48
|
+
let sanitizedContent = content;
|
|
49
|
+
if (enableHtml) {
|
|
50
|
+
sanitizedContent = sanitizeHtml(content, {
|
|
51
|
+
allowedTags: (sanitizeHtmlOptions === null || sanitizeHtmlOptions === void 0 ? void 0 : sanitizeHtmlOptions.allowedTags) || DEFAULT_ALLOWED_TAGS,
|
|
52
|
+
allowedIframeDomains: (sanitizeHtmlOptions === null || sanitizeHtmlOptions === void 0 ? void 0 : sanitizeHtmlOptions.allowedIframeDomains) ||
|
|
53
|
+
DEFAULT_ALLOWED_IFRAME_DOMAINS,
|
|
54
|
+
allowedAttributes: (sanitizeHtmlOptions === null || sanitizeHtmlOptions === void 0 ? void 0 : sanitizeHtmlOptions.allowedAttributes) || DEFAULT_ALLOWED_ATTRIBUTES,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return (_jsx("div", Object.assign({}, rest, { children: _jsx(ReactMarkdown, Object.assign({ rehypePlugins: enableHtml ? [rehypeRaw] : [], components: {
|
|
34
58
|
h1: (props) => (_jsx(Heading, Object.assign({ size: HEADING_SIZE.XL }, props, { standalone: true, sx: headingStyles, "aria-level": 1 }))),
|
|
35
59
|
h2: (props) => (_jsx(Heading, Object.assign({ size: HEADING_SIZE.LG }, props, { standalone: true, sx: headingStyles, "aria-level": 2 }))),
|
|
36
60
|
h3: (props) => (_jsx(Heading, Object.assign({ size: HEADING_SIZE.MD }, props, { standalone: true, sx: headingStyles, "aria-level": 3 }))),
|
|
@@ -39,12 +63,13 @@ export const Markdown = (_a) => {
|
|
|
39
63
|
h6: (props) => (_jsx(Heading, Object.assign({ size: HEADING_SIZE.SM }, props, { standalone: true, sx: headingStyles, "aria-level": 6 }))),
|
|
40
64
|
ol: (props) => _jsx(List, Object.assign({}, props, { ordered: true, sx: { mt: 4 } })),
|
|
41
65
|
ul: (props) => _jsx(List, Object.assign({}, props, { sx: { mt: 4 } })),
|
|
66
|
+
// eslint-disable-next-line
|
|
42
67
|
li: (_a) => {
|
|
43
|
-
var { index } = _a, props = __rest(_a, ["index"]);
|
|
44
|
-
return _jsx(ListItem, Object.assign({ index: index }, props));
|
|
68
|
+
var { index, ordered } = _a, props = __rest(_a, ["index", "ordered"]);
|
|
69
|
+
return (_jsx(ListItem, Object.assign({ index: index }, props)));
|
|
45
70
|
},
|
|
46
71
|
p: (props) => _jsx(Paragraph, Object.assign({}, props, { sx: paragraphStyles })),
|
|
47
72
|
strong: (props) => _jsx(Bold, Object.assign({}, props)),
|
|
48
73
|
em: (props) => _jsx(Italic, Object.assign({}, props)),
|
|
49
|
-
} }, { children:
|
|
74
|
+
} }, { children: sanitizedContent })) })));
|
|
50
75
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Spinner } from '.';
|
|
3
|
+
declare const meta: Meta<typeof Spinner>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof Spinner>;
|
|
6
|
+
export declare const Default: Story;
|
|
7
|
+
export declare const CustomColor: Story;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Column, Group, HEADING_SIZE, Heading } from '../../..';
|
|
3
|
+
import { SPINNER_SIZE, Spinner } from '.';
|
|
4
|
+
import { GROUP_TYPE } from '../Group';
|
|
5
|
+
import { COLOR } from '../../../theme/colors';
|
|
6
|
+
// import { HEADING_SIZE, Heading } from '.'
|
|
7
|
+
// import { COLOR } from '../../../../theme/colors'
|
|
8
|
+
const meta = {
|
|
9
|
+
title: 'Elements/Spinner',
|
|
10
|
+
component: Spinner,
|
|
11
|
+
//👇 Enables auto-generated documentation for the component story
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
};
|
|
14
|
+
export default meta;
|
|
15
|
+
const sizes = [
|
|
16
|
+
{ size: SPINNER_SIZE.LG, label: 'Large' },
|
|
17
|
+
{ size: SPINNER_SIZE.MD, label: 'Medium' },
|
|
18
|
+
{ size: SPINNER_SIZE.SM, label: 'Small' },
|
|
19
|
+
];
|
|
20
|
+
export const Default = {
|
|
21
|
+
render: () => (_jsx(Column, { children: sizes.map((size) => (_jsxs(Group, Object.assign({ type: GROUP_TYPE.REGION }, { children: [_jsx(Heading, Object.assign({ size: HEADING_SIZE.SM }, { children: size.label })), _jsx(Spinner, { size: size.size })] })))) })),
|
|
22
|
+
args: {},
|
|
23
|
+
};
|
|
24
|
+
export const CustomColor = {
|
|
25
|
+
render: () => (_jsx(Column, { children: _jsx(Spinner, { color: COLOR.SECONDARY }) })),
|
|
26
|
+
args: {
|
|
27
|
+
children: 'Heading',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render } from '../../../utils/test';
|
|
3
|
+
import { SPINNER_LABEL, Spinner } from '.';
|
|
4
|
+
describe('Button', () => {
|
|
5
|
+
it('renders without throwing an error', () => {
|
|
6
|
+
const { getByLabelText } = render(_jsx(Spinner, {}));
|
|
7
|
+
expect(getByLabelText(SPINNER_LABEL)).toBeInTheDocument();
|
|
8
|
+
});
|
|
9
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { StyledElementProps } from '../../../theme';
|
|
3
|
+
import { COLOR } from '../../../theme/colors';
|
|
4
|
+
export declare enum SPINNER_SIZE {
|
|
5
|
+
SM = "sm",
|
|
6
|
+
MD = "md",
|
|
7
|
+
LG = "lg"
|
|
8
|
+
}
|
|
9
|
+
export type SpinnerProps = StyledElementProps<HTMLDivElement, {
|
|
10
|
+
size?: SPINNER_SIZE;
|
|
11
|
+
color?: COLOR;
|
|
12
|
+
}>;
|
|
13
|
+
export declare const SPINNER_LABEL = "Loading";
|
|
14
|
+
export declare const Spinner: React.FC<SpinnerProps>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "theme-ui/jsx-runtime";
|
|
13
|
+
/** @jsxImportSource theme-ui */
|
|
14
|
+
import { keyframes } from '@emotion/react';
|
|
15
|
+
import { useTheme } from '../../../theme';
|
|
16
|
+
import { COLOR } from '../../../theme/colors';
|
|
17
|
+
export var SPINNER_SIZE;
|
|
18
|
+
(function (SPINNER_SIZE) {
|
|
19
|
+
SPINNER_SIZE["SM"] = "sm";
|
|
20
|
+
SPINNER_SIZE["MD"] = "md";
|
|
21
|
+
SPINNER_SIZE["LG"] = "lg";
|
|
22
|
+
})(SPINNER_SIZE || (SPINNER_SIZE = {}));
|
|
23
|
+
const spin = keyframes({
|
|
24
|
+
from: {
|
|
25
|
+
transform: 'rotate(0deg)',
|
|
26
|
+
},
|
|
27
|
+
to: {
|
|
28
|
+
transform: 'rotate(360deg)',
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
const sizeLengthMap = {
|
|
32
|
+
[SPINNER_SIZE.SM]: '1.25rem',
|
|
33
|
+
[SPINNER_SIZE.MD]: '2rem',
|
|
34
|
+
[SPINNER_SIZE.LG]: '3rem',
|
|
35
|
+
};
|
|
36
|
+
export const SPINNER_LABEL = 'Loading';
|
|
37
|
+
export const Spinner = (_a) => {
|
|
38
|
+
var { size: sizeParam, color: colorParam } = _a, rest = __rest(_a, ["size", "color"]);
|
|
39
|
+
const { colors } = useTheme();
|
|
40
|
+
const size = sizeParam || SPINNER_SIZE.MD;
|
|
41
|
+
const color = colors[colorParam || COLOR.PRIMARY];
|
|
42
|
+
return (_jsx("div", Object.assign({ "aria-label": SPINNER_LABEL, sx: {
|
|
43
|
+
animation: `${spin} 1.4s linear 0s infinite normal none running;`,
|
|
44
|
+
borderRadius: '50%',
|
|
45
|
+
height: sizeLengthMap[size],
|
|
46
|
+
width: sizeLengthMap[size],
|
|
47
|
+
border: size === SPINNER_SIZE.LG ? 'solid 6px' : 'solid 4px',
|
|
48
|
+
borderColor: `${color} ${color} transparent`,
|
|
49
|
+
} }, rest)));
|
|
50
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
const fallbackMemory = {};
|
|
3
|
+
const isBrowser = typeof window !== 'undefined';
|
|
3
4
|
const fallbackStorage = {
|
|
4
5
|
getItem(key) {
|
|
5
6
|
return fallbackMemory[key];
|
|
@@ -10,7 +11,12 @@ const fallbackStorage = {
|
|
|
10
11
|
};
|
|
11
12
|
class StorageAdaptor {
|
|
12
13
|
constructor(prefix) {
|
|
13
|
-
|
|
14
|
+
if (isBrowser) {
|
|
15
|
+
this.storage = window.sessionStorage || fallbackStorage;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
this.storage = fallbackStorage;
|
|
19
|
+
}
|
|
14
20
|
this.prefix = prefix;
|
|
15
21
|
}
|
|
16
22
|
getStorageKey(key) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ndlib/component-library",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"files": [
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"@testing-library/user-event": "^14.4.3",
|
|
54
54
|
"@types/react": "^18.0.28",
|
|
55
55
|
"@types/react-dom": "^18.0.11",
|
|
56
|
+
"@types/sanitize-html": "^2.9.0",
|
|
56
57
|
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
|
57
58
|
"@typescript-eslint/parser": "^5.57.1",
|
|
58
59
|
"@vitejs/plugin-react-swc": "^3.0.0",
|
|
@@ -67,7 +68,7 @@
|
|
|
67
68
|
"react": "^18.2.0",
|
|
68
69
|
"react-dom": "^18.2.0",
|
|
69
70
|
"storybook": "^7.0.17",
|
|
70
|
-
"theme-ui": "^0.
|
|
71
|
+
"theme-ui": "^0.16.0",
|
|
71
72
|
"typescript": "^5.0.2",
|
|
72
73
|
"vite": "^4.3.9",
|
|
73
74
|
"vitest": "^0.31.4"
|
|
@@ -78,6 +79,8 @@
|
|
|
78
79
|
},
|
|
79
80
|
"dependencies": {
|
|
80
81
|
"@floating-ui/react": "^0.24.5",
|
|
81
|
-
"react-markdown": "^8.0.7"
|
|
82
|
+
"react-markdown": "^8.0.7",
|
|
83
|
+
"rehype-raw": "^6.1.1",
|
|
84
|
+
"sanitize-html": "^2.11.0"
|
|
82
85
|
}
|
|
83
86
|
}
|