@os-design/core 1.0.198 → 1.0.200
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/cjs/InputSearch/index.js +2 -2
- package/dist/cjs/InputSearch/index.js.map +1 -1
- package/dist/esm/InputSearch/index.js +2 -2
- package/dist/esm/InputSearch/index.js.map +1 -1
- package/dist/types/InputSearch/index.d.ts.map +1 -1
- package/package.json +21 -13
- package/src/@types/emotion.d.ts +7 -0
- package/src/Alert/index.tsx +112 -0
- package/src/Avatar/index.tsx +173 -0
- package/src/Avatar/utils/nameToInitials.ts +12 -0
- package/src/Avatar/utils/strToHue.ts +13 -0
- package/src/AvatarSkeleton/index.tsx +29 -0
- package/src/Breadcrumb/index.tsx +93 -0
- package/src/BreadcrumbItem/index.tsx +83 -0
- package/src/Button/ButtonContent.tsx +91 -0
- package/src/Button/index.tsx +225 -0
- package/src/Button/utils/useButtonColors.ts +84 -0
- package/src/Checkbox/index.tsx +225 -0
- package/src/CheckboxSkeleton/index.tsx +50 -0
- package/src/DatePicker/DatePickerCalendar.tsx +220 -0
- package/src/DatePicker/index.tsx +568 -0
- package/src/Drawer/index.tsx +212 -0
- package/src/Form/FormConfigContext.ts +16 -0
- package/src/Form/index.tsx +49 -0
- package/src/FormDivider/index.tsx +74 -0
- package/src/FormItem/index.tsx +118 -0
- package/src/Gallery/Status.tsx +62 -0
- package/src/Gallery/index.tsx +290 -0
- package/src/GlobalStyles/index.tsx +17 -0
- package/src/GlobalStyles/resetStyles.ts +17 -0
- package/src/GlobalStyles/typographyStyles.ts +78 -0
- package/src/HeaderSkeleton/index.tsx +64 -0
- package/src/Image/index.tsx +104 -0
- package/src/ImageSkeleton/index.tsx +22 -0
- package/src/Input/index.tsx +330 -0
- package/src/Input/utils/getFocusableElements.ts +8 -0
- package/src/InputNumber/index.tsx +208 -0
- package/src/InputNumber/utils/defaultLocale.ts +9 -0
- package/src/InputPassword/index.tsx +201 -0
- package/src/InputPassword/utils/defaultLocale.ts +11 -0
- package/src/InputSearch/index.tsx +111 -0
- package/src/InputSearch/utils/defaultLocale.ts +9 -0
- package/src/InputSkeleton/index.tsx +28 -0
- package/src/Layout/LayoutContext.ts +21 -0
- package/src/Layout/index.tsx +44 -0
- package/src/Link/index.tsx +129 -0
- package/src/LinkButton/index.tsx +100 -0
- package/src/List/WindowScroller.tsx +53 -0
- package/src/List/index.tsx +255 -0
- package/src/List/utils/bodyPointerEvents.ts +24 -0
- package/src/List/utils/frameTimeout.ts +36 -0
- package/src/List/utils/useRWLoadNext.ts +38 -0
- package/src/ListItem/index.tsx +92 -0
- package/src/ListItemActions/index.tsx +207 -0
- package/src/ListItemLink/index.tsx +63 -0
- package/src/ListSkeleton/index.tsx +115 -0
- package/src/LogoLink/index.tsx +93 -0
- package/src/LogoLink/logo.example.svg +18 -0
- package/src/Menu/index.tsx +128 -0
- package/src/Menu/utils/useFocusWithArrows.ts +50 -0
- package/src/MenuDivider/index.tsx +22 -0
- package/src/MenuGroup/index.tsx +190 -0
- package/src/MenuItem/index.tsx +108 -0
- package/src/Modal/index.tsx +411 -0
- package/src/Modal/utils/defaultLocale.ts +9 -0
- package/src/Navigation/index.tsx +214 -0
- package/src/Navigation/utils/useScrollFlags.ts +39 -0
- package/src/NavigationItem/index.tsx +136 -0
- package/src/PageContent/index.tsx +99 -0
- package/src/PageHeader/index.tsx +246 -0
- package/src/PageHeader/utils/defaultLocale.ts +9 -0
- package/src/PageHeaderInputSearch/index.tsx +145 -0
- package/src/PageHeaderInputSearch/utils/defaultLocale.ts +16 -0
- package/src/PageHeaderSkeleton/index.tsx +33 -0
- package/src/ParagraphSkeleton/index.tsx +65 -0
- package/src/Popover/index.tsx +243 -0
- package/src/Popover/utils/usePopoverPosition.ts +216 -0
- package/src/Progress/index.tsx +100 -0
- package/src/RadioGroup/index.tsx +165 -0
- package/src/RadioGroupSkeleton/index.tsx +36 -0
- package/src/Result/index.tsx +109 -0
- package/src/ScrollButton/index.tsx +159 -0
- package/src/ScrollButton/utils/useContainerPosition.ts +41 -0
- package/src/ScrollButton/utils/useVisibility.ts +56 -0
- package/src/Select/index.tsx +970 -0
- package/src/Select/utils/defaultLocale.ts +11 -0
- package/src/Skeleton/index.tsx +52 -0
- package/src/Switch/index.tsx +217 -0
- package/src/SwitchSkeleton/index.tsx +30 -0
- package/src/Tag/index.tsx +75 -0
- package/src/TagLink/index.tsx +53 -0
- package/src/TagList/index.tsx +95 -0
- package/src/TagListSkeleton/index.tsx +38 -0
- package/src/TagSkeleton/index.tsx +40 -0
- package/src/TextArea/index.tsx +231 -0
- package/src/TextAreaSkeleton/index.tsx +20 -0
- package/src/ThemeSwitcher/index.tsx +39 -0
- package/src/TimePicker/index.tsx +142 -0
- package/src/Video/index.tsx +41 -0
- package/src/index.ts +125 -0
- package/src/message/AlertIcon.tsx +50 -0
- package/src/message/Message.tsx +108 -0
- package/src/message/index.tsx +64 -0
- package/src/message/styles.ts +25 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import styled from '@emotion/styled';
|
|
2
|
+
import { Left, Right } from '@os-design/icons';
|
|
3
|
+
import { ThemeOverrider, clr } from '@os-design/theming';
|
|
4
|
+
import {
|
|
5
|
+
isTouchDevice,
|
|
6
|
+
omitEmotionProps,
|
|
7
|
+
useForwardedRef,
|
|
8
|
+
useSize,
|
|
9
|
+
} from '@os-design/utils';
|
|
10
|
+
import React, {
|
|
11
|
+
MouseEventHandler,
|
|
12
|
+
TouchEventHandler,
|
|
13
|
+
forwardRef,
|
|
14
|
+
useCallback,
|
|
15
|
+
useEffect,
|
|
16
|
+
useMemo,
|
|
17
|
+
useRef,
|
|
18
|
+
useState,
|
|
19
|
+
} from 'react';
|
|
20
|
+
import Button from '../Button';
|
|
21
|
+
import Image, { ImageProps } from '../Image';
|
|
22
|
+
import GalleryStatus from './Status';
|
|
23
|
+
|
|
24
|
+
interface ContainerProps {
|
|
25
|
+
heightPercent: number;
|
|
26
|
+
}
|
|
27
|
+
const Container = styled(
|
|
28
|
+
'div',
|
|
29
|
+
omitEmotionProps('heightPercent')
|
|
30
|
+
)<ContainerProps>`
|
|
31
|
+
position: relative;
|
|
32
|
+
padding-bottom: ${(p) => p.heightPercent}%;
|
|
33
|
+
|
|
34
|
+
display: flex;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
|
|
37
|
+
background-color: ${(p) => clr(p.theme.galleryColorBg)};
|
|
38
|
+
border-radius: ${(p) => p.theme.borderRadius}em;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const StyledImage = styled(Image)`
|
|
43
|
+
position: absolute;
|
|
44
|
+
width: auto;
|
|
45
|
+
height: 100%;
|
|
46
|
+
border-radius: 0;
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
const NavButton = styled(Button)`
|
|
50
|
+
position: absolute;
|
|
51
|
+
top: 50%;
|
|
52
|
+
transform: translateY(-50%);
|
|
53
|
+
|
|
54
|
+
background-color: hsla(0, 0%, 0%, 0.5);
|
|
55
|
+
backdrop-filter: blur(0.2em);
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
const LeftButton = styled(NavButton)`
|
|
59
|
+
left: 0.2em;
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
const RightButton = styled(NavButton)`
|
|
63
|
+
right: 0.2em;
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
type JsxDivProps = Omit<JSX.IntrinsicElements['div'], 'ref'>;
|
|
67
|
+
export interface GalleryProps extends JsxDivProps {
|
|
68
|
+
/**
|
|
69
|
+
* The image urls.
|
|
70
|
+
*/
|
|
71
|
+
urls: string[] | ReadonlyArray<string>;
|
|
72
|
+
/**
|
|
73
|
+
* The aspect ratio of the gallery.
|
|
74
|
+
* E.g. [16,9] – 16:9.
|
|
75
|
+
*/
|
|
76
|
+
aspectRatio?: [number, number];
|
|
77
|
+
/**
|
|
78
|
+
* The props of the image component.
|
|
79
|
+
*/
|
|
80
|
+
imageProps?: Omit<ImageProps, 'url'>;
|
|
81
|
+
/**
|
|
82
|
+
* Whether the navigation buttons is shown.
|
|
83
|
+
*/
|
|
84
|
+
hideArrows?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* The children that can be displayed on top of the image.
|
|
87
|
+
* E.g. tags.
|
|
88
|
+
*/
|
|
89
|
+
children?: React.ReactNode;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const MIN_DIST_PX = 30;
|
|
93
|
+
const MAX_ANGLE = 30;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The image gallery. Change the cursor/touch position to change images.
|
|
97
|
+
* The lib 'lazysizes/plugins/attrchange/ls.attrchange' must be imported.
|
|
98
|
+
*/
|
|
99
|
+
const Gallery = forwardRef<HTMLDivElement, GalleryProps>(
|
|
100
|
+
(
|
|
101
|
+
{
|
|
102
|
+
urls,
|
|
103
|
+
aspectRatio = [16, 9],
|
|
104
|
+
imageProps = {},
|
|
105
|
+
hideArrows = false,
|
|
106
|
+
children,
|
|
107
|
+
...rest
|
|
108
|
+
},
|
|
109
|
+
ref
|
|
110
|
+
) => {
|
|
111
|
+
const [innerContainerRef, mergedContainerRef] = useForwardedRef(ref);
|
|
112
|
+
|
|
113
|
+
const [imageUrl, setImageUrl] = useState<string | undefined>(undefined);
|
|
114
|
+
const [imageIndex, setImageIndex] = useState(urls.length > 0 ? 0 : null);
|
|
115
|
+
|
|
116
|
+
const heightPercent = useMemo(
|
|
117
|
+
() => Math.round((aspectRatio[1] / aspectRatio[0]) * 1000000) / 10000,
|
|
118
|
+
[aspectRatio]
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const imageIndexRef = useRef(imageIndex);
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
imageIndexRef.current = imageIndex;
|
|
124
|
+
}, [imageIndex]);
|
|
125
|
+
|
|
126
|
+
// Update the image if the index was changed
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
setImageUrl(imageIndex !== null ? urls[imageIndex] : undefined);
|
|
129
|
+
}, [imageIndex, urls]);
|
|
130
|
+
|
|
131
|
+
const startTouchPosRef = useRef<{ x: number; y: number } | null>(null);
|
|
132
|
+
|
|
133
|
+
const size = useSize(innerContainerRef);
|
|
134
|
+
const sizeRef = useRef(size);
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
sizeRef.current = size;
|
|
137
|
+
}, [size]);
|
|
138
|
+
|
|
139
|
+
const statusHeight = useMemo(
|
|
140
|
+
() => Math.round(size.height / 70),
|
|
141
|
+
[size.height]
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const updateGalleryImage = useCallback(
|
|
145
|
+
(clientX: number) => {
|
|
146
|
+
if (!innerContainerRef.current) return;
|
|
147
|
+
const { x } = innerContainerRef.current.getBoundingClientRect();
|
|
148
|
+
const widthPerImage = sizeRef.current.width / urls.length;
|
|
149
|
+
const xPos = clientX - x;
|
|
150
|
+
if (xPos < 0) return;
|
|
151
|
+
const nextIndex = Math.floor(xPos / widthPerImage);
|
|
152
|
+
if (imageIndexRef.current !== nextIndex) {
|
|
153
|
+
setImageIndex(nextIndex);
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
[innerContainerRef, urls.length]
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const prev = useCallback(() => {
|
|
160
|
+
const index = imageIndexRef.current;
|
|
161
|
+
if (index === null) return;
|
|
162
|
+
setImageIndex(index > 0 ? index - 1 : urls.length - 1);
|
|
163
|
+
}, [urls.length]);
|
|
164
|
+
|
|
165
|
+
const next = useCallback(() => {
|
|
166
|
+
const index = imageIndexRef.current;
|
|
167
|
+
if (index === null) return;
|
|
168
|
+
setImageIndex(index < urls.length - 1 ? index + 1 : 0);
|
|
169
|
+
}, [urls.length]);
|
|
170
|
+
|
|
171
|
+
const left = useCallback<MouseEventHandler>(
|
|
172
|
+
(e) => {
|
|
173
|
+
prev();
|
|
174
|
+
e.preventDefault();
|
|
175
|
+
},
|
|
176
|
+
[prev]
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const right = useCallback<MouseEventHandler>(
|
|
180
|
+
(e) => {
|
|
181
|
+
next();
|
|
182
|
+
e.preventDefault();
|
|
183
|
+
},
|
|
184
|
+
[next]
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const mouseMoveHandler = useCallback<MouseEventHandler<HTMLDivElement>>(
|
|
188
|
+
(e) => updateGalleryImage(e.clientX),
|
|
189
|
+
[updateGalleryImage]
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const touchStartHandler = useCallback<TouchEventHandler<HTMLDivElement>>(
|
|
193
|
+
(e) => {
|
|
194
|
+
const { clientX, clientY } = e.touches[0];
|
|
195
|
+
startTouchPosRef.current = { x: clientX, y: clientY };
|
|
196
|
+
},
|
|
197
|
+
[]
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const touchMoveHandler = useCallback<TouchEventHandler<HTMLDivElement>>(
|
|
201
|
+
(e) => {
|
|
202
|
+
if (!startTouchPosRef.current) return;
|
|
203
|
+
const { x, y } = startTouchPosRef.current;
|
|
204
|
+
const { clientX, clientY } = e.touches[0];
|
|
205
|
+
const diffX = Math.abs(x - clientX);
|
|
206
|
+
const diffY = Math.abs(y - clientY);
|
|
207
|
+
const diff = Math.sqrt(diffX ** 2 + diffY ** 2);
|
|
208
|
+
if (diff < MIN_DIST_PX) return;
|
|
209
|
+
const angle = (Math.atan(diffY / diffX) * 180) / Math.PI;
|
|
210
|
+
if (angle > MAX_ANGLE) {
|
|
211
|
+
startTouchPosRef.current = null;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (x < clientX) prev();
|
|
215
|
+
else next();
|
|
216
|
+
startTouchPosRef.current = null;
|
|
217
|
+
},
|
|
218
|
+
[next, prev]
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const touchEndHandler = useCallback<
|
|
222
|
+
TouchEventHandler<HTMLDivElement>
|
|
223
|
+
>(() => {
|
|
224
|
+
startTouchPosRef.current = null;
|
|
225
|
+
}, []);
|
|
226
|
+
|
|
227
|
+
const handlers = useMemo(() => {
|
|
228
|
+
if (isTouchDevice()) {
|
|
229
|
+
return {
|
|
230
|
+
onTouchStart: touchStartHandler,
|
|
231
|
+
onTouchMove: touchMoveHandler,
|
|
232
|
+
onTouchEnd: touchEndHandler,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
onMouseMove: mouseMoveHandler,
|
|
237
|
+
};
|
|
238
|
+
}, [
|
|
239
|
+
mouseMoveHandler,
|
|
240
|
+
touchEndHandler,
|
|
241
|
+
touchMoveHandler,
|
|
242
|
+
touchStartHandler,
|
|
243
|
+
]);
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<ThemeOverrider activeTheme='dark'>
|
|
247
|
+
<Container
|
|
248
|
+
heightPercent={heightPercent}
|
|
249
|
+
{...handlers}
|
|
250
|
+
{...rest}
|
|
251
|
+
ref={mergedContainerRef}
|
|
252
|
+
>
|
|
253
|
+
<StyledImage url={imageUrl} {...imageProps} />
|
|
254
|
+
{urls.length > 1 && imageIndex !== null && (
|
|
255
|
+
<>
|
|
256
|
+
<GalleryStatus
|
|
257
|
+
count={urls.length}
|
|
258
|
+
current={imageIndex}
|
|
259
|
+
height={statusHeight}
|
|
260
|
+
/>
|
|
261
|
+
{isTouchDevice() && !hideArrows && (
|
|
262
|
+
<ThemeOverrider overrides={{ colorPrimary: [0, 0, 100] }}>
|
|
263
|
+
<LeftButton
|
|
264
|
+
type='ghost'
|
|
265
|
+
wide='never'
|
|
266
|
+
size='small'
|
|
267
|
+
onClick={left}
|
|
268
|
+
>
|
|
269
|
+
<Left />
|
|
270
|
+
</LeftButton>
|
|
271
|
+
<RightButton
|
|
272
|
+
type='ghost'
|
|
273
|
+
wide='never'
|
|
274
|
+
size='small'
|
|
275
|
+
onClick={right}
|
|
276
|
+
>
|
|
277
|
+
<Right />
|
|
278
|
+
</RightButton>
|
|
279
|
+
</ThemeOverrider>
|
|
280
|
+
)}
|
|
281
|
+
</>
|
|
282
|
+
)}
|
|
283
|
+
{children}
|
|
284
|
+
</Container>
|
|
285
|
+
</ThemeOverrider>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
export default Gallery;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Global } from '@emotion/react';
|
|
2
|
+
import { useVh } from '@os-design/utils';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
import resetStyles from './resetStyles';
|
|
6
|
+
import typographyStyles from './typographyStyles';
|
|
7
|
+
|
|
8
|
+
const GlobalStyles: React.FC = () => {
|
|
9
|
+
useVh();
|
|
10
|
+
return (
|
|
11
|
+
<Global styles={(theme) => [resetStyles(theme), typographyStyles(theme)]} />
|
|
12
|
+
);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
GlobalStyles.displayName = 'GlobalStyles';
|
|
16
|
+
|
|
17
|
+
export default GlobalStyles;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { css } from '@emotion/react';
|
|
2
|
+
import { SerializedStyles } from '@emotion/serialize';
|
|
3
|
+
import { Theme } from '@os-design/theming';
|
|
4
|
+
|
|
5
|
+
const resetStyles = (theme: Theme): SerializedStyles => css`
|
|
6
|
+
body {
|
|
7
|
+
margin: 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
p,
|
|
11
|
+
figure,
|
|
12
|
+
pre {
|
|
13
|
+
margin: 0 0 ${theme.paragraphMarginBottom}em;
|
|
14
|
+
}
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
export default resetStyles;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { css } from '@emotion/react';
|
|
2
|
+
import { SerializedStyles } from '@emotion/serialize';
|
|
3
|
+
import { m } from '@os-design/media';
|
|
4
|
+
import { Theme, clr } from '@os-design/theming';
|
|
5
|
+
import fp from 'facepaint';
|
|
6
|
+
|
|
7
|
+
const headingsFontSizeStyles = (fontSize: number[]) => {
|
|
8
|
+
const headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
|
|
9
|
+
return fp(headings, { literal: true })({
|
|
10
|
+
fontSize: fontSize.map((item) => `${item}em`),
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const headingsMarginTopStyles = (marginTop: number[]) => {
|
|
15
|
+
const headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].map(
|
|
16
|
+
(h) => `${h}:not(:first-of-type)`
|
|
17
|
+
);
|
|
18
|
+
return fp(headings, { literal: true })({
|
|
19
|
+
marginTop: marginTop.map((item) => `${item}em`),
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const typographyStyles = (theme: Theme): SerializedStyles => css`
|
|
24
|
+
html,
|
|
25
|
+
button,
|
|
26
|
+
input,
|
|
27
|
+
textarea,
|
|
28
|
+
select {
|
|
29
|
+
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
html,
|
|
33
|
+
input {
|
|
34
|
+
color: ${clr(theme.colorText)};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
html {
|
|
38
|
+
line-height: ${theme.lineHeight};
|
|
39
|
+
|
|
40
|
+
// Sets the font smoothing
|
|
41
|
+
-webkit-font-smoothing: antialiased;
|
|
42
|
+
-moz-osx-font-smoothing: grayscale;
|
|
43
|
+
text-rendering: optimizeLegibility;
|
|
44
|
+
|
|
45
|
+
// Sets the base font size and increases it on large screens
|
|
46
|
+
${fp([m.min.md, m.min.xxl])({
|
|
47
|
+
fontSize: theme.fontSize.map((s) => `${s}px`),
|
|
48
|
+
})};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
body {
|
|
52
|
+
background-color: ${clr(theme.colorBg)};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
h1,
|
|
56
|
+
h2,
|
|
57
|
+
h3,
|
|
58
|
+
h4,
|
|
59
|
+
h5,
|
|
60
|
+
h6 {
|
|
61
|
+
font-weight: bold;
|
|
62
|
+
line-height: 1.2;
|
|
63
|
+
margin: 0 0 ${theme.headingsMarginBottom}em;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Sets the margin top of headings
|
|
67
|
+
${headingsMarginTopStyles(theme.headingsMarginTop)};
|
|
68
|
+
|
|
69
|
+
// Sets the base font size of headings
|
|
70
|
+
${headingsFontSizeStyles(theme.headingsFontSize)};
|
|
71
|
+
|
|
72
|
+
// Sets the font size of headings on large screens
|
|
73
|
+
${m.min.md} {
|
|
74
|
+
${headingsFontSizeStyles(theme.headingsFontSizeMd)}
|
|
75
|
+
}
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
export default typographyStyles;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { css } from '@emotion/react';
|
|
2
|
+
|
|
3
|
+
import styled from '@emotion/styled';
|
|
4
|
+
import { m } from '@os-design/media';
|
|
5
|
+
|
|
6
|
+
import { omitEmotionProps } from '@os-design/utils';
|
|
7
|
+
import React, { forwardRef } from 'react';
|
|
8
|
+
|
|
9
|
+
import Skeleton, { SkeletonProps } from '../Skeleton';
|
|
10
|
+
|
|
11
|
+
export interface HeaderSkeletonProps extends SkeletonProps {
|
|
12
|
+
/**
|
|
13
|
+
* The header type.
|
|
14
|
+
* @default 1
|
|
15
|
+
*/
|
|
16
|
+
type?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
17
|
+
/**
|
|
18
|
+
* Whether the header has top and bottom margins.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
hasMargin?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const hasMarginStyles = (p) =>
|
|
25
|
+
p.hasMargin &&
|
|
26
|
+
css`
|
|
27
|
+
margin: ${p.theme.headingsMarginTop[p.type - 1]}em 0
|
|
28
|
+
${p.theme.headingsMarginBottom}em;
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
const MULTIPLIER = 0.9;
|
|
32
|
+
|
|
33
|
+
type StyledHeaderSkeletonProps = Required<
|
|
34
|
+
Pick<HeaderSkeletonProps, 'type' | 'hasMargin'>
|
|
35
|
+
>;
|
|
36
|
+
const StyledHeaderSkeleton = styled(
|
|
37
|
+
Skeleton,
|
|
38
|
+
omitEmotionProps('type', 'hasMargin')
|
|
39
|
+
)<StyledHeaderSkeletonProps>`
|
|
40
|
+
font-size: ${(p) => p.theme.headingsFontSize[p.type - 1] * MULTIPLIER}em;
|
|
41
|
+
${m.min.md} {
|
|
42
|
+
font-size: ${(p) => p.theme.headingsFontSizeMd[p.type - 1] * MULTIPLIER}em;
|
|
43
|
+
}
|
|
44
|
+
${hasMarginStyles};
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Provides a header placeholder while a user waits for the content to load.
|
|
49
|
+
*/
|
|
50
|
+
const HeaderSkeleton = forwardRef<HTMLDivElement, HeaderSkeletonProps>(
|
|
51
|
+
({ type = 1, hasMargin = false, width = '100%', ...rest }, ref) => (
|
|
52
|
+
<StyledHeaderSkeleton
|
|
53
|
+
type={type}
|
|
54
|
+
hasMargin={hasMargin}
|
|
55
|
+
width={width}
|
|
56
|
+
{...rest}
|
|
57
|
+
ref={ref}
|
|
58
|
+
/>
|
|
59
|
+
)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
HeaderSkeleton.displayName = 'HeaderSkeleton';
|
|
63
|
+
|
|
64
|
+
export default HeaderSkeleton;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { css } from '@emotion/react';
|
|
2
|
+
|
|
3
|
+
import styled from '@emotion/styled';
|
|
4
|
+
import { omitEmotionProps } from '@os-design/utils';
|
|
5
|
+
import React, { forwardRef, useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
const EMPTY_IMAGE =
|
|
8
|
+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mO8+R8AArcB2pIvCSwAAAAASUVORK5CYII=';
|
|
9
|
+
|
|
10
|
+
type JsxImgProps = Omit<JSX.IntrinsicElements['img'], 'sizes' | 'ref'>;
|
|
11
|
+
export interface ImageProps extends JsxImgProps {
|
|
12
|
+
/**
|
|
13
|
+
* The image URL.
|
|
14
|
+
* @default undefined
|
|
15
|
+
*/
|
|
16
|
+
url?: string;
|
|
17
|
+
/**
|
|
18
|
+
* All available sizes of the image.
|
|
19
|
+
* @default [72, 192, 512, 1024, 2560]
|
|
20
|
+
*/
|
|
21
|
+
sizes?: number[];
|
|
22
|
+
/**
|
|
23
|
+
* The image size if the browser does not support lazy loading.
|
|
24
|
+
* @default 72
|
|
25
|
+
*/
|
|
26
|
+
defaultSize?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Whether the image is cropped.
|
|
29
|
+
* @default false
|
|
30
|
+
*/
|
|
31
|
+
cropped?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Sets object-fit: cover.
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
cover?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const coverStyles = (p) =>
|
|
40
|
+
p.cover &&
|
|
41
|
+
css`
|
|
42
|
+
height: 100%;
|
|
43
|
+
object-fit: cover;
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
type StyledImageProps = Pick<ImageProps, 'cover'>;
|
|
47
|
+
const StyledImage = styled('img', omitEmotionProps('cover'))<StyledImageProps>`
|
|
48
|
+
display: block; // To remove the indent under the image
|
|
49
|
+
width: 100%;
|
|
50
|
+
border-radius: ${(p) => p.theme.borderRadius}em;
|
|
51
|
+
${coverStyles};
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The image with lazy loading. Required lazysizes.
|
|
56
|
+
* Should be loaded by @os-team/image-storage.
|
|
57
|
+
*/
|
|
58
|
+
const Image = forwardRef<HTMLImageElement, ImageProps>(
|
|
59
|
+
(
|
|
60
|
+
{
|
|
61
|
+
url,
|
|
62
|
+
sizes = [72, 192, 512, 1024, 2560],
|
|
63
|
+
defaultSize = 72,
|
|
64
|
+
cropped = false,
|
|
65
|
+
cover = false,
|
|
66
|
+
className,
|
|
67
|
+
...rest
|
|
68
|
+
},
|
|
69
|
+
ref
|
|
70
|
+
) => {
|
|
71
|
+
const getUrl = useCallback(
|
|
72
|
+
(size: number) => `${url}-${size}${cropped ? '-c' : ''}`,
|
|
73
|
+
[url, cropped]
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (!url) {
|
|
77
|
+
return (
|
|
78
|
+
<StyledImage
|
|
79
|
+
src={EMPTY_IMAGE}
|
|
80
|
+
className={className}
|
|
81
|
+
{...rest}
|
|
82
|
+
ref={ref}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<StyledImage
|
|
89
|
+
src={getUrl(defaultSize)}
|
|
90
|
+
srcSet={EMPTY_IMAGE}
|
|
91
|
+
data-sizes='auto'
|
|
92
|
+
data-srcset={sizes.map((size) => `${getUrl(size)} ${size}w`).join(', ')}
|
|
93
|
+
cover={cover}
|
|
94
|
+
className={[className, 'lazyload'].filter((i) => i).join(' ')}
|
|
95
|
+
{...rest}
|
|
96
|
+
ref={ref}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
Image.displayName = 'Image';
|
|
103
|
+
|
|
104
|
+
export default Image;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import styled from '@emotion/styled';
|
|
2
|
+
|
|
3
|
+
import React, { forwardRef } from 'react';
|
|
4
|
+
|
|
5
|
+
import Skeleton, { SkeletonProps } from '../Skeleton';
|
|
6
|
+
|
|
7
|
+
export type ImageSkeletonProps = Omit<SkeletonProps, 'width'>;
|
|
8
|
+
|
|
9
|
+
const StyledImageSkeleton = styled(Skeleton)`
|
|
10
|
+
height: 100%;
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Provides an image placeholder while a user waits for the content to load.
|
|
15
|
+
*/
|
|
16
|
+
const ImageSkeleton = forwardRef<HTMLDivElement, ImageSkeletonProps>(
|
|
17
|
+
(props, ref) => <StyledImageSkeleton width='100%' {...props} ref={ref} />
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
ImageSkeleton.displayName = 'ImageSkeleton';
|
|
21
|
+
|
|
22
|
+
export default ImageSkeleton;
|