@os-design/core 1.0.199 → 1.0.201

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 +20 -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,246 @@
1
+ import { css } from '@emotion/react';
2
+ import styled from '@emotion/styled';
3
+ import { Left } from '@os-design/icons';
4
+ import { m } from '@os-design/media';
5
+
6
+ import { ellipsisStyles } from '@os-design/styles';
7
+ import { ThemeOverrider, clr } from '@os-design/theming';
8
+
9
+ import { omitEmotionProps } from '@os-design/utils';
10
+ import React, { forwardRef, useContext } from 'react';
11
+
12
+ import Button from '../Button';
13
+ import LayoutContext from '../Layout/LayoutContext';
14
+ import defaultLocale, { PageHeaderLocale } from './utils/defaultLocale';
15
+
16
+ type JsxDivProps = Omit<JSX.IntrinsicElements['div'], 'ref'>;
17
+ export interface PageHeaderProps extends JsxDivProps {
18
+ /**
19
+ * The title of the page.
20
+ * @default undefined
21
+ */
22
+ title: string;
23
+ /**
24
+ * The subtitle of the page.
25
+ * @default undefined
26
+ */
27
+ subtitle?: string;
28
+ /**
29
+ * The component located on the left side.
30
+ * @default undefined
31
+ */
32
+ left?: React.ReactNode;
33
+ /**
34
+ * Reduces the left padding of the page header.
35
+ * @default false
36
+ */
37
+ leftIsGhostButton?: boolean;
38
+ /**
39
+ * The component located on the right side.
40
+ * @default undefined
41
+ */
42
+ right?: React.ReactNode;
43
+ /**
44
+ * Reduces the right padding of the page header.
45
+ * @default false
46
+ */
47
+ rightIsGhostButton?: boolean;
48
+ /**
49
+ * The back event handler. If passed, the page header has the back button.
50
+ * @default undefined
51
+ */
52
+ onBack?: () => void;
53
+ /**
54
+ * The locale.
55
+ * @default undefined
56
+ */
57
+ locale?: PageHeaderLocale;
58
+ }
59
+
60
+ const getReducedPadding = (p, isMinSm: boolean) => {
61
+ const i = isMinSm ? 1 : 0;
62
+ let padding = p.theme.horizontalPadding[i] - p.theme.buttonPaddingHorizontal;
63
+ if (padding < 0) padding = 0;
64
+ return padding;
65
+ };
66
+
67
+ const horizontalPaddingStyles = (p) => {
68
+ const reducedPadding = getReducedPadding(p, false);
69
+ const reducedPaddingSm = getReducedPadding(p, true);
70
+ return css`
71
+ padding-left: ${p.leftIsGhostButton
72
+ ? reducedPadding
73
+ : p.theme.horizontalPadding[0]}em;
74
+ padding-right: ${p.rightIsGhostButton
75
+ ? reducedPadding
76
+ : p.theme.horizontalPadding[0]}em;
77
+
78
+ ${m.min.sm} {
79
+ padding-left: ${p.leftIsGhostButton
80
+ ? reducedPaddingSm
81
+ : p.theme.horizontalPadding[1]}em;
82
+ padding-right: ${p.rightIsGhostButton
83
+ ? reducedPaddingSm
84
+ : p.theme.horizontalPadding[1]}em;
85
+ }
86
+ `;
87
+ };
88
+
89
+ const hasNavigationIndentStyles = (p) =>
90
+ p.hasNavigationIndent &&
91
+ css`
92
+ ${m.min.md} {
93
+ left: ${p.theme.navigationSideWidth}em;
94
+ }
95
+ `;
96
+
97
+ interface ContainerProps {
98
+ leftIsGhostButton?: boolean;
99
+ rightIsGhostButton?: boolean;
100
+ hasNavigationIndent?: boolean;
101
+ }
102
+ export const PageHeaderContainer = styled(
103
+ 'div',
104
+ omitEmotionProps(
105
+ 'leftIsGhostButton',
106
+ 'rightIsGhostButton',
107
+ 'hasNavigationIndent'
108
+ )
109
+ )<ContainerProps>`
110
+ position: fixed;
111
+ top: 0;
112
+ left: 0;
113
+ right: 0;
114
+
115
+ height: ${(p) => p.theme.pageHeaderHeight[0]}em;
116
+ ${m.min.md} {
117
+ height: ${(p) => p.theme.pageHeaderHeight[1]}em;
118
+ }
119
+
120
+ display: flex;
121
+ align-items: center;
122
+ box-sizing: border-box;
123
+ z-index: 101;
124
+
125
+ background-color: ${(p) => clr(p.theme.pageHeaderColorBg)};
126
+ color: ${(p) => clr(p.theme.pageHeaderColorText)};
127
+ border-bottom: 1px solid ${(p) => clr(p.theme.pageHeaderColorBorder)};
128
+
129
+ ${horizontalPaddingStyles};
130
+ ${hasNavigationIndentStyles};
131
+ `;
132
+
133
+ const BackButton = styled(Button)`
134
+ margin-right: 0.2em;
135
+ flex-shrink: 0;
136
+ `;
137
+
138
+ const Content = styled.div`
139
+ overflow: hidden; // For ellipsis
140
+ `;
141
+
142
+ const notHasSubtitleStyles = (p) =>
143
+ !p.hasSubtitle &&
144
+ css`
145
+ font-size: ${p.theme.sizes.large}em;
146
+ `;
147
+
148
+ interface TitleProps {
149
+ hasSubtitle: boolean;
150
+ }
151
+ const Title = styled('h1', omitEmotionProps('hasSubtitle'))<TitleProps>`
152
+ margin: 0;
153
+ font-size: 1em;
154
+ font-weight: 500;
155
+ line-height: 1.2;
156
+ ${notHasSubtitleStyles};
157
+ ${ellipsisStyles};
158
+ `;
159
+
160
+ const Subtitle = styled.div`
161
+ font-size: ${(p) => p.theme.sizes.small}em;
162
+ color: ${(p) => clr(p.theme.pageHeaderSubtitleColorText)};
163
+ line-height: 1;
164
+ margin-top: 0.2em;
165
+ ${ellipsisStyles};
166
+ `;
167
+
168
+ const Addon = styled.div`
169
+ display: flex;
170
+ align-items: center;
171
+ `;
172
+
173
+ const LeftAddon = styled(Addon)`
174
+ padding-right: ${(p) => p.theme.pageHeaderAddonPaddingHorizontal}em;
175
+ `;
176
+
177
+ const RightAddon = styled(Addon)`
178
+ padding-left: ${(p) => p.theme.pageHeaderAddonPaddingHorizontal}em;
179
+ margin-left: auto;
180
+
181
+ display: grid;
182
+ grid-auto-flow: column;
183
+ grid-column-gap: 0.4em;
184
+ `;
185
+
186
+ /**
187
+ * The header of the page.
188
+ */
189
+ const PageHeader = forwardRef<HTMLDivElement, PageHeaderProps>(
190
+ (
191
+ {
192
+ title,
193
+ subtitle,
194
+ left,
195
+ leftIsGhostButton = false,
196
+ right,
197
+ rightIsGhostButton = false,
198
+ onBack,
199
+ locale = defaultLocale,
200
+ ...rest
201
+ },
202
+ ref
203
+ ) => {
204
+ const { hasNavigation } = useContext(LayoutContext);
205
+
206
+ return (
207
+ <ThemeOverrider
208
+ overrides={(t) => ({
209
+ buttonPaddingHorizontal: t.pageHeaderButtonPaddingHorizontal,
210
+ })}
211
+ >
212
+ <PageHeaderContainer
213
+ leftIsGhostButton={leftIsGhostButton || !!onBack}
214
+ rightIsGhostButton={rightIsGhostButton}
215
+ hasNavigationIndent={hasNavigation}
216
+ {...rest}
217
+ ref={ref}
218
+ >
219
+ {onBack && (
220
+ <BackButton
221
+ type='ghost'
222
+ wide='never'
223
+ onClick={onBack}
224
+ aria-label={locale.backLabel}
225
+ >
226
+ <Left />
227
+ </BackButton>
228
+ )}
229
+
230
+ {left && <LeftAddon>{left}</LeftAddon>}
231
+
232
+ <Content>
233
+ <Title hasSubtitle={!!subtitle}>{title}</Title>
234
+ {subtitle && <Subtitle>{subtitle}</Subtitle>}
235
+ </Content>
236
+
237
+ {right && <RightAddon>{right}</RightAddon>}
238
+ </PageHeaderContainer>
239
+ </ThemeOverrider>
240
+ );
241
+ }
242
+ );
243
+
244
+ PageHeader.displayName = 'PageHeader';
245
+
246
+ export default PageHeader;
@@ -0,0 +1,9 @@
1
+ export interface PageHeaderLocale {
2
+ backLabel: string;
3
+ }
4
+
5
+ const defaultLocale: PageHeaderLocale = {
6
+ backLabel: 'Back',
7
+ };
8
+
9
+ export default defaultLocale;
@@ -0,0 +1,145 @@
1
+ import { css, keyframes } from '@emotion/react';
2
+ import styled from '@emotion/styled';
3
+ import { Close, Search } from '@os-design/icons';
4
+ import { ThemeOverrider, useTheme } from '@os-design/theming';
5
+ import { omitEmotionProps, useClosable } from '@os-design/utils';
6
+ import React, { forwardRef, useContext, useState } from 'react';
7
+ import Button from '../Button';
8
+ import InputSearch, { InputSearchProps } from '../InputSearch';
9
+ import LayoutContext from '../Layout/LayoutContext';
10
+ import { PageHeaderContainer } from '../PageHeader';
11
+ import defaultLocale, {
12
+ PageHeaderInputSearchLocale,
13
+ } from './utils/defaultLocale';
14
+
15
+ export interface PageHeaderInputSearchProps
16
+ extends Omit<InputSearchProps, 'size' | 'locale'> {
17
+ /**
18
+ * The locale.
19
+ * @default undefined
20
+ */
21
+ locale?: PageHeaderInputSearchLocale;
22
+ /**
23
+ * Whether the search input opens on mount.
24
+ * @default false
25
+ */
26
+ autoOpen?: boolean;
27
+ /**
28
+ * The close event handler.
29
+ * @default undefined
30
+ */
31
+ onClose?: () => void;
32
+ }
33
+
34
+ const fadeIn = keyframes`
35
+ from { transform: translateY(-100%); }
36
+ to { transform: translateY(0); }
37
+ `;
38
+
39
+ const fadeOut = keyframes`
40
+ from { transform: translateY(0); }
41
+ to { transform: translateY(-100%); }
42
+ `;
43
+
44
+ const visibleStyles = (p) =>
45
+ p.visible &&
46
+ css`
47
+ animation: ${fadeIn} ${p.theme.transitionDelay}ms forwards;
48
+ `;
49
+
50
+ const invisibleStyles = (p) =>
51
+ !p.visible &&
52
+ css`
53
+ animation: ${fadeOut} ${p.theme.transitionDelay}ms forwards;
54
+ `;
55
+
56
+ interface ContainerProps {
57
+ visible: boolean;
58
+ }
59
+ const Container = styled(
60
+ PageHeaderContainer,
61
+ omitEmotionProps('visible')
62
+ )<ContainerProps>`
63
+ z-index: 102; // After PageHeaderContainer
64
+ ${visibleStyles};
65
+ ${invisibleStyles};
66
+ `;
67
+
68
+ const CloseButton = styled(Button)`
69
+ margin-left: ${(p) => p.theme.pageHeaderAddonPaddingHorizontal}em;
70
+ `;
71
+
72
+ /**
73
+ * The search input for the page header.
74
+ */
75
+ const PageHeaderInputSearch = forwardRef<
76
+ HTMLInputElement,
77
+ PageHeaderInputSearchProps
78
+ >(
79
+ (
80
+ {
81
+ locale = defaultLocale,
82
+ autoOpen = false,
83
+ onClose = () => {},
84
+ loading = false,
85
+ ...rest
86
+ },
87
+ ref
88
+ ) => {
89
+ const [visible, setVisible] = useState(autoOpen);
90
+ const { theme } = useTheme();
91
+ const mounted = useClosable(visible, theme.transitionDelay);
92
+ const { hasNavigation } = useContext(LayoutContext);
93
+
94
+ return (
95
+ <>
96
+ <Button
97
+ type='ghost'
98
+ wide='never'
99
+ loading={loading}
100
+ onClick={() => setVisible(true)}
101
+ aria-label={locale.searchLabel}
102
+ >
103
+ <Search />
104
+ </Button>
105
+
106
+ {mounted && (
107
+ <ThemeOverrider
108
+ overrides={(t) => ({
109
+ buttonPaddingHorizontal: t.pageHeaderButtonPaddingHorizontal,
110
+ })}
111
+ >
112
+ <Container
113
+ visible={visible}
114
+ rightIsGhostButton
115
+ hasNavigationIndent={hasNavigation}
116
+ >
117
+ <InputSearch
118
+ autoFocus
119
+ locale={locale}
120
+ loading={loading}
121
+ {...rest}
122
+ ref={ref}
123
+ />
124
+ <CloseButton
125
+ type='ghost'
126
+ wide='never'
127
+ onClick={() => {
128
+ setVisible(false);
129
+ onClose();
130
+ }}
131
+ aria-label={locale.closeLabel}
132
+ >
133
+ <Close />
134
+ </CloseButton>
135
+ </Container>
136
+ </ThemeOverrider>
137
+ )}
138
+ </>
139
+ );
140
+ }
141
+ );
142
+
143
+ PageHeaderInputSearch.displayName = 'PageHeaderInputSearch';
144
+
145
+ export default PageHeaderInputSearch;
@@ -0,0 +1,16 @@
1
+ import inputSearchDefaultLocale, {
2
+ InputSearchLocale,
3
+ } from '../../InputSearch/utils/defaultLocale';
4
+
5
+ export interface PageHeaderInputSearchLocale extends InputSearchLocale {
6
+ searchLabel: string;
7
+ closeLabel: string;
8
+ }
9
+
10
+ const defaultLocale: PageHeaderInputSearchLocale = {
11
+ ...inputSearchDefaultLocale,
12
+ searchLabel: 'Search',
13
+ closeLabel: 'Close search',
14
+ };
15
+
16
+ export default defaultLocale;
@@ -0,0 +1,33 @@
1
+ import { WithSize } from '@os-design/styles';
2
+
3
+ import React, { forwardRef, useContext } from 'react';
4
+ import LayoutContext from '../Layout/LayoutContext';
5
+ import { PageHeaderContainer } from '../PageHeader';
6
+
7
+ import Skeleton, { SkeletonProps } from '../Skeleton';
8
+
9
+ export type PageHeaderSkeletonProps = SkeletonProps & WithSize;
10
+
11
+ /**
12
+ * Provides a page header placeholder while a user waits for
13
+ * the content to load.
14
+ */
15
+ const PageHeaderSkeleton = forwardRef<HTMLDivElement, PageHeaderSkeletonProps>(
16
+ ({ width = '30%', ...rest }, ref) => {
17
+ const { hasNavigation } = useContext(LayoutContext);
18
+
19
+ return (
20
+ <PageHeaderContainer
21
+ hasNavigationIndent={hasNavigation}
22
+ {...rest}
23
+ ref={ref}
24
+ >
25
+ <Skeleton width={width} />
26
+ </PageHeaderContainer>
27
+ );
28
+ }
29
+ );
30
+
31
+ PageHeaderSkeleton.displayName = 'PageHeaderSkeleton';
32
+
33
+ export default PageHeaderSkeleton;
@@ -0,0 +1,65 @@
1
+ import { css } from '@emotion/react';
2
+
3
+ import styled from '@emotion/styled';
4
+
5
+ import { omitEmotionProps } from '@os-design/utils';
6
+ import React, { forwardRef } from 'react';
7
+
8
+ import Skeleton, { SkeletonProps } from '../Skeleton';
9
+
10
+ export interface ParagraphSkeletonProps extends SkeletonProps {
11
+ /**
12
+ * The number of rows.
13
+ * @default 4
14
+ */
15
+ rows?: number;
16
+ /**
17
+ * The width of the last row.
18
+ * @default 70%
19
+ */
20
+ width?: string;
21
+ /**
22
+ * Whether the paragraph has a bottom margin.
23
+ * @default false
24
+ */
25
+ hasMargin?: boolean;
26
+ }
27
+
28
+ const hasMarginStyles = (p) =>
29
+ p.hasMargin &&
30
+ css`
31
+ margin-bottom: ${p.theme.paragraphMarginBottom +
32
+ (p.theme.lineHeight - 1)}em;
33
+ `;
34
+
35
+ type ContainerProps = Pick<ParagraphSkeletonProps, 'hasMargin'>;
36
+ const Container = styled('div', omitEmotionProps('hasMargin'))<ContainerProps>`
37
+ & > *:not(:last-of-type) {
38
+ margin-bottom: ${(p) => p.theme.lineHeight - 1}em;
39
+ }
40
+ ${hasMarginStyles};
41
+ `;
42
+
43
+ let key = 0;
44
+
45
+ /**
46
+ * Provides a paragraph placeholder while a user waits for the content to load.
47
+ */
48
+ const ParagraphSkeleton = forwardRef<HTMLDivElement, ParagraphSkeletonProps>(
49
+ ({ rows = 4, width = '70%', hasMargin = false, ...rest }, ref) => (
50
+ <Container hasMargin={hasMargin} {...rest} ref={ref}>
51
+ {Array(rows)
52
+ .fill({})
53
+ .map((_, index) => {
54
+ key += 1;
55
+ return (
56
+ <Skeleton key={key} width={index < rows - 1 ? '100%' : width} />
57
+ );
58
+ })}
59
+ </Container>
60
+ )
61
+ );
62
+
63
+ ParagraphSkeleton.displayName = 'ParagraphSkeleton';
64
+
65
+ export default ParagraphSkeleton;