@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,83 @@
|
|
|
1
|
+
import styled from '@emotion/styled';
|
|
2
|
+
import { Right } from '@os-design/icons';
|
|
3
|
+
import { ellipsisStyles } from '@os-design/styles';
|
|
4
|
+
import { clr } from '@os-design/theming';
|
|
5
|
+
import React, { forwardRef } from 'react';
|
|
6
|
+
import Link, { LinkProps } from '../Link';
|
|
7
|
+
|
|
8
|
+
export interface BreadcrumbItemProps extends LinkProps {
|
|
9
|
+
/**
|
|
10
|
+
* Whether the item is the current page.
|
|
11
|
+
* @default false
|
|
12
|
+
*/
|
|
13
|
+
currentPage?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the right arrow located to the right of the text is visible.
|
|
16
|
+
* @default false
|
|
17
|
+
*/
|
|
18
|
+
hasRightArrow?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The position of the breadcrumb item.
|
|
21
|
+
* @default undefined
|
|
22
|
+
*/
|
|
23
|
+
position?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const Container = styled.li`
|
|
27
|
+
list-style: none;
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const Name = styled.span`
|
|
31
|
+
max-width: 20em;
|
|
32
|
+
${ellipsisStyles};
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const RightIcon = styled(Right)`
|
|
36
|
+
color: ${(p) => clr(p.theme.colorText)};
|
|
37
|
+
margin: 0 0.6em;
|
|
38
|
+
font-size: 0.6em;
|
|
39
|
+
opacity: 0.8;
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The item of the breadcrumb.
|
|
44
|
+
*/
|
|
45
|
+
const BreadcrumbItem = forwardRef<HTMLAnchorElement, BreadcrumbItemProps>(
|
|
46
|
+
(
|
|
47
|
+
{
|
|
48
|
+
currentPage = false,
|
|
49
|
+
hasRightArrow = false,
|
|
50
|
+
position,
|
|
51
|
+
href,
|
|
52
|
+
children,
|
|
53
|
+
...rest
|
|
54
|
+
},
|
|
55
|
+
ref
|
|
56
|
+
) => (
|
|
57
|
+
<Container>
|
|
58
|
+
<Link
|
|
59
|
+
itemProp='itemListElement'
|
|
60
|
+
itemScope
|
|
61
|
+
itemType='https://schema.org/ListItem'
|
|
62
|
+
href={href}
|
|
63
|
+
{...(currentPage
|
|
64
|
+
? {
|
|
65
|
+
underline: 'always',
|
|
66
|
+
'aria-current': 'page',
|
|
67
|
+
}
|
|
68
|
+
: {})}
|
|
69
|
+
{...rest}
|
|
70
|
+
ref={ref}
|
|
71
|
+
>
|
|
72
|
+
<link itemProp='item' href={href} />
|
|
73
|
+
{position && <meta itemProp='position' content={position.toString()} />}
|
|
74
|
+
<Name itemProp='name'>{children}</Name>
|
|
75
|
+
</Link>
|
|
76
|
+
{hasRightArrow && <RightIcon role='presentation' />}
|
|
77
|
+
</Container>
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
BreadcrumbItem.displayName = 'BreadcrumbItem';
|
|
82
|
+
|
|
83
|
+
export default BreadcrumbItem;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { keyframes } from '@emotion/react';
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
|
|
4
|
+
import { Loading } from '@os-design/icons';
|
|
5
|
+
import { clr } from '@os-design/theming';
|
|
6
|
+
|
|
7
|
+
import { omitEmotionProps } from '@os-design/utils';
|
|
8
|
+
import React from 'react';
|
|
9
|
+
|
|
10
|
+
import { LoadingColors } from './utils/useButtonColors';
|
|
11
|
+
|
|
12
|
+
interface ButtonContentProps {
|
|
13
|
+
left?: React.ReactNode;
|
|
14
|
+
right?: React.ReactNode;
|
|
15
|
+
loading?: boolean;
|
|
16
|
+
loadingColors: LoadingColors;
|
|
17
|
+
children?: React.ReactNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const LeftAddon = styled.div`
|
|
21
|
+
display: inherit;
|
|
22
|
+
padding-right: ${(p) => p.theme.buttonAddonPaddingHorizontal}em;
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
const RightAddon = styled.div`
|
|
26
|
+
display: inherit;
|
|
27
|
+
padding-left: ${(p) => p.theme.buttonAddonPaddingHorizontal}em;
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const Content = styled.span`
|
|
31
|
+
display: inherit;
|
|
32
|
+
|
|
33
|
+
& > svg {
|
|
34
|
+
transform: scale(${(p) => p.theme.buttonIconScaleFactor});
|
|
35
|
+
vertical-align: middle;
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
const LoadingIcon = styled(Loading)`
|
|
40
|
+
font-size: 1.2em;
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
const loadingFadeIn = keyframes`
|
|
44
|
+
from { opacity: 0; }
|
|
45
|
+
to { opacity: 1; }
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
interface LoadingContainerProps {
|
|
49
|
+
colors: LoadingColors;
|
|
50
|
+
}
|
|
51
|
+
const LoadingContainer = styled(
|
|
52
|
+
'div',
|
|
53
|
+
omitEmotionProps('colors')
|
|
54
|
+
)<LoadingContainerProps>`
|
|
55
|
+
position: absolute;
|
|
56
|
+
top: 0;
|
|
57
|
+
left: 0;
|
|
58
|
+
right: 0;
|
|
59
|
+
bottom: 0;
|
|
60
|
+
border-radius: inherit;
|
|
61
|
+
|
|
62
|
+
display: flex;
|
|
63
|
+
justify-content: center;
|
|
64
|
+
align-items: center;
|
|
65
|
+
|
|
66
|
+
color: ${(p) => clr(p.colors.text)};
|
|
67
|
+
background-color: ${(p) => clr(p.colors.bg)};
|
|
68
|
+
animation: ${loadingFadeIn} ${(p) => p.theme.transitionDelay}ms;
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
// Used by Button, LinkButton
|
|
72
|
+
const ButtonContent: React.FC<ButtonContentProps> = ({
|
|
73
|
+
left,
|
|
74
|
+
right,
|
|
75
|
+
loading = false,
|
|
76
|
+
loadingColors,
|
|
77
|
+
children,
|
|
78
|
+
}) => (
|
|
79
|
+
<>
|
|
80
|
+
{left && <LeftAddon>{left}</LeftAddon>}
|
|
81
|
+
<Content>{children}</Content>
|
|
82
|
+
{right && <RightAddon>{right}</RightAddon>}
|
|
83
|
+
{loading && (
|
|
84
|
+
<LoadingContainer colors={loadingColors}>
|
|
85
|
+
<LoadingIcon />
|
|
86
|
+
</LoadingContainer>
|
|
87
|
+
)}
|
|
88
|
+
</>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
export default ButtonContent;
|
|
@@ -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;
|