@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.
- 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,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;
|