@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,214 @@
1
+ import { css } from '@emotion/react';
2
+ import styled from '@emotion/styled';
3
+ import { m } from '@os-design/media';
4
+
5
+ import { enableScrollingStyles } from '@os-design/styles';
6
+ import { clr } from '@os-design/theming';
7
+
8
+ import { omitEmotionProps } from '@os-design/utils';
9
+ import React, { forwardRef, useRef } from 'react';
10
+
11
+ import useScrollFlags from './utils/useScrollFlags';
12
+
13
+ type JsxDivProps = Omit<JSX.IntrinsicElements['div'], 'ref'>;
14
+ export interface NavigationProps extends JsxDivProps {
15
+ /**
16
+ * The top component in the side navigator.
17
+ * E.g. the user avatar.
18
+ * @default undefined
19
+ */
20
+ sideTop?: React.ReactNode;
21
+ /**
22
+ * The bottom component in the side navigator.
23
+ * E.g. the current tariff.
24
+ * @default undefined
25
+ */
26
+ sideBottom?: React.ReactNode;
27
+ }
28
+
29
+ const Container = styled.div`
30
+ position: fixed;
31
+ box-sizing: border-box;
32
+ z-index: 101;
33
+
34
+ background-color: ${(p) => clr(p.theme.navigationColorBg)};
35
+ color: ${(p) => clr(p.theme.navigationColorText)};
36
+
37
+ // Tab navigator
38
+ bottom: 0;
39
+ left: 0;
40
+ right: 0;
41
+ height: calc(
42
+ ${(p) => p.theme.navigationTabHeight}em + env(safe-area-inset-bottom)
43
+ );
44
+ border-top: 1px solid ${(p) => clr(p.theme.navigationColorBorder)};
45
+
46
+ // Side navigator
47
+ ${m.min.md} {
48
+ // Reset tab navigator styles
49
+ right: unset;
50
+ height: unset;
51
+ border-top: unset;
52
+
53
+ left: 0;
54
+ top: 0;
55
+ bottom: 0;
56
+ width: ${(p) => p.theme.navigationSideWidth}em;
57
+ border-right: 1px solid ${(p) => clr(p.theme.navigationColorBorder)};
58
+ }
59
+ `;
60
+
61
+ const notHasSideTopStyles = (p) =>
62
+ !p.hasSideTop &&
63
+ css`
64
+ padding-top: ${p.theme.pageHeaderHeight[1]}em;
65
+ `;
66
+
67
+ const hasPrevStyles = (p) =>
68
+ p.hasPrev &&
69
+ !p.hasNext &&
70
+ css`
71
+ mask-image: linear-gradient(
72
+ to right,
73
+ transparent,
74
+ white ${p.theme.navigationMaskImageSize}em
75
+ );
76
+ ${m.min.md} {
77
+ mask-image: linear-gradient(
78
+ to bottom,
79
+ transparent,
80
+ white ${p.theme.navigationMaskImageSize}em
81
+ );
82
+ }
83
+ `;
84
+
85
+ const hasNextStyles = (p) =>
86
+ !p.hasPrev &&
87
+ p.hasNext &&
88
+ css`
89
+ mask-image: linear-gradient(
90
+ to left,
91
+ transparent,
92
+ white ${p.theme.navigationMaskImageSize}em
93
+ );
94
+ ${m.min.md} {
95
+ mask-image: linear-gradient(
96
+ to top,
97
+ transparent,
98
+ white ${p.theme.navigationMaskImageSize}em
99
+ );
100
+ }
101
+ `;
102
+
103
+ const hasPrevNextStyles = (p) =>
104
+ p.hasPrev &&
105
+ p.hasNext &&
106
+ css`
107
+ mask-image: linear-gradient(
108
+ to right,
109
+ transparent,
110
+ white ${p.theme.navigationMaskImageSize}em,
111
+ white calc(100% - ${p.theme.navigationMaskImageSize}em),
112
+ transparent
113
+ );
114
+ ${m.min.md} {
115
+ mask-image: linear-gradient(
116
+ to bottom,
117
+ transparent,
118
+ white ${p.theme.navigationMaskImageSize}em,
119
+ white calc(100% - ${p.theme.navigationMaskImageSize}em),
120
+ transparent
121
+ );
122
+ }
123
+ `;
124
+
125
+ interface ContentProps {
126
+ hasSideTop: boolean;
127
+ hasPrev: boolean;
128
+ hasNext: boolean;
129
+ }
130
+ const Content = styled(
131
+ 'div',
132
+ omitEmotionProps('hasSideTop', 'hasPrev', 'hasNext')
133
+ )<ContentProps>`
134
+ position: absolute;
135
+ top: 0;
136
+ right: 0;
137
+ bottom: 0;
138
+ left: 0;
139
+
140
+ display: flex;
141
+ flex-direction: row;
142
+ justify-content: space-between;
143
+ align-items: flex-start;
144
+ &::before,
145
+ &::after {
146
+ content: ''; // Insert space before the first item and after the last one
147
+ }
148
+
149
+ overflow-y: hidden;
150
+ ${enableScrollingStyles('x', false)};
151
+
152
+ ${m.min.md} {
153
+ // Reset tab navigator styles
154
+ justify-content: unset;
155
+ align-items: unset;
156
+
157
+ flex-direction: column;
158
+
159
+ overflow-x: hidden;
160
+ ${enableScrollingStyles('y', false)};
161
+
162
+ ${notHasSideTopStyles};
163
+ }
164
+
165
+ ${hasPrevStyles};
166
+ ${hasNextStyles};
167
+ ${hasPrevNextStyles};
168
+ `;
169
+
170
+ const Addon = styled.div`
171
+ display: none;
172
+ ${m.min.md} {
173
+ display: block;
174
+ }
175
+ `;
176
+
177
+ const BottomAddon = styled(Addon)`
178
+ margin-top: auto;
179
+ `;
180
+
181
+ /**
182
+ * The main navigation.
183
+ */
184
+ const Navigation = forwardRef<HTMLDivElement, NavigationProps>(
185
+ ({ sideTop, sideBottom, children, ...rest }, ref) => {
186
+ const contentRef = useRef<HTMLDivElement>(null);
187
+ const { hasPrev, hasNext } = useScrollFlags(contentRef);
188
+
189
+ return (
190
+ <Container
191
+ role='navigation'
192
+ aria-label='Main navigation'
193
+ {...rest}
194
+ ref={ref}
195
+ >
196
+ <Content
197
+ hasSideTop={!!sideTop}
198
+ hasPrev={hasPrev}
199
+ hasNext={hasNext}
200
+ ref={contentRef}
201
+ role='list'
202
+ >
203
+ {sideTop && <Addon>{sideTop}</Addon>}
204
+ {children}
205
+ {sideBottom && <BottomAddon>{sideBottom}</BottomAddon>}
206
+ </Content>
207
+ </Container>
208
+ );
209
+ }
210
+ );
211
+
212
+ Navigation.displayName = 'Navigation';
213
+
214
+ export default Navigation;
@@ -0,0 +1,39 @@
1
+ import { useIsMinWidth } from '@os-design/media';
2
+ import { useBrowserLayoutEffect, useEvent } from '@os-design/utils';
3
+ import { RefObject, useCallback, useState } from 'react';
4
+
5
+ interface UseScrollFlagsRes {
6
+ hasPrev: boolean;
7
+ hasNext: boolean;
8
+ }
9
+
10
+ const useScrollFlags = (ref: RefObject<Element>): UseScrollFlagsRes => {
11
+ const [hasPrev, setHasPrev] = useState(false);
12
+ const [hasNext, setHasNext] = useState(false);
13
+ const isMinMd = useIsMinWidth('md');
14
+
15
+ const scrollHandler = useCallback(() => {
16
+ const { current } = ref;
17
+ if (!current) return;
18
+
19
+ setHasPrev(current[isMinMd ? 'scrollTop' : 'scrollLeft'] > 0);
20
+ setHasNext(
21
+ current[isMinMd ? 'scrollHeight' : 'scrollWidth'] -
22
+ current[isMinMd ? 'scrollTop' : 'scrollLeft'] -
23
+ current[isMinMd ? 'clientHeight' : 'clientWidth'] >
24
+ 0
25
+ );
26
+ }, [ref, isMinMd]);
27
+
28
+ useBrowserLayoutEffect(() => scrollHandler(), [scrollHandler]);
29
+ useEvent(ref, 'scroll', scrollHandler);
30
+ useEvent(
31
+ (typeof window !== 'undefined' ? window : undefined) as EventTarget,
32
+ 'resize',
33
+ scrollHandler
34
+ );
35
+
36
+ return { hasPrev, hasNext };
37
+ };
38
+
39
+ export default useScrollFlags;
@@ -0,0 +1,136 @@
1
+ import { css } from '@emotion/react';
2
+ import styled from '@emotion/styled';
3
+ import { m } from '@os-design/media';
4
+ import { resetFocusStyles, transitionStyles } from '@os-design/styles';
5
+ import { clr } from '@os-design/theming';
6
+ import { omitEmotionProps } from '@os-design/utils';
7
+ import React, { forwardRef } from 'react';
8
+ import { LinkProps, ReactRouterLinkProps } from '../Link';
9
+
10
+ type JsxAProps = Omit<JSX.IntrinsicElements['a'], 'ref'>;
11
+ export interface NavigationItemProps
12
+ extends JsxAProps,
13
+ ReactRouterLinkProps,
14
+ Pick<LinkProps, 'as'> {
15
+ /**
16
+ * The icon of the item.
17
+ * @default undefined
18
+ */
19
+ icon: React.ReactElement;
20
+ /**
21
+ * Whether the item is the current page.
22
+ * @default false
23
+ */
24
+ currentPage?: boolean;
25
+ }
26
+
27
+ const currentPageStyles = (p) =>
28
+ p.currentPage &&
29
+ css`
30
+ background-color: ${clr(p.theme.navigationItemColorBgActive)};
31
+ color: ${clr(p.theme.navigationItemColorTextActive)};
32
+ `;
33
+
34
+ type NavigationLinkProps = Pick<NavigationItemProps, 'currentPage'>;
35
+ const NavigationLink = styled(
36
+ 'a',
37
+ omitEmotionProps('currentPage', 'as')
38
+ )<NavigationLinkProps>`
39
+ ${resetFocusStyles};
40
+ text-decoration: none;
41
+ cursor: pointer;
42
+ overflow: hidden;
43
+ border-radius: ${(p) => p.theme.borderRadius}em;
44
+
45
+ display: flex;
46
+ flex-direction: column;
47
+ justify-content: center;
48
+ align-items: center;
49
+
50
+ color: ${(p) => clr(p.theme.navigationItemColorText)};
51
+ @media (hover: hover) {
52
+ &:hover,
53
+ &:focus {
54
+ background-color: ${(p) => clr(p.theme.navigationItemColorBgHover)};
55
+ }
56
+ }
57
+
58
+ // Tab navigator
59
+ width: ${(p) => p.theme.navigationItemTabWidth}em;
60
+ min-width: ${(p) => p.theme.navigationItemTabWidth}em;
61
+ height: calc(${(p) => p.theme.navigationTabHeight}em - 1px);
62
+
63
+ // Side navigator
64
+ ${m.min.md} {
65
+ // Reset tab navigator styles
66
+ min-width: unset;
67
+
68
+ width: 100%;
69
+ height: ${(p) => p.theme.navigationItemSideHeight}em;
70
+ min-height: ${(p) => p.theme.navigationItemSideHeight}em;
71
+ }
72
+
73
+ ${currentPageStyles};
74
+ ${transitionStyles('background-color', 'color')};
75
+ `;
76
+
77
+ const IconContainer = styled.span`
78
+ display: inherit;
79
+
80
+ // Tab navigator
81
+ font-size: 1.6em;
82
+
83
+ // Side navigator
84
+ ${m.min.md} {
85
+ font-size: 1.8em;
86
+ }
87
+ `;
88
+
89
+ const Title = styled.span`
90
+ font-size: 0.75em;
91
+ font-weight: 500;
92
+ white-space: nowrap;
93
+
94
+ // Side navigator
95
+ ${m.min.md} {
96
+ margin-top: 0.3em;
97
+ }
98
+ `;
99
+
100
+ /**
101
+ * The item of the navigation.
102
+ */
103
+ const NavigationItem = forwardRef<HTMLAnchorElement, NavigationItemProps>(
104
+ (
105
+ {
106
+ icon,
107
+ currentPage = false,
108
+ as,
109
+ onMouseDown = () => {},
110
+ children,
111
+ ...rest
112
+ },
113
+ ref
114
+ ) => (
115
+ <div role='listitem'>
116
+ <NavigationLink
117
+ currentPage={currentPage}
118
+ as={as}
119
+ onMouseDown={(e) => {
120
+ onMouseDown(e);
121
+ e.preventDefault();
122
+ }}
123
+ aria-current={currentPage ? 'page' : undefined}
124
+ {...rest}
125
+ ref={ref}
126
+ >
127
+ <IconContainer>{icon}</IconContainer>
128
+ {children && <Title>{children}</Title>}
129
+ </NavigationLink>
130
+ </div>
131
+ )
132
+ );
133
+
134
+ NavigationItem.displayName = 'NavigationItem';
135
+
136
+ export default NavigationItem;
@@ -0,0 +1,99 @@
1
+ import { css } from '@emotion/react';
2
+
3
+ import styled from '@emotion/styled';
4
+ import { m } from '@os-design/media';
5
+
6
+ import { horizontalPaddingStyles } from '@os-design/styles';
7
+ import { omitEmotionProps } from '@os-design/utils';
8
+ import React, { forwardRef, useContext } from 'react';
9
+
10
+ import LayoutContext from '../Layout/LayoutContext';
11
+
12
+ type JsxDivProps = Omit<JSX.IntrinsicElements['div'], 'ref'>;
13
+ export interface PageContentProps extends JsxDivProps {
14
+ /**
15
+ * Whether there is the list in the page content.
16
+ * @default false
17
+ */
18
+ hasList?: boolean;
19
+ }
20
+
21
+ const hasNavigationStyles = (p) =>
22
+ p.hasNavigation &&
23
+ css`
24
+ ${!p.hasList &&
25
+ css`
26
+ margin-bottom: calc(
27
+ ${p.theme.navigationTabHeight}em + env(safe-area-inset-bottom)
28
+ );
29
+ `}
30
+ ${m.min.md} {
31
+ margin-bottom: 0;
32
+ margin-left: ${p.theme.navigationSideWidth}em;
33
+ }
34
+ `;
35
+
36
+ const hasPageHeaderStyles = (p) =>
37
+ p.hasPageHeader &&
38
+ !p.hasList &&
39
+ css`
40
+ margin-top: ${p.theme.pageHeaderHeight[0]}em;
41
+ ${m.min.md} {
42
+ margin-top: ${p.theme.pageHeaderHeight[1]}em;
43
+ }
44
+ `;
45
+
46
+ const notHasListStyles = (p) =>
47
+ !p.hasList &&
48
+ css`
49
+ padding-top: ${p.theme.pageContentPaddingVertical}em;
50
+ padding-bottom: ${p.theme.pageContentPaddingVertical}em;
51
+ ${horizontalPaddingStyles()(p)};
52
+ `;
53
+
54
+ const notHasNavigationStyles = (p) =>
55
+ !p.hasNavigation &&
56
+ !p.hasList &&
57
+ css`
58
+ margin-bottom: env(safe-area-inset-bottom);
59
+ `;
60
+
61
+ interface ContainerProps {
62
+ hasNavigation?: boolean;
63
+ hasPageHeader?: boolean;
64
+ hasList?: boolean;
65
+ }
66
+ const Container = styled(
67
+ 'main',
68
+ omitEmotionProps('hasNavigation', 'hasPageHeader', 'hasList')
69
+ )<ContainerProps>`
70
+ ${hasNavigationStyles};
71
+ ${hasPageHeaderStyles};
72
+ ${notHasListStyles};
73
+ ${notHasNavigationStyles};
74
+ `;
75
+
76
+ /**
77
+ * The wrapper that adds padding to the page content.
78
+ */
79
+ const PageContent = forwardRef<HTMLElement, PageContentProps>(
80
+ ({ hasList = false, children, ...rest }, ref) => {
81
+ const { hasNavigation, hasPageHeader } = useContext(LayoutContext);
82
+
83
+ return (
84
+ <Container
85
+ hasNavigation={hasNavigation}
86
+ hasPageHeader={hasPageHeader}
87
+ hasList={hasList}
88
+ {...rest}
89
+ ref={ref}
90
+ >
91
+ {children}
92
+ </Container>
93
+ );
94
+ }
95
+ );
96
+
97
+ PageContent.displayName = 'PageContent';
98
+
99
+ export default PageContent;