@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,568 @@
1
+ import styled from '@emotion/styled';
2
+ import {
3
+ DatePickerLocale,
4
+ defaultLocale,
5
+ getAccessibilityDateLabel,
6
+ useFormattedValue,
7
+ } from '@os-design/date-picker-utils';
8
+ import { Down, Up } from '@os-design/icons';
9
+ import { useIsMinWidth } from '@os-design/media';
10
+ import { WithSize } from '@os-design/styles';
11
+ import { ThemeOverrider } from '@os-design/theming';
12
+ import {
13
+ useForwardedRef,
14
+ useForwardedState,
15
+ useKeyPress,
16
+ } from '@os-design/utils';
17
+ import React, {
18
+ forwardRef,
19
+ useCallback,
20
+ useEffect,
21
+ useMemo,
22
+ useRef,
23
+ useState,
24
+ } from 'react';
25
+ import Button from '../Button';
26
+ import Modal from '../Modal';
27
+ import Popover from '../Popover';
28
+ import {
29
+ ClearIcon,
30
+ Placeholder,
31
+ SelectContainer,
32
+ Title,
33
+ ToggleContent,
34
+ ToggleIconContainer,
35
+ ToggleLeftAddon,
36
+ ToggleRightAddon,
37
+ } from '../Select';
38
+ import TimePicker from '../TimePicker';
39
+ import DatePickerCalendar from './DatePickerCalendar';
40
+
41
+ type JsxDivProps = Omit<
42
+ JSX.IntrinsicElements['div'],
43
+ 'value' | 'defaultValue' | 'onChange' | 'ref'
44
+ >;
45
+ interface BaseDatePickerProps<T> extends JsxDivProps, WithSize {
46
+ /**
47
+ * The component located on the left side.
48
+ * @default undefined
49
+ */
50
+ left?: React.ReactNode;
51
+ /**
52
+ * Adds padding to the left component.
53
+ * It can be useful when passing an icon or text in the left component.
54
+ * @default false
55
+ */
56
+ leftHasPadding?: boolean;
57
+ /**
58
+ * The component located on the right side.
59
+ * @default undefined
60
+ */
61
+ right?: React.ReactNode;
62
+ /**
63
+ * Adds padding to the right component.
64
+ * It can be useful when passing an icon or text in the right component.
65
+ * @default false
66
+ */
67
+ rightHasPadding?: boolean;
68
+ /**
69
+ * The placeholder of the date picker.
70
+ * @default undefined
71
+ */
72
+ placeholder?: string;
73
+ /**
74
+ * Whether the date picker is disabled.
75
+ * @default false
76
+ */
77
+ disabled?: boolean;
78
+ /**
79
+ * Whether the component have a focus.
80
+ * @default false
81
+ */
82
+ autoFocus?: boolean;
83
+ /**
84
+ * Whether the component opens the popup calendar.
85
+ * @default false
86
+ */
87
+ autoOpen?: boolean;
88
+ /**
89
+ * The format of the date.
90
+ * @default undefined
91
+ */
92
+ format?: (value: T) => string;
93
+ /**
94
+ * The first day of the week.
95
+ * @default sunday
96
+ */
97
+ firstDayOfWeek?: 'sunday' | 'monday';
98
+ /**
99
+ * The locale of the date picker.
100
+ * @default undefined
101
+ */
102
+ locale?: DatePickerLocale;
103
+ /**
104
+ * Whether the time is shown.
105
+ * @default false
106
+ */
107
+ showTime?: boolean;
108
+ /**
109
+ * The time notation.
110
+ * @default 12-hour
111
+ */
112
+ timeNotation?: '12-hour' | '24-hour';
113
+ /**
114
+ * Selected date.
115
+ * @default undefined
116
+ */
117
+ value?: T | null;
118
+ /**
119
+ * The default value.
120
+ * @default undefined
121
+ */
122
+ defaultValue?: T | null;
123
+ /**
124
+ * The change event handler.
125
+ * @default undefined
126
+ */
127
+ onChange?: (value: T | null) => void;
128
+ /**
129
+ * The event handler that is called whenever a popup closes.
130
+ * @default undefined
131
+ */
132
+ onClose?: () => void;
133
+ }
134
+ export interface DateNotRangePickerProps extends BaseDatePickerProps<Date> {
135
+ /**
136
+ * Whether the value is a range.
137
+ * @default false
138
+ */
139
+ range?: false;
140
+ }
141
+ export interface DateRangePickerProps
142
+ extends BaseDatePickerProps<[Date, Date]> {
143
+ /**
144
+ * Whether the value is a range.
145
+ * @default false
146
+ */
147
+ range: true;
148
+ }
149
+ export type DatePickerProps = DateNotRangePickerProps | DateRangePickerProps;
150
+
151
+ const StyledPopover = styled(Popover)`
152
+ padding: ${(p) => p.theme.datePickerPadding}em;
153
+ `;
154
+
155
+ const TimeContainer = styled.div`
156
+ display: flex;
157
+ justify-content: center;
158
+ margin-top: 0.5em;
159
+ `;
160
+
161
+ const Dash = styled.div`
162
+ margin: 0 0.5em;
163
+ line-height: ${(p) => p.theme.baseHeight}em;
164
+ `;
165
+
166
+ const StyledTimePicker = styled(TimePicker)`
167
+ width: 2.5em;
168
+ text-align: center;
169
+ `;
170
+
171
+ const timePickerStyle = { style: { width: 'auto' } };
172
+
173
+ const createDate = (date: Date, time?: Date | null) => {
174
+ const d = new Date(0);
175
+ d.setFullYear(date.getFullYear());
176
+ d.setMonth(date.getMonth());
177
+ d.setDate(date.getDate());
178
+ d.setHours(0);
179
+ d.setMinutes(0);
180
+ if (time) {
181
+ d.setHours(time.getHours());
182
+ d.setMinutes(time.getMinutes());
183
+ }
184
+ return d;
185
+ };
186
+
187
+ /**
188
+ * The component to choose a date.
189
+ */
190
+ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
191
+ (
192
+ {
193
+ left,
194
+ leftHasPadding = false,
195
+ right,
196
+ rightHasPadding = false,
197
+ placeholder,
198
+ disabled = false,
199
+ autoFocus = false,
200
+ autoOpen = false,
201
+ format,
202
+ firstDayOfWeek = 'sunday',
203
+ locale = defaultLocale,
204
+ showTime = false,
205
+ timeNotation = '12-hour',
206
+ range = false,
207
+ value,
208
+ defaultValue,
209
+ onChange,
210
+ onClose = () => {},
211
+ onBlur = () => {},
212
+ size,
213
+ ...rest
214
+ },
215
+ ref
216
+ ) => {
217
+ const [containerRef, mergedContainerRef] = useForwardedRef(ref);
218
+ const [opened, setOpened] = useState(autoOpen);
219
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
220
+ const [forwardedValue, setForwardedValue] = useForwardedState<any>({
221
+ value,
222
+ defaultValue,
223
+ onChange,
224
+ });
225
+
226
+ useEffect(() => {
227
+ if (autoFocus) containerRef.current?.focus();
228
+ }, [autoFocus, containerRef]);
229
+
230
+ const onCloseRef = useRef(onClose);
231
+ useEffect(() => {
232
+ onCloseRef.current = onClose;
233
+ }, [onClose]);
234
+
235
+ useEffect(() => {
236
+ if (!opened) {
237
+ onCloseRef.current();
238
+ containerRef.current?.focus();
239
+ }
240
+ }, [containerRef, opened]);
241
+
242
+ const formattedValue = useFormattedValue({
243
+ forwardedValue,
244
+ format,
245
+ showTime,
246
+ timeNotation,
247
+ });
248
+
249
+ const closeHandler = useCallback(() => {
250
+ setOpened(false);
251
+ }, []);
252
+
253
+ useKeyPress(
254
+ (typeof window !== 'undefined' ? window : undefined) as EventTarget,
255
+ 'Escape',
256
+ closeHandler
257
+ );
258
+ const isMinXs = useIsMinWidth('xs');
259
+
260
+ const blurHandler = useCallback(
261
+ (e) => {
262
+ if (!opened) onBlur(e);
263
+ },
264
+ [onBlur, opened]
265
+ );
266
+
267
+ const valueIsSpecified = useMemo(
268
+ () => formattedValue !== undefined && formattedValue !== null,
269
+ [formattedValue]
270
+ );
271
+
272
+ const rightHasPaddingValue = useMemo(() => {
273
+ if (valueIsSpecified) return false;
274
+ return right ? rightHasPadding : true;
275
+ }, [right, rightHasPadding, valueIsSpecified]);
276
+
277
+ const rightValue = useMemo(() => {
278
+ if (valueIsSpecified) {
279
+ return (
280
+ <div aria-hidden>
281
+ <Button
282
+ type='ghost'
283
+ wide='never'
284
+ size='small'
285
+ disabled={disabled}
286
+ onClick={(e) => {
287
+ setForwardedValue(null);
288
+ e.stopPropagation();
289
+ }}
290
+ onKeyDown={(e) => {
291
+ if (disabled) return;
292
+ if (['Enter', ' '].includes(e.key)) {
293
+ setForwardedValue(null);
294
+ if (!containerRef.current) return;
295
+ containerRef.current.focus();
296
+ e.preventDefault();
297
+ e.stopPropagation();
298
+ }
299
+ }}
300
+ aria-label={locale.clearLabel}
301
+ >
302
+ <ClearIcon />
303
+ </Button>
304
+ </div>
305
+ );
306
+ }
307
+ return (
308
+ right || (
309
+ <ToggleIconContainer>
310
+ {opened ? <Up /> : <Down />}
311
+ </ToggleIconContainer>
312
+ )
313
+ );
314
+ }, [
315
+ containerRef,
316
+ disabled,
317
+ locale.clearLabel,
318
+ opened,
319
+ right,
320
+ setForwardedValue,
321
+ valueIsSpecified,
322
+ ]);
323
+
324
+ const firstTimePickerRef = useRef<HTMLInputElement>(null);
325
+ const secondTimePickerRef = useRef<HTMLInputElement>(null);
326
+ const rangeStartedRef = useRef(false);
327
+
328
+ useEffect(() => {
329
+ rangeStartedRef.current = false;
330
+ }, [opened]);
331
+
332
+ const changeHandler = useCallback(
333
+ (date: Date) => {
334
+ setForwardedValue((prev) => {
335
+ if (!range) return createDate(date, prev);
336
+
337
+ const d = createDate(date);
338
+ const nextValue =
339
+ prev && Array.isArray(prev) && rangeStartedRef.current
340
+ ? [new Date(prev[0].getTime()), createDate(date, prev[1])]
341
+ : [d, d];
342
+
343
+ rangeStartedRef.current = !rangeStartedRef.current;
344
+ return nextValue.sort(
345
+ (a: Date, b: Date) => a.getTime() - b.getTime()
346
+ );
347
+ });
348
+ },
349
+ [range, setForwardedValue]
350
+ );
351
+
352
+ const calendarComponent = useMemo(
353
+ () => (
354
+ <DatePickerCalendar
355
+ firstDayOfWeek={firstDayOfWeek}
356
+ locale={locale}
357
+ value={forwardedValue}
358
+ onSelect={(v) => {
359
+ changeHandler(v);
360
+ firstTimePickerRef.current?.focus();
361
+ if (!showTime && (!range || !rangeStartedRef.current)) {
362
+ closeHandler();
363
+ }
364
+ }}
365
+ />
366
+ ),
367
+ [
368
+ changeHandler,
369
+ closeHandler,
370
+ firstDayOfWeek,
371
+ forwardedValue,
372
+ locale,
373
+ range,
374
+ showTime,
375
+ ]
376
+ );
377
+
378
+ const tabOnInputComplete = useCallback(
379
+ (e) => {
380
+ if (e.target.selectionStart < 5) return;
381
+ secondTimePickerRef.current?.focus();
382
+ setForwardedValue(([from]) => {
383
+ const nextTo = new Date(from.getTime());
384
+ nextTo.setHours(nextTo.getHours() + 1);
385
+ return [from, nextTo];
386
+ });
387
+ },
388
+ [setForwardedValue]
389
+ );
390
+
391
+ const closeOnInputComplete = useCallback(
392
+ (e) => {
393
+ if (e.target.selectionStart < 5) return;
394
+ closeHandler();
395
+ },
396
+ [closeHandler]
397
+ );
398
+
399
+ const timeComponent = useMemo(
400
+ () =>
401
+ showTime && forwardedValue ? (
402
+ <TimeContainer>
403
+ {Array.isArray(forwardedValue) ? (
404
+ <>
405
+ <StyledTimePicker
406
+ ref={firstTimePickerRef}
407
+ autoFocus
408
+ disabled={disabled}
409
+ notation={timeNotation}
410
+ containerProps={timePickerStyle}
411
+ value={forwardedValue[0]}
412
+ onChange={(v) => setForwardedValue([v, forwardedValue[1]])}
413
+ onKeyDown={(e) => {
414
+ if (disabled) return;
415
+ if (e.key === 'Enter') {
416
+ setOpened(!opened);
417
+ }
418
+ }}
419
+ onKeyUp={tabOnInputComplete}
420
+ />
421
+ <Dash>–</Dash>
422
+ <StyledTimePicker
423
+ ref={secondTimePickerRef}
424
+ disabled={disabled}
425
+ notation={timeNotation}
426
+ containerProps={timePickerStyle}
427
+ value={forwardedValue[1]}
428
+ onChange={(v) => setForwardedValue([forwardedValue[0], v])}
429
+ onKeyDown={(e) => {
430
+ if (disabled) return;
431
+ if (e.key === 'Enter') {
432
+ setOpened(!opened);
433
+ }
434
+ }}
435
+ onKeyUp={closeOnInputComplete}
436
+ />
437
+ </>
438
+ ) : (
439
+ <StyledTimePicker
440
+ ref={firstTimePickerRef}
441
+ autoFocus
442
+ disabled={disabled}
443
+ notation={timeNotation}
444
+ containerProps={timePickerStyle}
445
+ value={forwardedValue}
446
+ onChange={setForwardedValue}
447
+ onKeyDown={(e) => {
448
+ if (disabled) return;
449
+ if (e.key === 'Enter') {
450
+ setOpened(!opened);
451
+ }
452
+ }}
453
+ onKeyUp={closeOnInputComplete}
454
+ />
455
+ )}
456
+ </TimeContainer>
457
+ ) : null,
458
+ [
459
+ closeOnInputComplete,
460
+ disabled,
461
+ forwardedValue,
462
+ opened,
463
+ setForwardedValue,
464
+ showTime,
465
+ tabOnInputComplete,
466
+ timeNotation,
467
+ ]
468
+ );
469
+
470
+ return (
471
+ <>
472
+ <SelectContainer
473
+ opened={opened}
474
+ disabled={disabled}
475
+ size={size}
476
+ tabIndex={!disabled ? 0 : -1}
477
+ onClick={() => {
478
+ if (disabled) return;
479
+ setOpened(!opened);
480
+ }}
481
+ onKeyDown={(e) => {
482
+ if (disabled) return;
483
+ if (['Enter', ' '].includes(e.key)) {
484
+ setOpened(!opened);
485
+ e.preventDefault();
486
+ }
487
+ }}
488
+ onMouseDown={(e) => e.preventDefault()}
489
+ onBlur={blurHandler}
490
+ role='combobox'
491
+ aria-label={
492
+ forwardedValue
493
+ ? getAccessibilityDateLabel(forwardedValue, locale)
494
+ : undefined
495
+ }
496
+ aria-disabled={disabled}
497
+ {...rest}
498
+ ref={mergedContainerRef}
499
+ >
500
+ {left && (
501
+ <ThemeOverrider
502
+ overrides={(t) => ({
503
+ buttonPaddingHorizontal: 0.8,
504
+ baseHeight: t.selectToggleListItemHeight / t.sizes.small,
505
+ })}
506
+ >
507
+ <ToggleLeftAddon hasPadding={leftHasPadding}>
508
+ {left}
509
+ </ToggleLeftAddon>
510
+ </ThemeOverrider>
511
+ )}
512
+
513
+ <ToggleContent hasLeft={!!left} hasRight={!!right}>
514
+ {valueIsSpecified ? (
515
+ <Title disabled={disabled} aria-hidden>
516
+ {formattedValue}
517
+ </Title>
518
+ ) : (
519
+ <Placeholder>{placeholder}</Placeholder>
520
+ )}
521
+ </ToggleContent>
522
+
523
+ {rightValue && (
524
+ <ThemeOverrider
525
+ overrides={(t) => ({
526
+ buttonPaddingHorizontal: 0.8,
527
+ baseHeight: t.selectToggleListItemHeight / t.sizes.small,
528
+ })}
529
+ >
530
+ <ToggleRightAddon hasPadding={rightHasPaddingValue}>
531
+ {rightValue}
532
+ </ToggleRightAddon>
533
+ </ThemeOverrider>
534
+ )}
535
+ </SelectContainer>
536
+
537
+ {isMinXs ? (
538
+ <StyledPopover
539
+ trigger={containerRef}
540
+ placement='bottom-start'
541
+ visible={opened}
542
+ onClose={closeHandler}
543
+ size={size}
544
+ >
545
+ {calendarComponent}
546
+ {timeComponent}
547
+ </StyledPopover>
548
+ ) : (
549
+ <Modal
550
+ title={placeholder}
551
+ header={null}
552
+ footer={null}
553
+ visible={opened}
554
+ onClose={closeHandler}
555
+ size={size}
556
+ >
557
+ {calendarComponent}
558
+ {timeComponent}
559
+ </Modal>
560
+ )}
561
+ </>
562
+ );
563
+ }
564
+ );
565
+
566
+ DatePicker.displayName = 'DatePicker';
567
+
568
+ export default DatePicker;