@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.
Files changed (104) hide show
  1. package/dist/cjs/InputSearch/index.js +2 -2
  2. package/dist/cjs/InputSearch/index.js.map +1 -1
  3. package/dist/esm/InputSearch/index.js +2 -2
  4. package/dist/esm/InputSearch/index.js.map +1 -1
  5. package/dist/types/InputSearch/index.d.ts.map +1 -1
  6. package/package.json +21 -13
  7. package/src/@types/emotion.d.ts +7 -0
  8. package/src/Alert/index.tsx +112 -0
  9. package/src/Avatar/index.tsx +173 -0
  10. package/src/Avatar/utils/nameToInitials.ts +12 -0
  11. package/src/Avatar/utils/strToHue.ts +13 -0
  12. package/src/AvatarSkeleton/index.tsx +29 -0
  13. package/src/Breadcrumb/index.tsx +93 -0
  14. package/src/BreadcrumbItem/index.tsx +83 -0
  15. package/src/Button/ButtonContent.tsx +91 -0
  16. package/src/Button/index.tsx +225 -0
  17. package/src/Button/utils/useButtonColors.ts +84 -0
  18. package/src/Checkbox/index.tsx +225 -0
  19. package/src/CheckboxSkeleton/index.tsx +50 -0
  20. package/src/DatePicker/DatePickerCalendar.tsx +220 -0
  21. package/src/DatePicker/index.tsx +568 -0
  22. package/src/Drawer/index.tsx +212 -0
  23. package/src/Form/FormConfigContext.ts +16 -0
  24. package/src/Form/index.tsx +49 -0
  25. package/src/FormDivider/index.tsx +74 -0
  26. package/src/FormItem/index.tsx +118 -0
  27. package/src/Gallery/Status.tsx +62 -0
  28. package/src/Gallery/index.tsx +290 -0
  29. package/src/GlobalStyles/index.tsx +17 -0
  30. package/src/GlobalStyles/resetStyles.ts +17 -0
  31. package/src/GlobalStyles/typographyStyles.ts +78 -0
  32. package/src/HeaderSkeleton/index.tsx +64 -0
  33. package/src/Image/index.tsx +104 -0
  34. package/src/ImageSkeleton/index.tsx +22 -0
  35. package/src/Input/index.tsx +330 -0
  36. package/src/Input/utils/getFocusableElements.ts +8 -0
  37. package/src/InputNumber/index.tsx +208 -0
  38. package/src/InputNumber/utils/defaultLocale.ts +9 -0
  39. package/src/InputPassword/index.tsx +201 -0
  40. package/src/InputPassword/utils/defaultLocale.ts +11 -0
  41. package/src/InputSearch/index.tsx +111 -0
  42. package/src/InputSearch/utils/defaultLocale.ts +9 -0
  43. package/src/InputSkeleton/index.tsx +28 -0
  44. package/src/Layout/LayoutContext.ts +21 -0
  45. package/src/Layout/index.tsx +44 -0
  46. package/src/Link/index.tsx +129 -0
  47. package/src/LinkButton/index.tsx +100 -0
  48. package/src/List/WindowScroller.tsx +53 -0
  49. package/src/List/index.tsx +255 -0
  50. package/src/List/utils/bodyPointerEvents.ts +24 -0
  51. package/src/List/utils/frameTimeout.ts +36 -0
  52. package/src/List/utils/useRWLoadNext.ts +38 -0
  53. package/src/ListItem/index.tsx +92 -0
  54. package/src/ListItemActions/index.tsx +207 -0
  55. package/src/ListItemLink/index.tsx +63 -0
  56. package/src/ListSkeleton/index.tsx +115 -0
  57. package/src/LogoLink/index.tsx +93 -0
  58. package/src/LogoLink/logo.example.svg +18 -0
  59. package/src/Menu/index.tsx +128 -0
  60. package/src/Menu/utils/useFocusWithArrows.ts +50 -0
  61. package/src/MenuDivider/index.tsx +22 -0
  62. package/src/MenuGroup/index.tsx +190 -0
  63. package/src/MenuItem/index.tsx +108 -0
  64. package/src/Modal/index.tsx +411 -0
  65. package/src/Modal/utils/defaultLocale.ts +9 -0
  66. package/src/Navigation/index.tsx +214 -0
  67. package/src/Navigation/utils/useScrollFlags.ts +39 -0
  68. package/src/NavigationItem/index.tsx +136 -0
  69. package/src/PageContent/index.tsx +99 -0
  70. package/src/PageHeader/index.tsx +246 -0
  71. package/src/PageHeader/utils/defaultLocale.ts +9 -0
  72. package/src/PageHeaderInputSearch/index.tsx +145 -0
  73. package/src/PageHeaderInputSearch/utils/defaultLocale.ts +16 -0
  74. package/src/PageHeaderSkeleton/index.tsx +33 -0
  75. package/src/ParagraphSkeleton/index.tsx +65 -0
  76. package/src/Popover/index.tsx +243 -0
  77. package/src/Popover/utils/usePopoverPosition.ts +216 -0
  78. package/src/Progress/index.tsx +100 -0
  79. package/src/RadioGroup/index.tsx +165 -0
  80. package/src/RadioGroupSkeleton/index.tsx +36 -0
  81. package/src/Result/index.tsx +109 -0
  82. package/src/ScrollButton/index.tsx +159 -0
  83. package/src/ScrollButton/utils/useContainerPosition.ts +41 -0
  84. package/src/ScrollButton/utils/useVisibility.ts +56 -0
  85. package/src/Select/index.tsx +970 -0
  86. package/src/Select/utils/defaultLocale.ts +11 -0
  87. package/src/Skeleton/index.tsx +52 -0
  88. package/src/Switch/index.tsx +217 -0
  89. package/src/SwitchSkeleton/index.tsx +30 -0
  90. package/src/Tag/index.tsx +75 -0
  91. package/src/TagLink/index.tsx +53 -0
  92. package/src/TagList/index.tsx +95 -0
  93. package/src/TagListSkeleton/index.tsx +38 -0
  94. package/src/TagSkeleton/index.tsx +40 -0
  95. package/src/TextArea/index.tsx +231 -0
  96. package/src/TextAreaSkeleton/index.tsx +20 -0
  97. package/src/ThemeSwitcher/index.tsx +39 -0
  98. package/src/TimePicker/index.tsx +142 -0
  99. package/src/Video/index.tsx +41 -0
  100. package/src/index.ts +125 -0
  101. package/src/message/AlertIcon.tsx +50 -0
  102. package/src/message/Message.tsx +108 -0
  103. package/src/message/index.tsx +64 -0
  104. 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;