@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,225 @@
|
|
|
1
|
+
import { css } from '@emotion/react';
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
import { m } from '@os-design/media';
|
|
4
|
+
import {
|
|
5
|
+
resetButtonStyles,
|
|
6
|
+
sizeStyles,
|
|
7
|
+
transitionStyles,
|
|
8
|
+
WithSize,
|
|
9
|
+
} from '@os-design/styles';
|
|
10
|
+
import { clr } from '@os-design/theming';
|
|
11
|
+
import { omitEmotionProps } from '@os-design/utils';
|
|
12
|
+
import React, { forwardRef } from 'react';
|
|
13
|
+
import ButtonContent from './ButtonContent';
|
|
14
|
+
import useButtonColors, { ButtonColors } from './utils/useButtonColors';
|
|
15
|
+
|
|
16
|
+
type JsxButtonProps = Omit<
|
|
17
|
+
JSX.IntrinsicElements['button'],
|
|
18
|
+
'type' | 'color' | 'ref'
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
// Used by Button, LinkButton
|
|
22
|
+
export interface BaseButtonProps extends WithSize {
|
|
23
|
+
/**
|
|
24
|
+
* Type of button styles.
|
|
25
|
+
* @default primary
|
|
26
|
+
*/
|
|
27
|
+
type?: 'primary' | 'outline' | 'ghost';
|
|
28
|
+
/**
|
|
29
|
+
* Sets the danger styles.
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
danger?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* The component located on the left side.
|
|
35
|
+
* @default undefined
|
|
36
|
+
*/
|
|
37
|
+
left?: React.ReactNode;
|
|
38
|
+
/**
|
|
39
|
+
* The component located on the right side.
|
|
40
|
+
* @default undefined
|
|
41
|
+
*/
|
|
42
|
+
right?: React.ReactNode;
|
|
43
|
+
/**
|
|
44
|
+
* Whether the button has full width.
|
|
45
|
+
* Possible values:
|
|
46
|
+
* `default` – the button has full width if the screen width is less than xs;
|
|
47
|
+
* `always` – the button always has full width;
|
|
48
|
+
* `never` – the button never has full width.
|
|
49
|
+
* @default default
|
|
50
|
+
*/
|
|
51
|
+
wide?: 'default' | 'always' | 'never';
|
|
52
|
+
/**
|
|
53
|
+
* Shows the loading status and disables the button.
|
|
54
|
+
* @default false
|
|
55
|
+
*/
|
|
56
|
+
loading?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Whether the button is disabled.
|
|
59
|
+
* @default false
|
|
60
|
+
*/
|
|
61
|
+
disabled?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type ButtonProps = JsxButtonProps & BaseButtonProps;
|
|
65
|
+
|
|
66
|
+
interface StyledButtonProps
|
|
67
|
+
extends Pick<ButtonProps, 'wide' | 'loading' | 'disabled' | 'size'> {
|
|
68
|
+
btnType: ButtonProps['type'];
|
|
69
|
+
colors: ButtonColors;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const hoverStyles = (p) =>
|
|
73
|
+
!p.disabled &&
|
|
74
|
+
css`
|
|
75
|
+
@media (hover: hover) {
|
|
76
|
+
&:hover,
|
|
77
|
+
&:focus {
|
|
78
|
+
background-color: ${clr(p.colors.bgHover)};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
const primaryStyles = (p) =>
|
|
84
|
+
p.btnType === 'primary' &&
|
|
85
|
+
css`
|
|
86
|
+
color: ${clr(p.colors.text)};
|
|
87
|
+
background-color: ${clr(p.colors.bg)};
|
|
88
|
+
${hoverStyles(p)};
|
|
89
|
+
`;
|
|
90
|
+
|
|
91
|
+
const ghostStyles = (p) =>
|
|
92
|
+
p.btnType === 'ghost' &&
|
|
93
|
+
css`
|
|
94
|
+
color: ${clr(p.colors.text)};
|
|
95
|
+
background-color: transparent;
|
|
96
|
+
${hoverStyles(p)};
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
const outlineStyles = (p) =>
|
|
100
|
+
p.btnType === 'outline' &&
|
|
101
|
+
css`
|
|
102
|
+
color: ${clr(p.colors.text)};
|
|
103
|
+
background-color: transparent;
|
|
104
|
+
border: 1px solid currentColor;
|
|
105
|
+
${hoverStyles(p)};
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
const wideDefaultStyles = (p) =>
|
|
109
|
+
p.wide === 'default' &&
|
|
110
|
+
css`
|
|
111
|
+
${m.max.xxs} {
|
|
112
|
+
width: 100%;
|
|
113
|
+
}
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
const wideAlwaysStyles = (p) =>
|
|
117
|
+
p.wide === 'always' &&
|
|
118
|
+
css`
|
|
119
|
+
width: 100%;
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
const disabledStyles = (p) =>
|
|
123
|
+
p.disabled &&
|
|
124
|
+
css`
|
|
125
|
+
cursor: not-allowed;
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
export const StyledButton = styled(
|
|
129
|
+
'button',
|
|
130
|
+
omitEmotionProps('btnType', 'colors', 'wide', 'loading', 'size')
|
|
131
|
+
)<StyledButtonProps>`
|
|
132
|
+
${resetButtonStyles};
|
|
133
|
+
position: relative; // Because LoadingContainer has an absolute position
|
|
134
|
+
cursor: pointer;
|
|
135
|
+
user-select: none;
|
|
136
|
+
box-sizing: border-box; // Important for LinkButton
|
|
137
|
+
|
|
138
|
+
// Disable multiline
|
|
139
|
+
white-space: nowrap;
|
|
140
|
+
overflow: hidden;
|
|
141
|
+
|
|
142
|
+
border-radius: ${(p) => p.theme.borderRadius}em;
|
|
143
|
+
height: ${(p) => p.theme.buttonHeight}em;
|
|
144
|
+
padding: 0 ${(p) => p.theme.buttonPaddingHorizontal}em;
|
|
145
|
+
|
|
146
|
+
// Do not set inline-flex, otherwise the mobile safari cuts off
|
|
147
|
+
// the bottom border of the button (tested in iPhone 6)
|
|
148
|
+
display: flex;
|
|
149
|
+
justify-content: center;
|
|
150
|
+
align-items: center;
|
|
151
|
+
|
|
152
|
+
font-weight: 500;
|
|
153
|
+
line-height: 1;
|
|
154
|
+
|
|
155
|
+
${primaryStyles};
|
|
156
|
+
${outlineStyles};
|
|
157
|
+
${ghostStyles};
|
|
158
|
+
|
|
159
|
+
${wideDefaultStyles};
|
|
160
|
+
${wideAlwaysStyles};
|
|
161
|
+
|
|
162
|
+
${disabledStyles};
|
|
163
|
+
|
|
164
|
+
${sizeStyles};
|
|
165
|
+
${transitionStyles('background-color', 'color')};
|
|
166
|
+
`;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Used to trigger the corresponding business logic.
|
|
170
|
+
*/
|
|
171
|
+
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
172
|
+
(
|
|
173
|
+
{
|
|
174
|
+
type = 'primary',
|
|
175
|
+
danger = false,
|
|
176
|
+
left,
|
|
177
|
+
right,
|
|
178
|
+
wide = 'default',
|
|
179
|
+
loading = false,
|
|
180
|
+
disabled = false,
|
|
181
|
+
size,
|
|
182
|
+
children,
|
|
183
|
+
onMouseDown = () => {},
|
|
184
|
+
...rest
|
|
185
|
+
},
|
|
186
|
+
ref
|
|
187
|
+
) => {
|
|
188
|
+
const { buttonColors, loadingColors } = useButtonColors({
|
|
189
|
+
type,
|
|
190
|
+
danger,
|
|
191
|
+
disabled,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<StyledButton
|
|
196
|
+
btnType={type}
|
|
197
|
+
colors={buttonColors}
|
|
198
|
+
wide={wide}
|
|
199
|
+
loading={loading}
|
|
200
|
+
disabled={disabled || loading}
|
|
201
|
+
size={size}
|
|
202
|
+
onMouseDown={(e) => {
|
|
203
|
+
onMouseDown(e);
|
|
204
|
+
e.preventDefault();
|
|
205
|
+
}}
|
|
206
|
+
aria-busy={loading}
|
|
207
|
+
{...rest}
|
|
208
|
+
ref={ref}
|
|
209
|
+
>
|
|
210
|
+
<ButtonContent
|
|
211
|
+
left={left}
|
|
212
|
+
right={right}
|
|
213
|
+
loading={loading}
|
|
214
|
+
loadingColors={loadingColors}
|
|
215
|
+
>
|
|
216
|
+
{children}
|
|
217
|
+
</ButtonContent>
|
|
218
|
+
</StyledButton>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
Button.displayName = 'Button';
|
|
224
|
+
|
|
225
|
+
export default Button;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Color, useTheme } from '@os-design/theming';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
type: 'primary' | 'outline' | 'ghost';
|
|
6
|
+
danger: boolean;
|
|
7
|
+
disabled: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ButtonColors {
|
|
11
|
+
text: Color;
|
|
12
|
+
bg?: Color;
|
|
13
|
+
bgHover?: Color;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LoadingColors {
|
|
17
|
+
text: Color;
|
|
18
|
+
bg: Color;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface UseButtonColorsRes {
|
|
22
|
+
buttonColors: ButtonColors;
|
|
23
|
+
loadingColors: LoadingColors;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Used by Button, LinkButton
|
|
27
|
+
const useButtonColors = ({
|
|
28
|
+
type,
|
|
29
|
+
danger,
|
|
30
|
+
disabled,
|
|
31
|
+
}: Props): UseButtonColorsRes => {
|
|
32
|
+
const { theme } = useTheme();
|
|
33
|
+
|
|
34
|
+
const prefix = useMemo<string>(() => {
|
|
35
|
+
if (danger) return 'Danger';
|
|
36
|
+
return '';
|
|
37
|
+
}, [danger]);
|
|
38
|
+
|
|
39
|
+
const buttonColors = useMemo<ButtonColors>(() => {
|
|
40
|
+
if (type === 'primary') {
|
|
41
|
+
return !disabled
|
|
42
|
+
? {
|
|
43
|
+
text: theme[`button${prefix}PrimaryColorText`],
|
|
44
|
+
bg: theme[`button${prefix}PrimaryColorBg`],
|
|
45
|
+
bgHover: theme[`button${prefix}PrimaryColorBgHover`],
|
|
46
|
+
}
|
|
47
|
+
: {
|
|
48
|
+
text: theme.buttonDisabledPrimaryColorText,
|
|
49
|
+
bg: theme.buttonDisabledPrimaryColorBg,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return !disabled
|
|
53
|
+
? {
|
|
54
|
+
text: theme[`button${prefix}GhostColorText`],
|
|
55
|
+
bgHover: theme[`button${prefix}GhostColorBgHover`],
|
|
56
|
+
}
|
|
57
|
+
: {
|
|
58
|
+
text: theme.buttonDisabledGhostColorText,
|
|
59
|
+
};
|
|
60
|
+
}, [type, disabled, theme, prefix]);
|
|
61
|
+
|
|
62
|
+
const loadingColors = useMemo<LoadingColors>(() => {
|
|
63
|
+
if (disabled) {
|
|
64
|
+
return {
|
|
65
|
+
text: theme.buttonDisabledPrimaryColorText,
|
|
66
|
+
bg: theme.buttonDisabledPrimaryColorBg,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (type === 'primary') {
|
|
70
|
+
return {
|
|
71
|
+
text: theme[`button${prefix}PrimaryColorLoadingText`],
|
|
72
|
+
bg: theme[`button${prefix}PrimaryColorLoadingBg`],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
text: theme[`button${prefix}GhostColorLoadingText`],
|
|
77
|
+
bg: theme[`button${prefix}GhostColorLoadingBg`],
|
|
78
|
+
};
|
|
79
|
+
}, [disabled, type, theme, prefix]);
|
|
80
|
+
|
|
81
|
+
return { buttonColors, loadingColors };
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export default useButtonColors;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { css } from '@emotion/react';
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
import { Check } from '@os-design/icons';
|
|
4
|
+
import {
|
|
5
|
+
resetFocusStyles,
|
|
6
|
+
sizeStyles,
|
|
7
|
+
transitionStyles,
|
|
8
|
+
WithSize,
|
|
9
|
+
} from '@os-design/styles';
|
|
10
|
+
import { clr } from '@os-design/theming';
|
|
11
|
+
import { omitEmotionProps, useForwardedState } from '@os-design/utils';
|
|
12
|
+
import React, { forwardRef } from 'react';
|
|
13
|
+
|
|
14
|
+
type JsxLabelProps = Omit<
|
|
15
|
+
JSX.IntrinsicElements['label'],
|
|
16
|
+
'defaultValue' | 'onChange' | 'onClick' | 'ref'
|
|
17
|
+
>;
|
|
18
|
+
export interface CheckboxProps extends JsxLabelProps, WithSize {
|
|
19
|
+
/**
|
|
20
|
+
* Whether the checkbox is disabled.
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether the checkbox is checked.
|
|
26
|
+
* @default false
|
|
27
|
+
*/
|
|
28
|
+
value?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* The default value.
|
|
31
|
+
* @default undefined
|
|
32
|
+
*/
|
|
33
|
+
defaultValue?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The change event handler.
|
|
36
|
+
* @default undefined
|
|
37
|
+
*/
|
|
38
|
+
onChange?: (value: boolean) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const uncheckedIconStyles = (p) =>
|
|
42
|
+
!p.checked &&
|
|
43
|
+
css`
|
|
44
|
+
background-color: ${clr(p.theme.checkboxUncheckedColorBg)};
|
|
45
|
+
border-color: ${clr(p.theme.checkboxUncheckedColorBorder)};
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const checkedIconStyles = (p) =>
|
|
49
|
+
p.checked &&
|
|
50
|
+
css`
|
|
51
|
+
background-color: ${clr(p.theme.checkboxCheckedColorBg)};
|
|
52
|
+
border-color: ${clr(p.theme.checkboxCheckedColorBg)};
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
const disabledIconStyles = (p) =>
|
|
56
|
+
p.disabled &&
|
|
57
|
+
css`
|
|
58
|
+
background-color: ${clr(p.theme.checkboxDisabledColorBg)};
|
|
59
|
+
color: ${clr(p.theme.checkboxDisabledColorIcon)};
|
|
60
|
+
border-color: ${clr(p.theme.checkboxDisabledColorBorder)};
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
interface IconContainerProps extends Required<Pick<CheckboxProps, 'disabled'>> {
|
|
64
|
+
checked: Required<CheckboxProps['value']>;
|
|
65
|
+
}
|
|
66
|
+
const IconContainer = styled(
|
|
67
|
+
'span',
|
|
68
|
+
omitEmotionProps('disabled', 'checked')
|
|
69
|
+
)<IconContainerProps>`
|
|
70
|
+
width: ${(p) => p.theme.checkboxSize}em;
|
|
71
|
+
height: ${(p) => p.theme.checkboxSize}em;
|
|
72
|
+
min-width: ${(p) => p.theme.checkboxSize}em;
|
|
73
|
+
min-height: ${(p) => p.theme.checkboxSize}em;
|
|
74
|
+
|
|
75
|
+
display: flex;
|
|
76
|
+
justify-content: center;
|
|
77
|
+
align-items: center;
|
|
78
|
+
|
|
79
|
+
box-sizing: border-box;
|
|
80
|
+
line-height: 1;
|
|
81
|
+
|
|
82
|
+
border: 1px solid transparent;
|
|
83
|
+
border-radius: ${(p) => p.theme.borderRadius}em;
|
|
84
|
+
color: ${(p) => clr(p.theme.checkboxCheckedColorIcon)};
|
|
85
|
+
margin-top: ${(p) => (p.theme.lineHeight - p.theme.checkboxSize) / 2}em;
|
|
86
|
+
|
|
87
|
+
${uncheckedIconStyles};
|
|
88
|
+
${checkedIconStyles};
|
|
89
|
+
${disabledIconStyles};
|
|
90
|
+
${transitionStyles('background-color', 'color', 'border-color')};
|
|
91
|
+
`;
|
|
92
|
+
|
|
93
|
+
const hoverUncheckedIconStyles = (p) =>
|
|
94
|
+
!p.checked &&
|
|
95
|
+
css`
|
|
96
|
+
background-color: ${clr(p.theme.checkboxUncheckedColorBgHover)};
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
const hoverCheckedIconStyles = (p) =>
|
|
100
|
+
p.checked &&
|
|
101
|
+
css`
|
|
102
|
+
background-color: ${clr(p.theme.checkboxCheckedColorBgHover)};
|
|
103
|
+
border-color: ${clr(p.theme.checkboxCheckedColorBgHover)};
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
const hoverStyles = (p) =>
|
|
107
|
+
!p.disabled &&
|
|
108
|
+
css`
|
|
109
|
+
@media (hover: hover) {
|
|
110
|
+
&:hover,
|
|
111
|
+
&:focus {
|
|
112
|
+
& > span {
|
|
113
|
+
${hoverUncheckedIconStyles(p)};
|
|
114
|
+
${hoverCheckedIconStyles(p)};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
const disabledContainerStyles = (p) =>
|
|
121
|
+
p.disabled &&
|
|
122
|
+
css`
|
|
123
|
+
cursor: not-allowed;
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
interface ContainerProps
|
|
127
|
+
extends Required<Pick<CheckboxProps, 'disabled'>>,
|
|
128
|
+
Pick<CheckboxProps, 'size'> {
|
|
129
|
+
checked: Required<CheckboxProps['value']>;
|
|
130
|
+
}
|
|
131
|
+
const Container = styled(
|
|
132
|
+
'label',
|
|
133
|
+
omitEmotionProps('disabled', 'size', 'checked')
|
|
134
|
+
)<ContainerProps>`
|
|
135
|
+
${resetFocusStyles};
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
user-select: none;
|
|
138
|
+
|
|
139
|
+
display: flex;
|
|
140
|
+
margin: ${(p) => p.theme.checkboxVerticalIndent}em 0;
|
|
141
|
+
|
|
142
|
+
${hoverStyles};
|
|
143
|
+
${disabledContainerStyles};
|
|
144
|
+
${sizeStyles};
|
|
145
|
+
${transitionStyles('color')};
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
const disabledTextStyles = (p) =>
|
|
149
|
+
p.disabled &&
|
|
150
|
+
css`
|
|
151
|
+
color: ${clr(p.theme.checkboxDisabledColorText)};
|
|
152
|
+
`;
|
|
153
|
+
|
|
154
|
+
type TextProps = Required<Pick<CheckboxProps, 'disabled'>>;
|
|
155
|
+
const Text = styled('div', omitEmotionProps('disabled'))<TextProps>`
|
|
156
|
+
margin-left: 0.4em;
|
|
157
|
+
color: ${(p) => clr(p.theme.colorText)};
|
|
158
|
+
${disabledTextStyles};
|
|
159
|
+
`;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The checkbox that can be enabled or disabled.
|
|
163
|
+
*/
|
|
164
|
+
const Checkbox = forwardRef<HTMLLabelElement, CheckboxProps>(
|
|
165
|
+
(
|
|
166
|
+
{
|
|
167
|
+
disabled = false,
|
|
168
|
+
value,
|
|
169
|
+
defaultValue,
|
|
170
|
+
onChange = () => {},
|
|
171
|
+
size,
|
|
172
|
+
onKeyDown = () => {},
|
|
173
|
+
onMouseDown = () => {},
|
|
174
|
+
children,
|
|
175
|
+
...rest
|
|
176
|
+
},
|
|
177
|
+
ref
|
|
178
|
+
) => {
|
|
179
|
+
const [forwardedValue, setForwardedValue] = useForwardedState({
|
|
180
|
+
value,
|
|
181
|
+
defaultValue,
|
|
182
|
+
onChange,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<Container
|
|
187
|
+
disabled={disabled}
|
|
188
|
+
checked={forwardedValue}
|
|
189
|
+
size={size}
|
|
190
|
+
tabIndex={!disabled ? 0 : -1}
|
|
191
|
+
onClick={() => {
|
|
192
|
+
if (disabled) return;
|
|
193
|
+
setForwardedValue(!forwardedValue);
|
|
194
|
+
}}
|
|
195
|
+
onKeyDown={(e) => {
|
|
196
|
+
if (disabled) return;
|
|
197
|
+
if (['Enter', ' '].includes(e.key)) {
|
|
198
|
+
setForwardedValue(!forwardedValue);
|
|
199
|
+
e.preventDefault();
|
|
200
|
+
}
|
|
201
|
+
onKeyDown(e);
|
|
202
|
+
}}
|
|
203
|
+
onMouseDown={(e) => {
|
|
204
|
+
onMouseDown(e);
|
|
205
|
+
e.preventDefault();
|
|
206
|
+
}}
|
|
207
|
+
role='checkbox'
|
|
208
|
+
aria-checked={forwardedValue}
|
|
209
|
+
aria-disabled={disabled}
|
|
210
|
+
{...rest}
|
|
211
|
+
ref={ref}
|
|
212
|
+
>
|
|
213
|
+
<IconContainer disabled={disabled} checked={forwardedValue}>
|
|
214
|
+
{forwardedValue && <Check />}
|
|
215
|
+
</IconContainer>
|
|
216
|
+
|
|
217
|
+
{children && <Text disabled={disabled}>{children}</Text>}
|
|
218
|
+
</Container>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
Checkbox.displayName = 'Checkbox';
|
|
224
|
+
|
|
225
|
+
export default Checkbox;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import styled from '@emotion/styled';
|
|
2
|
+
|
|
3
|
+
import { sizeStyles, WithSize } from '@os-design/styles';
|
|
4
|
+
import { clr } from '@os-design/theming';
|
|
5
|
+
|
|
6
|
+
import { omitEmotionProps } from '@os-design/utils';
|
|
7
|
+
import React, { forwardRef } from 'react';
|
|
8
|
+
|
|
9
|
+
import Skeleton from '../Skeleton';
|
|
10
|
+
|
|
11
|
+
type JsxDivProps = Omit<JSX.IntrinsicElements['div'], 'ref'>;
|
|
12
|
+
export type CheckboxSkeletonProps = JsxDivProps & WithSize;
|
|
13
|
+
|
|
14
|
+
const Container = styled('div', omitEmotionProps('size'))<WithSize>`
|
|
15
|
+
user-select: none;
|
|
16
|
+
display: flex;
|
|
17
|
+
margin: ${(p) => p.theme.checkboxVerticalIndent}em 0;
|
|
18
|
+
${sizeStyles};
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const ImageSkeleton = styled(Skeleton)`
|
|
22
|
+
width: ${(p) => p.theme.checkboxSize}em;
|
|
23
|
+
height: ${(p) => p.theme.checkboxSize}em;
|
|
24
|
+
min-width: ${(p) => p.theme.checkboxSize}em;
|
|
25
|
+
min-height: ${(p) => p.theme.checkboxSize}em;
|
|
26
|
+
|
|
27
|
+
border-radius: ${(p) => p.theme.borderRadius}em;
|
|
28
|
+
margin-top: ${(p) => (p.theme.lineHeight - p.theme.checkboxSize) / 2}em;
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
const Text = styled.div`
|
|
32
|
+
margin-left: 0.4em;
|
|
33
|
+
color: ${(p) => clr(p.theme.colorText)};
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Provides a checkbox placeholder while a user waits for the content to load.
|
|
38
|
+
*/
|
|
39
|
+
const CheckboxSkeleton = forwardRef<HTMLDivElement, CheckboxSkeletonProps>(
|
|
40
|
+
({ children, ...rest }, ref) => (
|
|
41
|
+
<Container role='checkbox' aria-busy {...rest} ref={ref}>
|
|
42
|
+
<ImageSkeleton />
|
|
43
|
+
{children && <Text>{children}</Text>}
|
|
44
|
+
</Container>
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CheckboxSkeleton.displayName = 'CheckboxSkeleton';
|
|
49
|
+
|
|
50
|
+
export default CheckboxSkeleton;
|