@os-design/core 1.0.199 → 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.
Files changed (99) hide show
  1. package/package.json +21 -13
  2. package/src/@types/emotion.d.ts +7 -0
  3. package/src/Alert/index.tsx +112 -0
  4. package/src/Avatar/index.tsx +173 -0
  5. package/src/Avatar/utils/nameToInitials.ts +12 -0
  6. package/src/Avatar/utils/strToHue.ts +13 -0
  7. package/src/AvatarSkeleton/index.tsx +29 -0
  8. package/src/Breadcrumb/index.tsx +93 -0
  9. package/src/BreadcrumbItem/index.tsx +83 -0
  10. package/src/Button/ButtonContent.tsx +91 -0
  11. package/src/Button/index.tsx +225 -0
  12. package/src/Button/utils/useButtonColors.ts +84 -0
  13. package/src/Checkbox/index.tsx +225 -0
  14. package/src/CheckboxSkeleton/index.tsx +50 -0
  15. package/src/DatePicker/DatePickerCalendar.tsx +220 -0
  16. package/src/DatePicker/index.tsx +568 -0
  17. package/src/Drawer/index.tsx +212 -0
  18. package/src/Form/FormConfigContext.ts +16 -0
  19. package/src/Form/index.tsx +49 -0
  20. package/src/FormDivider/index.tsx +74 -0
  21. package/src/FormItem/index.tsx +118 -0
  22. package/src/Gallery/Status.tsx +62 -0
  23. package/src/Gallery/index.tsx +290 -0
  24. package/src/GlobalStyles/index.tsx +17 -0
  25. package/src/GlobalStyles/resetStyles.ts +17 -0
  26. package/src/GlobalStyles/typographyStyles.ts +78 -0
  27. package/src/HeaderSkeleton/index.tsx +64 -0
  28. package/src/Image/index.tsx +104 -0
  29. package/src/ImageSkeleton/index.tsx +22 -0
  30. package/src/Input/index.tsx +330 -0
  31. package/src/Input/utils/getFocusableElements.ts +8 -0
  32. package/src/InputNumber/index.tsx +208 -0
  33. package/src/InputNumber/utils/defaultLocale.ts +9 -0
  34. package/src/InputPassword/index.tsx +201 -0
  35. package/src/InputPassword/utils/defaultLocale.ts +11 -0
  36. package/src/InputSearch/index.tsx +111 -0
  37. package/src/InputSearch/utils/defaultLocale.ts +9 -0
  38. package/src/InputSkeleton/index.tsx +28 -0
  39. package/src/Layout/LayoutContext.ts +21 -0
  40. package/src/Layout/index.tsx +44 -0
  41. package/src/Link/index.tsx +129 -0
  42. package/src/LinkButton/index.tsx +100 -0
  43. package/src/List/WindowScroller.tsx +53 -0
  44. package/src/List/index.tsx +255 -0
  45. package/src/List/utils/bodyPointerEvents.ts +24 -0
  46. package/src/List/utils/frameTimeout.ts +36 -0
  47. package/src/List/utils/useRWLoadNext.ts +38 -0
  48. package/src/ListItem/index.tsx +92 -0
  49. package/src/ListItemActions/index.tsx +207 -0
  50. package/src/ListItemLink/index.tsx +63 -0
  51. package/src/ListSkeleton/index.tsx +115 -0
  52. package/src/LogoLink/index.tsx +93 -0
  53. package/src/LogoLink/logo.example.svg +18 -0
  54. package/src/Menu/index.tsx +128 -0
  55. package/src/Menu/utils/useFocusWithArrows.ts +50 -0
  56. package/src/MenuDivider/index.tsx +22 -0
  57. package/src/MenuGroup/index.tsx +190 -0
  58. package/src/MenuItem/index.tsx +108 -0
  59. package/src/Modal/index.tsx +411 -0
  60. package/src/Modal/utils/defaultLocale.ts +9 -0
  61. package/src/Navigation/index.tsx +214 -0
  62. package/src/Navigation/utils/useScrollFlags.ts +39 -0
  63. package/src/NavigationItem/index.tsx +136 -0
  64. package/src/PageContent/index.tsx +99 -0
  65. package/src/PageHeader/index.tsx +246 -0
  66. package/src/PageHeader/utils/defaultLocale.ts +9 -0
  67. package/src/PageHeaderInputSearch/index.tsx +145 -0
  68. package/src/PageHeaderInputSearch/utils/defaultLocale.ts +16 -0
  69. package/src/PageHeaderSkeleton/index.tsx +33 -0
  70. package/src/ParagraphSkeleton/index.tsx +65 -0
  71. package/src/Popover/index.tsx +243 -0
  72. package/src/Popover/utils/usePopoverPosition.ts +216 -0
  73. package/src/Progress/index.tsx +100 -0
  74. package/src/RadioGroup/index.tsx +165 -0
  75. package/src/RadioGroupSkeleton/index.tsx +36 -0
  76. package/src/Result/index.tsx +109 -0
  77. package/src/ScrollButton/index.tsx +159 -0
  78. package/src/ScrollButton/utils/useContainerPosition.ts +41 -0
  79. package/src/ScrollButton/utils/useVisibility.ts +56 -0
  80. package/src/Select/index.tsx +970 -0
  81. package/src/Select/utils/defaultLocale.ts +11 -0
  82. package/src/Skeleton/index.tsx +52 -0
  83. package/src/Switch/index.tsx +217 -0
  84. package/src/SwitchSkeleton/index.tsx +30 -0
  85. package/src/Tag/index.tsx +75 -0
  86. package/src/TagLink/index.tsx +53 -0
  87. package/src/TagList/index.tsx +95 -0
  88. package/src/TagListSkeleton/index.tsx +38 -0
  89. package/src/TagSkeleton/index.tsx +40 -0
  90. package/src/TextArea/index.tsx +231 -0
  91. package/src/TextAreaSkeleton/index.tsx +20 -0
  92. package/src/ThemeSwitcher/index.tsx +39 -0
  93. package/src/TimePicker/index.tsx +142 -0
  94. package/src/Video/index.tsx +41 -0
  95. package/src/index.ts +125 -0
  96. package/src/message/AlertIcon.tsx +50 -0
  97. package/src/message/Message.tsx +108 -0
  98. package/src/message/index.tsx +64 -0
  99. package/src/message/styles.ts +25 -0
@@ -0,0 +1,108 @@
1
+ import { css } from '@emotion/react';
2
+ import styled from '@emotion/styled';
3
+ import { Check } from '@os-design/icons';
4
+ import { m } from '@os-design/media';
5
+ import { MenuContext } from '@os-design/menu-utils';
6
+ import { ThemeOverrider, clr } from '@os-design/theming';
7
+ import { omitEmotionProps } from '@os-design/utils';
8
+
9
+ import React, {
10
+ forwardRef,
11
+ useCallback,
12
+ useContext,
13
+ useEffect,
14
+ useRef,
15
+ } from 'react';
16
+ import Button, { ButtonProps } from '../Button';
17
+
18
+ export interface MenuItemProps
19
+ extends Omit<ButtonProps, 'type' | 'wide' | 'size'> {
20
+ /**
21
+ * Whether the menu item is selected.
22
+ * @default false
23
+ */
24
+ selected?: boolean;
25
+ /**
26
+ * The value of the menu item.
27
+ * @default undefined
28
+ */
29
+ value?: string;
30
+ }
31
+
32
+ const selectedStyles = (p) =>
33
+ p.selected &&
34
+ css`
35
+ background-color: ${clr(p.theme.menuItemSelectedColorBg)};
36
+ `;
37
+
38
+ type StyledButtonProps = Pick<MenuItemProps, 'selected'>;
39
+ const StyledButton = styled(
40
+ Button,
41
+ omitEmotionProps('selected')
42
+ )<StyledButtonProps>`
43
+ display: flex;
44
+ font-weight: normal;
45
+ border-radius: 0;
46
+ height: ${(p) => p.theme.menuItemHeight}em;
47
+
48
+ & > span {
49
+ flex: 1;
50
+ text-align: left;
51
+ overflow: hidden;
52
+ line-height: 1.5;
53
+ }
54
+
55
+ padding: 0 ${(p) => p.theme.menuItemPaddingHorizontal[0]}em;
56
+ ${m.min.xs} {
57
+ padding: 0 ${(p) => p.theme.menuItemPaddingHorizontal[1]}em;
58
+ }
59
+
60
+ ${selectedStyles};
61
+ `;
62
+
63
+ const SelectedIcon = styled(Check)`
64
+ color: ${(p) => clr(p.theme.menuItemSelectedColorIcon)};
65
+ `;
66
+
67
+ /**
68
+ * The base menu item.
69
+ */
70
+ const MenuItem = forwardRef<HTMLButtonElement, MenuItemProps>(
71
+ ({ selected = false, value, right, onClick = () => {}, ...rest }, ref) => {
72
+ const { closeOnSelect, onClose } = useContext(MenuContext);
73
+ const onClickRef = useRef<MenuItemProps['onClick']>();
74
+
75
+ useEffect(() => {
76
+ onClickRef.current = onClick;
77
+ }, [onClick]);
78
+
79
+ const clickHandler = useCallback(
80
+ (e) => {
81
+ if (onClickRef.current) onClickRef.current(e);
82
+ if (closeOnSelect) onClose();
83
+ },
84
+ [closeOnSelect, onClose]
85
+ );
86
+
87
+ return (
88
+ <ThemeOverrider
89
+ overrides={(theme) => ({ buttonGhostColorText: theme.colorText })}
90
+ >
91
+ <StyledButton
92
+ selected={selected}
93
+ right={selected ? <SelectedIcon /> : right}
94
+ type='ghost'
95
+ wide='always'
96
+ onClick={clickHandler}
97
+ role='menuitem'
98
+ {...rest}
99
+ ref={ref}
100
+ />
101
+ </ThemeOverrider>
102
+ );
103
+ }
104
+ );
105
+
106
+ MenuItem.displayName = 'MenuItem';
107
+
108
+ export default MenuItem;
@@ -0,0 +1,411 @@
1
+ import { css, keyframes } from '@emotion/react';
2
+ import styled from '@emotion/styled';
3
+ import { Close } from '@os-design/icons';
4
+ import { m } from '@os-design/media';
5
+ import Portal from '@os-design/portal';
6
+ import {
7
+ WithSize,
8
+ ellipsisStyles,
9
+ enableScrollingStyles,
10
+ sizeStyles,
11
+ } from '@os-design/styles';
12
+ import { ThemeOverrider, clr, useTheme } from '@os-design/theming';
13
+
14
+ import { omitEmotionProps, useBodyScroll, useClosable } from '@os-design/utils';
15
+ import React, {
16
+ forwardRef,
17
+ useCallback,
18
+ useContext,
19
+ useMemo,
20
+ useRef,
21
+ } from 'react';
22
+ import FocusLock from 'react-focus-lock';
23
+ import Button from '../Button';
24
+ import defaultLocale, { ModalLocale } from './utils/defaultLocale';
25
+
26
+ type JsxDivProps = Omit<JSX.IntrinsicElements['div'], 'ref'>;
27
+ export interface ModalProps extends JsxDivProps, WithSize {
28
+ /**
29
+ * The title of the modal.
30
+ * @default undefined
31
+ */
32
+ title?: string;
33
+ /**
34
+ * The text of the OK button.
35
+ * @default OK
36
+ */
37
+ okText?: string;
38
+ /**
39
+ * Sets the danger styles to the OK button.
40
+ * @default false
41
+ */
42
+ okDanger?: boolean;
43
+ /**
44
+ * Whether the OK button is loading.
45
+ * @default false
46
+ */
47
+ okLoading?: boolean;
48
+ /**
49
+ * Whether the OK button is disabled.
50
+ * @default false
51
+ */
52
+ okDisabled?: boolean;
53
+ /**
54
+ * The header component.
55
+ * Set as null if you don't need the default header.
56
+ * @default undefined
57
+ */
58
+ header?: React.ReactNode;
59
+ /**
60
+ * The footer component.
61
+ * Set as null if you don't need the default footer.
62
+ * @default undefined
63
+ */
64
+ footer?: React.ReactNode;
65
+ /**
66
+ * Whether the modal is visible.
67
+ * @default false
68
+ */
69
+ visible?: boolean;
70
+ /**
71
+ * The locale of the modal.
72
+ * @default undefined
73
+ */
74
+ locale?: ModalLocale;
75
+ /**
76
+ * Specifies a callback that will be called when a user clicks the mask or
77
+ * the close button. The callback should set the visible state to false.
78
+ * @default undefined
79
+ */
80
+ onClose?: () => void;
81
+ /**
82
+ * Specifies a callback that will be called when a user clicks the OK button.
83
+ * @default undefined
84
+ */
85
+ onOk?: () => void;
86
+ }
87
+
88
+ const MODAL_CONTAINER_Z_INDEX = 1000;
89
+ const MODAL_BOX_SHADOW_SIZE_EM = 1;
90
+
91
+ const maskFadeIn = keyframes`
92
+ from { opacity: 0; }
93
+ to { opacity: 1; }
94
+ `;
95
+
96
+ const maskFadeOut = keyframes`
97
+ from { opacity: 1; }
98
+ to { opacity: 0; }
99
+ `;
100
+
101
+ const maskVisibleStyles = (p) =>
102
+ p.visible &&
103
+ css`
104
+ animation: ${maskFadeIn} ${p.theme.transitionDelay}ms forwards;
105
+ `;
106
+
107
+ const maskInvisibleStyles = (p) =>
108
+ !p.visible &&
109
+ css`
110
+ animation: ${maskFadeOut} ${p.theme.transitionDelay}ms forwards;
111
+ `;
112
+
113
+ type MaskProps = Pick<ModalProps, 'visible'>;
114
+ export const ModalMask = styled('div', omitEmotionProps('visible'))<MaskProps>`
115
+ position: fixed;
116
+ top: 0;
117
+ left: 0;
118
+ right: 0;
119
+ height: 100%;
120
+ background-color: ${(p) => clr(p.theme.modalMaskColorBg)};
121
+ z-index: ${MODAL_CONTAINER_Z_INDEX};
122
+ ${maskVisibleStyles};
123
+ ${maskInvisibleStyles};
124
+ `;
125
+
126
+ const Container = styled.div`
127
+ position: fixed;
128
+ top: 0;
129
+ left: 0;
130
+ right: 0;
131
+ height: 100%;
132
+ z-index: ${MODAL_CONTAINER_Z_INDEX};
133
+
134
+ ${m.min.xs} {
135
+ ${enableScrollingStyles('y')};
136
+ }
137
+ `;
138
+
139
+ const contentFadeIn = keyframes`
140
+ from { transform: translateY(calc(var(--vh, 1vh) * 100 + ${MODAL_BOX_SHADOW_SIZE_EM}em)); }
141
+ to { transform: translateY(calc(var(--vh, 1vh) * 100 - 100%)); }
142
+ `;
143
+
144
+ const contentFadeOut = keyframes`
145
+ from { transform: translateY(calc(var(--vh, 1vh) * 100 - 100%)); }
146
+ to { transform: translateY(calc(var(--vh, 1vh) * 100 + ${MODAL_BOX_SHADOW_SIZE_EM}em)); }
147
+ `;
148
+
149
+ const contentFadeInXs = keyframes`
150
+ from { transform: translateY(calc(-100% - ${MODAL_BOX_SHADOW_SIZE_EM}em)); }
151
+ to { transform: translateY(6em); }
152
+ `;
153
+
154
+ const contentFadeOutXs = keyframes`
155
+ from { transform: translateY(6em); }
156
+ to { transform: translateY(calc(-100% - ${MODAL_BOX_SHADOW_SIZE_EM}em)); }
157
+ `;
158
+
159
+ const contentVisibleStyles = (p) =>
160
+ p.visible &&
161
+ css`
162
+ animation: ${contentFadeIn} ${p.theme.transitionDelay}ms forwards;
163
+ ${m.min.xs} {
164
+ animation: ${contentFadeInXs} ${p.theme.transitionDelay}ms forwards;
165
+ }
166
+ `;
167
+
168
+ const contentInvisibleStyles = (p) =>
169
+ !p.visible &&
170
+ css`
171
+ animation: ${contentFadeOut} ${p.theme.transitionDelay}ms forwards;
172
+ ${m.min.xs} {
173
+ animation: ${contentFadeOutXs} ${p.theme.transitionDelay}ms forwards;
174
+ }
175
+ `;
176
+
177
+ type ContentProps = Pick<ModalProps, 'visible' | 'size'>;
178
+ const Content = styled(
179
+ 'div',
180
+ omitEmotionProps('visible', 'size')
181
+ )<ContentProps>`
182
+ position: absolute;
183
+ display: flex;
184
+ flex-direction: column;
185
+ padding-bottom: env(safe-area-inset-bottom);
186
+
187
+ background-color: ${(p) => clr(p.theme.colorBg)};
188
+ color: ${(p) => clr(p.theme.colorText)};
189
+ box-shadow: 0 0 ${MODAL_BOX_SHADOW_SIZE_EM}em
190
+ ${(p) => clr(p.theme.modalColorBoxShadow)};
191
+
192
+ width: 100%;
193
+ max-height: 100%;
194
+ border-radius: ${(p) => p.theme.borderRadius}em
195
+ ${(p) => p.theme.borderRadius}em 0 0;
196
+
197
+ ${m.min.xs} {
198
+ width: ${(p) => p.theme.modalWidth}em;
199
+ max-height: unset;
200
+ border-radius: ${(p) => p.theme.borderRadius}em;
201
+
202
+ left: 50%;
203
+ margin-left: ${(p) => -p.theme.modalWidth / 2}em;
204
+ }
205
+
206
+ ${contentVisibleStyles};
207
+ ${contentInvisibleStyles}
208
+ ${sizeStyles};
209
+ `;
210
+
211
+ const Header = styled.div`
212
+ flex-shrink: 0;
213
+ height: ${(p) => p.theme.modalHeaderHeight}em;
214
+ border-bottom: 1px solid ${(p) => clr(p.theme.modalHeaderColorBorderBottom)};
215
+ box-sizing: border-box;
216
+
217
+ display: flex;
218
+ align-items: center;
219
+
220
+ padding-left: ${(p) => p.theme.modalBodyPaddingHorizontal[0]}em;
221
+ padding-right: ${(p) =>
222
+ Math.max(
223
+ p.theme.modalBodyPaddingHorizontal[1] - p.theme.buttonPaddingHorizontal,
224
+ 0
225
+ )}em;
226
+
227
+ ${m.min.xs} {
228
+ padding-left: ${(p) => p.theme.modalBodyPaddingHorizontal[1]}em;
229
+ padding-right: ${(p) =>
230
+ Math.max(
231
+ p.theme.modalBodyPaddingHorizontal[1] - p.theme.buttonPaddingHorizontal,
232
+ 0
233
+ )}em;
234
+ }
235
+ `;
236
+
237
+ const Title = styled.div`
238
+ flex: 1;
239
+ font-size: ${(p) => p.theme.sizes.large}em;
240
+ font-weight: 500;
241
+ ${ellipsisStyles};
242
+ `;
243
+
244
+ const Body = styled.div`
245
+ flex-grow: 1;
246
+
247
+ padding: ${(p) => p.theme.modalBodyPaddingVertical[0]}em
248
+ ${(p) => p.theme.modalBodyPaddingHorizontal[0]}em;
249
+
250
+ ${m.min.xs} {
251
+ padding: ${(p) => p.theme.modalBodyPaddingVertical[1]}em
252
+ ${(p) => p.theme.modalBodyPaddingHorizontal[1]}em;
253
+ }
254
+
255
+ ${enableScrollingStyles('y')};
256
+ `;
257
+
258
+ const Footer = styled.div`
259
+ padding: 0 ${(p) => p.theme.modalBodyPaddingHorizontal[0]}em
260
+ ${(p) => p.theme.modalBodyPaddingVertical[0]}em;
261
+
262
+ ${m.min.xs} {
263
+ display: flex;
264
+ justify-content: flex-end;
265
+ align-items: center;
266
+
267
+ padding: 0 ${(p) => p.theme.modalBodyPaddingHorizontal[1]}em
268
+ ${(p) => p.theme.modalBodyPaddingVertical[1]}em;
269
+ }
270
+ `;
271
+
272
+ const CloseModalContext = React.createContext<() => void>(() => {});
273
+
274
+ export const useCloseModal = () => useContext(CloseModalContext);
275
+
276
+ /**
277
+ * The base pop-up window.
278
+ */
279
+ const Modal = forwardRef<HTMLDivElement, ModalProps>(
280
+ (
281
+ {
282
+ title,
283
+ okText = 'OK',
284
+ okDanger = false,
285
+ okLoading = false,
286
+ okDisabled = false,
287
+ header,
288
+ footer,
289
+ visible = false,
290
+ locale = defaultLocale,
291
+ onClose = () => {},
292
+ onOk,
293
+ size,
294
+ id,
295
+ onClick = () => {},
296
+ children,
297
+ ...rest
298
+ },
299
+ ref
300
+ ) => {
301
+ const contentRef = useRef<HTMLDivElement>(null);
302
+ const { theme } = useTheme();
303
+ const mounted = useClosable(visible, theme.transitionDelay);
304
+
305
+ useBodyScroll(!visible);
306
+
307
+ const titleId = useMemo(
308
+ () => `modal-title-${Math.random().toString(36).slice(2, 11)}`,
309
+ []
310
+ );
311
+ const bodyId = useMemo(
312
+ () => id || `modal-body-${Math.random().toString(36).slice(2, 11)}`,
313
+ [id]
314
+ );
315
+
316
+ const clickHandler = useCallback(
317
+ (e) => {
318
+ e.stopPropagation();
319
+ onClick(e);
320
+ },
321
+ [onClick]
322
+ );
323
+
324
+ const keyDownHandler = useCallback(
325
+ (e) => {
326
+ if (e.key === 'Escape') {
327
+ e.stopPropagation();
328
+ onClose();
329
+ }
330
+ },
331
+ [onClose]
332
+ );
333
+
334
+ const ariaLabelledBy = useMemo(
335
+ () => (header === undefined ? titleId : undefined),
336
+ [header, titleId]
337
+ );
338
+
339
+ if (!mounted) return null;
340
+
341
+ return (
342
+ <Portal>
343
+ <ModalMask visible={visible} />
344
+ <Container onClick={onClose}>
345
+ <FocusLock autoFocus>
346
+ <CloseModalContext.Provider value={onClose}>
347
+ <Content
348
+ visible={visible}
349
+ size={size}
350
+ tabIndex={-1}
351
+ onKeyDown={keyDownHandler}
352
+ onClick={clickHandler}
353
+ role='dialog'
354
+ aria-modal
355
+ aria-labelledby={ariaLabelledBy}
356
+ aria-describedby={bodyId}
357
+ ref={contentRef}
358
+ >
359
+ {header === undefined ? (
360
+ <ThemeOverrider
361
+ overrides={(t) => ({
362
+ buttonPaddingHorizontal:
363
+ t.modalCloseButtonPaddingHorizontal,
364
+ })}
365
+ >
366
+ <Header>
367
+ <Title id={titleId}>{title}</Title>
368
+ <Button
369
+ type='ghost'
370
+ wide='never'
371
+ onClick={onClose}
372
+ aria-label={locale.closeLabel}
373
+ >
374
+ <Close />
375
+ </Button>
376
+ </Header>
377
+ </ThemeOverrider>
378
+ ) : (
379
+ header
380
+ )}
381
+
382
+ <Body id={bodyId} {...rest} ref={ref}>
383
+ {children}
384
+ </Body>
385
+
386
+ {footer === undefined ? (
387
+ <Footer>
388
+ <Button
389
+ danger={okDanger}
390
+ loading={okLoading}
391
+ disabled={okDisabled}
392
+ onClick={onOk}
393
+ >
394
+ {okText}
395
+ </Button>
396
+ </Footer>
397
+ ) : (
398
+ footer
399
+ )}
400
+ </Content>
401
+ </CloseModalContext.Provider>
402
+ </FocusLock>
403
+ </Container>
404
+ </Portal>
405
+ );
406
+ }
407
+ );
408
+
409
+ Modal.displayName = 'Modal';
410
+
411
+ export default Modal;
@@ -0,0 +1,9 @@
1
+ export interface ModalLocale {
2
+ closeLabel: string;
3
+ }
4
+
5
+ const defaultLocale: ModalLocale = {
6
+ closeLabel: 'Close',
7
+ };
8
+
9
+ export default defaultLocale;