@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,212 @@
|
|
|
1
|
+
import { css, keyframes } from '@emotion/react';
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
import Portal from '@os-design/portal';
|
|
4
|
+
import {
|
|
5
|
+
WithSize,
|
|
6
|
+
enableScrollingStyles,
|
|
7
|
+
resetFocusStyles,
|
|
8
|
+
sizeStyles,
|
|
9
|
+
} from '@os-design/styles';
|
|
10
|
+
import { clr, useTheme } from '@os-design/theming';
|
|
11
|
+
import { omitEmotionProps, useBodyScroll, useClosable } from '@os-design/utils';
|
|
12
|
+
import React, { forwardRef, useCallback, useContext } from 'react';
|
|
13
|
+
import FocusLock from 'react-focus-lock';
|
|
14
|
+
import { ModalMask } from '../Modal';
|
|
15
|
+
|
|
16
|
+
type JsxDivProps = Omit<JSX.IntrinsicElements['div'], 'ref'>;
|
|
17
|
+
export interface DrawerProps extends JsxDivProps, WithSize {
|
|
18
|
+
/**
|
|
19
|
+
* The placement of the drawer.
|
|
20
|
+
* @default right
|
|
21
|
+
*/
|
|
22
|
+
placement?: 'left' | 'right';
|
|
23
|
+
/**
|
|
24
|
+
* The width of the drawer.
|
|
25
|
+
* @default 15em
|
|
26
|
+
*/
|
|
27
|
+
width?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Whether the drawer is visible.
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
visible?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Specifies a callback that will be called when a user clicks the mask.
|
|
35
|
+
* The callback should set the visible state to false.
|
|
36
|
+
* @default undefined
|
|
37
|
+
*/
|
|
38
|
+
onClose?: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const DRAWER_CONTAINER_Z_INDEX = 900;
|
|
42
|
+
const DRAWER_BOX_SHADOW_SIZE_EM = 1;
|
|
43
|
+
|
|
44
|
+
const DrawerMask = styled(ModalMask)`
|
|
45
|
+
z-index: ${DRAWER_CONTAINER_Z_INDEX};
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const Container = styled.div`
|
|
49
|
+
position: fixed;
|
|
50
|
+
top: 0;
|
|
51
|
+
left: 0;
|
|
52
|
+
right: 0;
|
|
53
|
+
height: 100%;
|
|
54
|
+
z-index: ${DRAWER_CONTAINER_Z_INDEX};
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const placementLeftFadeIn = keyframes`
|
|
58
|
+
from { transform: translateX(calc(-100% - ${DRAWER_BOX_SHADOW_SIZE_EM}em)); }
|
|
59
|
+
to { transform: translateX(0); }
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
const placementLeftFadeOut = keyframes`
|
|
63
|
+
from { transform: translateX(0); }
|
|
64
|
+
to { transform: translateX(calc(-100% - ${DRAWER_BOX_SHADOW_SIZE_EM}em)); }
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const placementRightFadeIn = keyframes`
|
|
68
|
+
from { transform: translateX(calc(100vw + ${DRAWER_BOX_SHADOW_SIZE_EM}em)); }
|
|
69
|
+
to { transform: translateX(calc(100vw - 100%)); }
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
const placementRightFadeOut = keyframes`
|
|
73
|
+
from { transform: translateX(calc(100vw - 100%)); }
|
|
74
|
+
to { transform: translateX(calc(100vw + ${DRAWER_BOX_SHADOW_SIZE_EM}em)); }
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
const placementLeftVisibleStyles = (p) =>
|
|
78
|
+
p.placement === 'left' &&
|
|
79
|
+
p.visible &&
|
|
80
|
+
css`
|
|
81
|
+
animation: ${placementLeftFadeIn} ${p.theme.transitionDelay}ms forwards;
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
const placementLeftInvisibleStyles = (p) =>
|
|
85
|
+
p.placement === 'left' &&
|
|
86
|
+
!p.visible &&
|
|
87
|
+
css`
|
|
88
|
+
animation: ${placementLeftFadeOut} ${p.theme.transitionDelay}ms forwards;
|
|
89
|
+
`;
|
|
90
|
+
|
|
91
|
+
const placementRightVisibleStyles = (p) =>
|
|
92
|
+
p.placement === 'right' &&
|
|
93
|
+
p.visible &&
|
|
94
|
+
css`
|
|
95
|
+
animation: ${placementRightFadeIn} ${p.theme.transitionDelay}ms forwards;
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
const placementRightInvisibleStyles = (p) =>
|
|
99
|
+
p.placement === 'right' &&
|
|
100
|
+
!p.visible &&
|
|
101
|
+
css`
|
|
102
|
+
animation: ${placementRightFadeOut} ${p.theme.transitionDelay}ms forwards;
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
type ContentProps = Pick<
|
|
106
|
+
DrawerProps,
|
|
107
|
+
'placement' | 'width' | 'visible' | 'size'
|
|
108
|
+
>;
|
|
109
|
+
const Content = styled(
|
|
110
|
+
'div',
|
|
111
|
+
omitEmotionProps('placement', 'width', 'visible', 'size')
|
|
112
|
+
)<ContentProps>`
|
|
113
|
+
${resetFocusStyles};
|
|
114
|
+
|
|
115
|
+
position: absolute;
|
|
116
|
+
top: 0;
|
|
117
|
+
bottom: 0;
|
|
118
|
+
width: ${(p) => p.width};
|
|
119
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
120
|
+
|
|
121
|
+
background-color: ${(p) => clr(p.theme.colorBg)};
|
|
122
|
+
color: ${(p) => clr(p.theme.colorText)};
|
|
123
|
+
box-shadow: 0 0 ${DRAWER_BOX_SHADOW_SIZE_EM}em
|
|
124
|
+
${(p) => clr(p.theme.drawerColorBoxShadow)};
|
|
125
|
+
|
|
126
|
+
${placementLeftVisibleStyles};
|
|
127
|
+
${placementLeftInvisibleStyles};
|
|
128
|
+
${placementRightVisibleStyles};
|
|
129
|
+
${placementRightInvisibleStyles};
|
|
130
|
+
|
|
131
|
+
${enableScrollingStyles('y')};
|
|
132
|
+
${sizeStyles};
|
|
133
|
+
`;
|
|
134
|
+
|
|
135
|
+
const CloseDrawerContext = React.createContext<() => void>(() => {});
|
|
136
|
+
|
|
137
|
+
export const useCloseDrawer = () => useContext(CloseDrawerContext);
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The side panel that appears from the edge of the screen.
|
|
141
|
+
*/
|
|
142
|
+
const Drawer = forwardRef<HTMLDivElement, DrawerProps>(
|
|
143
|
+
(
|
|
144
|
+
{
|
|
145
|
+
placement = 'right',
|
|
146
|
+
width = '15em',
|
|
147
|
+
visible = false,
|
|
148
|
+
onClose = () => {},
|
|
149
|
+
size,
|
|
150
|
+
children,
|
|
151
|
+
onClick = () => {},
|
|
152
|
+
...rest
|
|
153
|
+
},
|
|
154
|
+
ref
|
|
155
|
+
) => {
|
|
156
|
+
const { theme } = useTheme();
|
|
157
|
+
const mounted = useClosable(visible, theme.transitionDelay);
|
|
158
|
+
|
|
159
|
+
useBodyScroll(!visible);
|
|
160
|
+
|
|
161
|
+
const clickHandler = useCallback(
|
|
162
|
+
(e) => {
|
|
163
|
+
e.stopPropagation();
|
|
164
|
+
onClick(e);
|
|
165
|
+
},
|
|
166
|
+
[onClick]
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const keyDownHandler = useCallback(
|
|
170
|
+
(e) => {
|
|
171
|
+
if (e.key === 'Escape') {
|
|
172
|
+
e.stopPropagation();
|
|
173
|
+
onClose();
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
[onClose]
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if (!mounted) return null;
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<Portal>
|
|
183
|
+
<DrawerMask visible={visible} />
|
|
184
|
+
<Container onClick={onClose}>
|
|
185
|
+
<FocusLock autoFocus>
|
|
186
|
+
<CloseDrawerContext.Provider value={onClose}>
|
|
187
|
+
<Content
|
|
188
|
+
placement={placement}
|
|
189
|
+
width={width}
|
|
190
|
+
visible={visible}
|
|
191
|
+
size={size}
|
|
192
|
+
tabIndex={-1}
|
|
193
|
+
onKeyDown={keyDownHandler}
|
|
194
|
+
onClick={clickHandler}
|
|
195
|
+
role='dialog'
|
|
196
|
+
aria-modal
|
|
197
|
+
{...rest}
|
|
198
|
+
ref={ref}
|
|
199
|
+
>
|
|
200
|
+
{children}
|
|
201
|
+
</Content>
|
|
202
|
+
</CloseDrawerContext.Provider>
|
|
203
|
+
</FocusLock>
|
|
204
|
+
</Container>
|
|
205
|
+
</Portal>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
Drawer.displayName = 'Drawer';
|
|
211
|
+
|
|
212
|
+
export default Drawer;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface FormConfigContextProps {
|
|
4
|
+
/**
|
|
5
|
+
* The text that is displayed to the right of the label of the optional field.
|
|
6
|
+
*/
|
|
7
|
+
optionalText: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const FormConfigContext = React.createContext<FormConfigContextProps>({
|
|
11
|
+
optionalText: 'optional',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
FormConfigContext.displayName = 'FormConfigContext';
|
|
15
|
+
|
|
16
|
+
export default FormConfigContext;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import styled from '@emotion/styled';
|
|
2
|
+
import { WithSize, sizeStyles } from '@os-design/styles';
|
|
3
|
+
import { omitEmotionProps } from '@os-design/utils';
|
|
4
|
+
import React, { forwardRef, useMemo } from 'react';
|
|
5
|
+
import FormConfigContext from './FormConfigContext';
|
|
6
|
+
|
|
7
|
+
type JsxDivProps = Omit<JSX.IntrinsicElements['div'], 'ref'>;
|
|
8
|
+
export interface FormProps extends JsxDivProps, WithSize {
|
|
9
|
+
/**
|
|
10
|
+
* The text that is displayed to the right of the label of the optional field.
|
|
11
|
+
* @default optional
|
|
12
|
+
*/
|
|
13
|
+
optionalText?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const StyledForm = styled('div', omitEmotionProps('size'))<WithSize>`
|
|
17
|
+
max-width: 50em;
|
|
18
|
+
|
|
19
|
+
& > * + * {
|
|
20
|
+
margin-top: 1em;
|
|
21
|
+
}
|
|
22
|
+
& > style + * {
|
|
23
|
+
margin-top: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
${sizeStyles};
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The wrapper of the form.
|
|
31
|
+
*/
|
|
32
|
+
const Form = forwardRef<HTMLDivElement, FormProps>(
|
|
33
|
+
({ optionalText = 'optional', ...rest }, ref) => {
|
|
34
|
+
const memoizedOptionalText = useMemo(
|
|
35
|
+
() => ({ optionalText }),
|
|
36
|
+
[optionalText]
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<FormConfigContext.Provider value={memoizedOptionalText}>
|
|
41
|
+
<StyledForm {...rest} ref={ref} />
|
|
42
|
+
</FormConfigContext.Provider>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
Form.displayName = 'Form';
|
|
48
|
+
|
|
49
|
+
export default Form;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import styled from '@emotion/styled';
|
|
2
|
+
import { ellipsisStyles, sizeStyles, WithSize } from '@os-design/styles';
|
|
3
|
+
import { clr } from '@os-design/theming';
|
|
4
|
+
|
|
5
|
+
import { omitEmotionProps } from '@os-design/utils';
|
|
6
|
+
import React, { forwardRef } from 'react';
|
|
7
|
+
|
|
8
|
+
type JsxDivProps = Omit<JSX.IntrinsicElements['div'], 'ref'>;
|
|
9
|
+
export interface FormDividerProps extends JsxDivProps, WithSize {
|
|
10
|
+
/**
|
|
11
|
+
* The title that is in the center of the divider.
|
|
12
|
+
*/
|
|
13
|
+
title: string;
|
|
14
|
+
/**
|
|
15
|
+
* The description that is under the title.
|
|
16
|
+
* @default undefined
|
|
17
|
+
*/
|
|
18
|
+
description?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const Container = styled('div', omitEmotionProps('size'))<WithSize>`
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
margin: 3em 0 1em;
|
|
25
|
+
|
|
26
|
+
&::before,
|
|
27
|
+
&::after {
|
|
28
|
+
content: '';
|
|
29
|
+
flex: 1;
|
|
30
|
+
min-width: 5%;
|
|
31
|
+
max-width: 50%;
|
|
32
|
+
border-top: 1px solid ${(p) => clr(p.theme.formDividerColor)};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
${sizeStyles};
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
const Content = styled.div`
|
|
39
|
+
padding: 0 1.5em;
|
|
40
|
+
text-align: center;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
const Title = styled.div`
|
|
45
|
+
color: ${(p) => clr(p.theme.colorText)};
|
|
46
|
+
font-size: ${(p) => p.theme.sizes.large}em;
|
|
47
|
+
font-weight: 500;
|
|
48
|
+
${ellipsisStyles};
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
const Description = styled.div`
|
|
52
|
+
color: ${(p) => clr(p.theme.inputColorPlaceholder)};
|
|
53
|
+
font-size: ${(p) => p.theme.sizes.small}em;
|
|
54
|
+
line-height: 1.2;
|
|
55
|
+
${ellipsisStyles};
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The divider line for separating the various elements of the form.
|
|
60
|
+
*/
|
|
61
|
+
const FormDivider = forwardRef<HTMLDivElement, FormDividerProps>(
|
|
62
|
+
({ title, description, ...rest }, ref) => (
|
|
63
|
+
<Container role='separator' {...rest} ref={ref}>
|
|
64
|
+
<Content>
|
|
65
|
+
<Title>{title}</Title>
|
|
66
|
+
<Description>{description}</Description>
|
|
67
|
+
</Content>
|
|
68
|
+
</Container>
|
|
69
|
+
)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
FormDivider.displayName = 'FormDivider';
|
|
73
|
+
|
|
74
|
+
export default FormDivider;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import styled from '@emotion/styled';
|
|
2
|
+
import { sizeStyles, WithSize } from '@os-design/styles';
|
|
3
|
+
import { clr, ThemeOverrider } from '@os-design/theming';
|
|
4
|
+
import { omitEmotionProps } from '@os-design/utils';
|
|
5
|
+
import React, { forwardRef, useContext, useMemo } from 'react';
|
|
6
|
+
import FormConfigContext from '../Form/FormConfigContext';
|
|
7
|
+
|
|
8
|
+
type JsxDivProps = Omit<JSX.IntrinsicElements['div'], 'ref'>;
|
|
9
|
+
export interface FormItemProps extends JsxDivProps, WithSize {
|
|
10
|
+
/**
|
|
11
|
+
* Label of the field.
|
|
12
|
+
* @default undefined
|
|
13
|
+
*/
|
|
14
|
+
label?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The help message located at the bottom of the field.
|
|
17
|
+
* @default undefined
|
|
18
|
+
*/
|
|
19
|
+
help?: string;
|
|
20
|
+
/**
|
|
21
|
+
* The error message located at the bottom of the field.
|
|
22
|
+
* If specified, the field has a red border.
|
|
23
|
+
* @default undefined
|
|
24
|
+
*/
|
|
25
|
+
error?: string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Whether the field is optional.
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
optional?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const Container = styled('div', omitEmotionProps('size'))<WithSize>`
|
|
34
|
+
${sizeStyles};
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
const Label = styled.label`
|
|
38
|
+
display: inline-flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
margin-bottom: 0.4em;
|
|
41
|
+
font-size: ${(p) => p.theme.sizes.small}em;
|
|
42
|
+
color: ${(p) => clr(p.theme.colorText)};
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const Optional = styled.span`
|
|
46
|
+
color: ${(p) => clr(p.theme.formItemColorOptional)};
|
|
47
|
+
margin-left: 0.3em;
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
const Help = styled.div`
|
|
51
|
+
margin-top: 0.1em;
|
|
52
|
+
font-size: ${(p) => p.theme.sizes.small}em;
|
|
53
|
+
color: ${(p) => clr(p.theme.formItemColorHelp)};
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
const Error = styled(Help)`
|
|
57
|
+
color: ${(p) => clr(p.theme.formItemColorError)};
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The wrapper of the field.
|
|
62
|
+
*/
|
|
63
|
+
const FormItem = forwardRef<HTMLDivElement, FormItemProps>(
|
|
64
|
+
({ label, help, error, optional = false, id, children, ...rest }, ref) => {
|
|
65
|
+
const { optionalText } = useContext(FormConfigContext);
|
|
66
|
+
|
|
67
|
+
const containerId = useMemo(
|
|
68
|
+
() => id || Math.random().toString(36).slice(2, 11),
|
|
69
|
+
[id]
|
|
70
|
+
);
|
|
71
|
+
const fieldId = useMemo(() => `field-${containerId}`, [containerId]);
|
|
72
|
+
const helpId = useMemo(() => `help-${containerId}`, [containerId]);
|
|
73
|
+
const ariaInvalid = useMemo(() => !!error, [error]);
|
|
74
|
+
const ariaDescribedBy = useMemo(
|
|
75
|
+
() => (help ? helpId : undefined),
|
|
76
|
+
[help, helpId]
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Container role='group' id={id} {...rest} ref={ref}>
|
|
81
|
+
{label && (
|
|
82
|
+
<Label htmlFor={fieldId}>
|
|
83
|
+
{label}
|
|
84
|
+
{optional && <Optional>({optionalText})</Optional>}
|
|
85
|
+
</Label>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
<div
|
|
89
|
+
id={fieldId}
|
|
90
|
+
aria-invalid={ariaInvalid}
|
|
91
|
+
aria-describedby={ariaDescribedBy}
|
|
92
|
+
>
|
|
93
|
+
<ThemeOverrider
|
|
94
|
+
overrides={(theme) => ({
|
|
95
|
+
inputColorBorder: error
|
|
96
|
+
? theme.formItemColorError
|
|
97
|
+
: theme.inputColorBorder,
|
|
98
|
+
})}
|
|
99
|
+
>
|
|
100
|
+
{children}
|
|
101
|
+
</ThemeOverrider>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
{error ? (
|
|
105
|
+
<Error aria-live='polite' id={helpId}>
|
|
106
|
+
{error}
|
|
107
|
+
</Error>
|
|
108
|
+
) : (
|
|
109
|
+
help && <Help id={helpId}>{help}</Help>
|
|
110
|
+
)}
|
|
111
|
+
</Container>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
FormItem.displayName = 'FormItem';
|
|
117
|
+
|
|
118
|
+
export default FormItem;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import styled from '@emotion/styled';
|
|
2
|
+
import { clr } from '@os-design/theming';
|
|
3
|
+
import { omitEmotionProps } from '@os-design/utils';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
interface StatusProps {
|
|
7
|
+
count: number;
|
|
8
|
+
current: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ContainerProps {
|
|
13
|
+
height: number;
|
|
14
|
+
}
|
|
15
|
+
const Container = styled('div', omitEmotionProps('height'))<ContainerProps>`
|
|
16
|
+
position: absolute;
|
|
17
|
+
bottom: 0;
|
|
18
|
+
left: 0;
|
|
19
|
+
right: 0;
|
|
20
|
+
height: ${(p) => p.height}px;
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
interface BlockProps {
|
|
24
|
+
count: number;
|
|
25
|
+
index: number;
|
|
26
|
+
current: boolean;
|
|
27
|
+
}
|
|
28
|
+
const Block = styled(
|
|
29
|
+
'div',
|
|
30
|
+
omitEmotionProps('count', 'index', 'current')
|
|
31
|
+
)<BlockProps>`
|
|
32
|
+
position: absolute;
|
|
33
|
+
left: ${(p) => (100 / p.count) * p.index}%;
|
|
34
|
+
width: ${(p) =>
|
|
35
|
+
p.index < p.count - 1
|
|
36
|
+
? `calc(${100 / p.count}% - 2px)`
|
|
37
|
+
: `${100 / p.count}%`};
|
|
38
|
+
height: 100%;
|
|
39
|
+
|
|
40
|
+
background-color: ${(p) =>
|
|
41
|
+
p.current
|
|
42
|
+
? clr(p.theme.galleryStatusColorBgCurrent)
|
|
43
|
+
: clr(p.theme.galleryStatusColorBg)};
|
|
44
|
+
backdrop-filter: blur(0.2em);
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const Status: React.FC<StatusProps> = ({ count, current, height }) => (
|
|
48
|
+
<Container height={height}>
|
|
49
|
+
{Array(count)
|
|
50
|
+
.fill(0)
|
|
51
|
+
.map((_, index) => (
|
|
52
|
+
<Block
|
|
53
|
+
key={index} // eslint-disable-line react/no-array-index-key
|
|
54
|
+
count={count}
|
|
55
|
+
index={index}
|
|
56
|
+
current={index === current}
|
|
57
|
+
/>
|
|
58
|
+
))}
|
|
59
|
+
</Container>
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
export default Status;
|