@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,231 @@
|
|
|
1
|
+
import { css } from '@emotion/react';
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
import { Loading } from '@os-design/icons';
|
|
4
|
+
import { WithSize } from '@os-design/styles';
|
|
5
|
+
import { clr, ThemeOverrider } from '@os-design/theming';
|
|
6
|
+
import { omitEmotionProps, useForwardedRef } from '@os-design/utils';
|
|
7
|
+
import React, {
|
|
8
|
+
ChangeEvent,
|
|
9
|
+
FocusEventHandler,
|
|
10
|
+
ForwardedRef,
|
|
11
|
+
forwardRef,
|
|
12
|
+
KeyboardEventHandler,
|
|
13
|
+
useCallback,
|
|
14
|
+
useMemo,
|
|
15
|
+
} from 'react';
|
|
16
|
+
import { InputContainer, StyledInput } from '../Input';
|
|
17
|
+
import getFocusableElements from '../Input/utils/getFocusableElements';
|
|
18
|
+
|
|
19
|
+
type JsxTextAreaProps = Omit<
|
|
20
|
+
JSX.IntrinsicElements['textarea'],
|
|
21
|
+
'value' | 'onChange' | 'ref'
|
|
22
|
+
>;
|
|
23
|
+
export interface TextAreaProps extends JsxTextAreaProps, WithSize {
|
|
24
|
+
/**
|
|
25
|
+
* The component located on the left side.
|
|
26
|
+
* @default undefined
|
|
27
|
+
*/
|
|
28
|
+
left?: React.ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* Adds padding to the left component.
|
|
31
|
+
* It can be useful when passing an icon or text in the left component.
|
|
32
|
+
* @default false
|
|
33
|
+
*/
|
|
34
|
+
leftHasPadding?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* The component located on the right side.
|
|
37
|
+
* @default undefined
|
|
38
|
+
*/
|
|
39
|
+
right?: React.ReactNode;
|
|
40
|
+
/**
|
|
41
|
+
* Adds padding to the right component.
|
|
42
|
+
* It can be useful when passing an icon or text in the right component.
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
rightHasPadding?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the textarea is disabled.
|
|
48
|
+
* @default false
|
|
49
|
+
*/
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Shows the loading status.
|
|
53
|
+
* @default false
|
|
54
|
+
*/
|
|
55
|
+
loading?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* The ref of the textarea container.
|
|
58
|
+
* @default undefined
|
|
59
|
+
*/
|
|
60
|
+
containerRef?: ForwardedRef<HTMLDivElement>;
|
|
61
|
+
/**
|
|
62
|
+
* The props of the textarea container.
|
|
63
|
+
* @default undefined
|
|
64
|
+
*/
|
|
65
|
+
containerProps?: JSX.IntrinsicElements['div'];
|
|
66
|
+
/**
|
|
67
|
+
* The textarea value.
|
|
68
|
+
* @default undefined
|
|
69
|
+
*/
|
|
70
|
+
value?: string;
|
|
71
|
+
/**
|
|
72
|
+
* The change event handler.
|
|
73
|
+
* @default undefined
|
|
74
|
+
*/
|
|
75
|
+
onChange?: (value: string, e: ChangeEvent<HTMLTextAreaElement>) => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const TextAreaContainer = styled(InputContainer)`
|
|
79
|
+
height: ${(p) => p.theme.textAreaHeight}em;
|
|
80
|
+
`;
|
|
81
|
+
|
|
82
|
+
const TextAreaField = styled(StyledInput.withComponent('textarea'))`
|
|
83
|
+
padding-top: ${(p) => p.theme.textAreaPaddingVertical}em;
|
|
84
|
+
padding-bottom: ${(p) => p.theme.textAreaPaddingVertical}em;
|
|
85
|
+
line-height: ${(p) => p.theme.lineHeight};
|
|
86
|
+
resize: none;
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
interface AddonProps {
|
|
90
|
+
hasPadding: boolean;
|
|
91
|
+
}
|
|
92
|
+
const Addon = styled('span', omitEmotionProps('hasPadding'))<AddonProps>`
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
user-select: none;
|
|
96
|
+
color: ${(p) => clr(p.theme.inputColorPlaceholder)};
|
|
97
|
+
|
|
98
|
+
svg {
|
|
99
|
+
transform: scale(1.2);
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
const LeftAddon = styled(Addon)`
|
|
104
|
+
padding-right: ${(p) => p.theme.inputAddonPaddingHorizontal}em;
|
|
105
|
+
${(p) =>
|
|
106
|
+
p.hasPadding &&
|
|
107
|
+
css`
|
|
108
|
+
padding-left: ${p.theme.inputPaddingHorizontal}em;
|
|
109
|
+
`}
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
const RightAddon = styled(Addon)`
|
|
113
|
+
padding-left: ${(p) => p.theme.inputAddonPaddingHorizontal}em;
|
|
114
|
+
${(p) =>
|
|
115
|
+
p.hasPadding &&
|
|
116
|
+
css`
|
|
117
|
+
padding-right: ${p.theme.inputPaddingHorizontal}em;
|
|
118
|
+
`}
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The multiline input component.
|
|
123
|
+
*/
|
|
124
|
+
const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|
125
|
+
(
|
|
126
|
+
{
|
|
127
|
+
left,
|
|
128
|
+
leftHasPadding = false,
|
|
129
|
+
right,
|
|
130
|
+
rightHasPadding = false,
|
|
131
|
+
disabled = false,
|
|
132
|
+
loading = false,
|
|
133
|
+
containerRef,
|
|
134
|
+
containerProps = {},
|
|
135
|
+
size,
|
|
136
|
+
value,
|
|
137
|
+
onChange = () => {},
|
|
138
|
+
...rest
|
|
139
|
+
},
|
|
140
|
+
ref
|
|
141
|
+
) => {
|
|
142
|
+
const [innerContainerRef, mergedContainerRef] =
|
|
143
|
+
useForwardedRef(containerRef);
|
|
144
|
+
|
|
145
|
+
const rightValue = useMemo(
|
|
146
|
+
() => (loading ? <Loading /> : right),
|
|
147
|
+
[loading, right]
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const rightHasPaddingValue = useMemo(
|
|
151
|
+
() => (loading ? true : rightHasPadding),
|
|
152
|
+
[loading, rightHasPadding]
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const onFocus = useCallback<FocusEventHandler>(
|
|
156
|
+
(e) => {
|
|
157
|
+
// Focus the next element if the container element was focused.
|
|
158
|
+
// The next element will be the input or button in the addon.
|
|
159
|
+
if (disabled || e.target !== innerContainerRef.current) return;
|
|
160
|
+
const focusableElements = getFocusableElements(
|
|
161
|
+
innerContainerRef.current
|
|
162
|
+
);
|
|
163
|
+
focusableElements[0].focus();
|
|
164
|
+
},
|
|
165
|
+
[disabled, innerContainerRef]
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const onKeyDown = useCallback<KeyboardEventHandler>(
|
|
169
|
+
(e) => {
|
|
170
|
+
// Focus the previous element if the first element in the input
|
|
171
|
+
// container is focused and the Shift + Tab combination is pressed.
|
|
172
|
+
const focusableElements = getFocusableElements(document);
|
|
173
|
+
const inputFocusableElements = innerContainerRef.current
|
|
174
|
+
? getFocusableElements(innerContainerRef.current)
|
|
175
|
+
: [];
|
|
176
|
+
const firstInputElementIsFocused =
|
|
177
|
+
inputFocusableElements[0] === document.activeElement;
|
|
178
|
+
if (firstInputElementIsFocused && e.key === 'Tab' && e.shiftKey) {
|
|
179
|
+
const inputContainerIndex = focusableElements.findIndex(
|
|
180
|
+
(el) => el === innerContainerRef.current
|
|
181
|
+
);
|
|
182
|
+
if (inputContainerIndex === 0) return;
|
|
183
|
+
const elementBeforeInputContainer =
|
|
184
|
+
focusableElements[inputContainerIndex - 1];
|
|
185
|
+
elementBeforeInputContainer.focus();
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
[innerContainerRef]
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<TextAreaContainer
|
|
193
|
+
disabled={disabled}
|
|
194
|
+
size={size}
|
|
195
|
+
tabIndex={!disabled ? 0 : -1}
|
|
196
|
+
onFocus={onFocus}
|
|
197
|
+
onKeyDown={onKeyDown}
|
|
198
|
+
ref={mergedContainerRef}
|
|
199
|
+
{...containerProps}
|
|
200
|
+
>
|
|
201
|
+
{left && (
|
|
202
|
+
<ThemeOverrider overrides={{ buttonPaddingHorizontal: 0.8 }}>
|
|
203
|
+
<LeftAddon hasPadding={leftHasPadding}>{left}</LeftAddon>
|
|
204
|
+
</ThemeOverrider>
|
|
205
|
+
)}
|
|
206
|
+
|
|
207
|
+
<TextAreaField
|
|
208
|
+
disabled={disabled}
|
|
209
|
+
hasLeft={!!left}
|
|
210
|
+
hasRight={!!right}
|
|
211
|
+
value={value || ''}
|
|
212
|
+
onChange={(e) => onChange(e.target.value, e)}
|
|
213
|
+
{...rest}
|
|
214
|
+
ref={ref}
|
|
215
|
+
/>
|
|
216
|
+
|
|
217
|
+
{rightValue && (
|
|
218
|
+
<ThemeOverrider overrides={{ buttonPaddingHorizontal: 0.8 }}>
|
|
219
|
+
<RightAddon hasPadding={rightHasPaddingValue}>
|
|
220
|
+
{rightValue}
|
|
221
|
+
</RightAddon>
|
|
222
|
+
</ThemeOverrider>
|
|
223
|
+
)}
|
|
224
|
+
</TextAreaContainer>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
TextArea.displayName = 'TextArea';
|
|
230
|
+
|
|
231
|
+
export default TextArea;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import styled from '@emotion/styled';
|
|
2
|
+
import React, { forwardRef } from 'react';
|
|
3
|
+
import InputSkeleton, { InputSkeletonProps } from '../InputSkeleton';
|
|
4
|
+
|
|
5
|
+
export type TextAreaSkeletonProps = InputSkeletonProps;
|
|
6
|
+
|
|
7
|
+
const StyledTextAreaSkeleton = styled(InputSkeleton)`
|
|
8
|
+
height: ${(p) => p.theme.textAreaHeight}em;
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Provides a textarea placeholder while a user waits for the content to load.
|
|
13
|
+
*/
|
|
14
|
+
const TextAreaSkeleton = forwardRef<HTMLDivElement, TextAreaSkeletonProps>(
|
|
15
|
+
(props, ref) => <StyledTextAreaSkeleton {...props} ref={ref} />
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
TextAreaSkeleton.displayName = 'TextAreaSkeleton';
|
|
19
|
+
|
|
20
|
+
export default TextAreaSkeleton;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Moon, Sun } from '@os-design/icons';
|
|
2
|
+
|
|
3
|
+
import { useTheme } from '@os-design/theming';
|
|
4
|
+
import React, { forwardRef } from 'react';
|
|
5
|
+
|
|
6
|
+
import Button, { ButtonProps } from '../Button';
|
|
7
|
+
|
|
8
|
+
export type ThemeSwitcherProps = ButtonProps;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The button to switch the current theme.
|
|
12
|
+
*/
|
|
13
|
+
const ThemeSwitcher = forwardRef<HTMLButtonElement, ThemeSwitcherProps>(
|
|
14
|
+
({ onClick = () => {}, ...rest }, ref) => {
|
|
15
|
+
const { activeTheme, setActiveTheme } = useTheme();
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Button
|
|
19
|
+
type='ghost'
|
|
20
|
+
wide='never'
|
|
21
|
+
onClick={(e) => {
|
|
22
|
+
setActiveTheme(activeTheme === 'light' ? 'dark' : 'light');
|
|
23
|
+
onClick(e);
|
|
24
|
+
}}
|
|
25
|
+
role='switch'
|
|
26
|
+
aria-checked={activeTheme === 'dark'}
|
|
27
|
+
aria-label='Dark theme'
|
|
28
|
+
{...rest}
|
|
29
|
+
ref={ref}
|
|
30
|
+
>
|
|
31
|
+
{activeTheme === 'light' ? <Moon /> : <Sun />}
|
|
32
|
+
</Button>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
ThemeSwitcher.displayName = 'ThemeSwitcher';
|
|
38
|
+
|
|
39
|
+
export default ThemeSwitcher;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { useTime } from '@os-design/time-picker-utils';
|
|
2
|
+
import { useForwardedRef, useForwardedState } from '@os-design/utils';
|
|
3
|
+
import React, {
|
|
4
|
+
forwardRef,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import Button from '../Button';
|
|
11
|
+
import Input, { InputProps } from '../Input';
|
|
12
|
+
|
|
13
|
+
export interface TimePickerProps
|
|
14
|
+
extends Omit<InputProps, 'type' | 'value' | 'defaultValue' | 'onChange'> {
|
|
15
|
+
/**
|
|
16
|
+
* The time notation.
|
|
17
|
+
* @default 12-hour
|
|
18
|
+
*/
|
|
19
|
+
notation?: '12-hour' | '24-hour';
|
|
20
|
+
/**
|
|
21
|
+
* The selected date.
|
|
22
|
+
* @default undefined
|
|
23
|
+
*/
|
|
24
|
+
value?: Date;
|
|
25
|
+
/**
|
|
26
|
+
* The default value.
|
|
27
|
+
* @default undefined
|
|
28
|
+
*/
|
|
29
|
+
defaultValue?: Date;
|
|
30
|
+
/**
|
|
31
|
+
* The change event handler.
|
|
32
|
+
* @default undefined
|
|
33
|
+
*/
|
|
34
|
+
onChange?: (value: Date) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface Selection {
|
|
38
|
+
start: number;
|
|
39
|
+
end: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The component to choose a time.
|
|
44
|
+
*/
|
|
45
|
+
const TimePicker = forwardRef<HTMLInputElement, TimePickerProps>(
|
|
46
|
+
(
|
|
47
|
+
{
|
|
48
|
+
notation = '12-hour',
|
|
49
|
+
value,
|
|
50
|
+
defaultValue,
|
|
51
|
+
onChange = () => {},
|
|
52
|
+
onSelect = () => {},
|
|
53
|
+
onKeyDown = () => {},
|
|
54
|
+
disabled = false,
|
|
55
|
+
right,
|
|
56
|
+
...rest
|
|
57
|
+
},
|
|
58
|
+
ref
|
|
59
|
+
) => {
|
|
60
|
+
const [inputRef, mergedInputRef] = useForwardedRef(ref);
|
|
61
|
+
const [forwardedValue, setForwardedValue] = useForwardedState({
|
|
62
|
+
value,
|
|
63
|
+
defaultValue,
|
|
64
|
+
onChange,
|
|
65
|
+
});
|
|
66
|
+
const [selection, setSelection] = useState<Selection>({ start: 0, end: 0 });
|
|
67
|
+
|
|
68
|
+
const { time, isPm, changePeriod, keyHandler } = useTime({
|
|
69
|
+
notation,
|
|
70
|
+
selection,
|
|
71
|
+
setSelection,
|
|
72
|
+
forwardedValue,
|
|
73
|
+
setForwardedValue,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const rightComponent = useMemo(() => {
|
|
77
|
+
if (notation !== '12-hour' && !right) return null;
|
|
78
|
+
return (
|
|
79
|
+
<>
|
|
80
|
+
{notation === '12-hour' && (
|
|
81
|
+
<Button
|
|
82
|
+
type='ghost'
|
|
83
|
+
wide='never'
|
|
84
|
+
size='small'
|
|
85
|
+
disabled={disabled}
|
|
86
|
+
onClick={changePeriod}
|
|
87
|
+
>
|
|
88
|
+
{isPm ? 'PM' : 'AM'}
|
|
89
|
+
</Button>
|
|
90
|
+
)}
|
|
91
|
+
{right}
|
|
92
|
+
</>
|
|
93
|
+
);
|
|
94
|
+
}, [changePeriod, disabled, isPm, notation, right]);
|
|
95
|
+
|
|
96
|
+
const keyDownHandler = useCallback(
|
|
97
|
+
(e) => {
|
|
98
|
+
keyHandler(e.key, e.metaKey);
|
|
99
|
+
onKeyDown(e);
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
},
|
|
102
|
+
[keyHandler, onKeyDown]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Update the selection
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (!inputRef.current) return;
|
|
108
|
+
inputRef.current.setSelectionRange(selection.start, selection.end);
|
|
109
|
+
}, [inputRef, selection]);
|
|
110
|
+
|
|
111
|
+
const selectHandler = useCallback(
|
|
112
|
+
(e) => {
|
|
113
|
+
// Update the selection state.
|
|
114
|
+
const { selectionStart, selectionEnd } = e.currentTarget;
|
|
115
|
+
setSelection({ start: selectionStart || 0, end: selectionEnd || 0 });
|
|
116
|
+
onSelect(e);
|
|
117
|
+
},
|
|
118
|
+
[onSelect]
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<Input
|
|
123
|
+
type='text'
|
|
124
|
+
inputMode='decimal'
|
|
125
|
+
role='spinbutton'
|
|
126
|
+
minLength={5}
|
|
127
|
+
maxLength={5}
|
|
128
|
+
disabled={disabled}
|
|
129
|
+
value={time}
|
|
130
|
+
right={rightComponent}
|
|
131
|
+
onSelect={selectHandler}
|
|
132
|
+
onKeyDown={keyDownHandler}
|
|
133
|
+
{...rest}
|
|
134
|
+
ref={mergedInputRef}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
TimePicker.displayName = 'TimePicker';
|
|
141
|
+
|
|
142
|
+
export default TimePicker;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import styled from '@emotion/styled';
|
|
2
|
+
|
|
3
|
+
import React, { forwardRef } from 'react';
|
|
4
|
+
|
|
5
|
+
type JsxIFrameProps = Omit<JSX.IntrinsicElements['iframe'], 'ref'>;
|
|
6
|
+
export type VideoProps = JsxIFrameProps;
|
|
7
|
+
|
|
8
|
+
const Container = styled.div`
|
|
9
|
+
position: relative;
|
|
10
|
+
padding-bottom: 56.25%; // 16:9
|
|
11
|
+
height: 0;
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
const IFrame = styled.iframe`
|
|
15
|
+
position: absolute;
|
|
16
|
+
top: 0;
|
|
17
|
+
left: 0;
|
|
18
|
+
width: 100%;
|
|
19
|
+
height: 100%;
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The video player.
|
|
24
|
+
*/
|
|
25
|
+
const Video = forwardRef<HTMLIFrameElement, VideoProps>(
|
|
26
|
+
({ id, ...rest }, ref) => (
|
|
27
|
+
<Container>
|
|
28
|
+
<IFrame
|
|
29
|
+
frameBorder={0}
|
|
30
|
+
allow='clipboard-write; autoplay'
|
|
31
|
+
allowFullScreen
|
|
32
|
+
{...rest}
|
|
33
|
+
ref={ref}
|
|
34
|
+
/>
|
|
35
|
+
</Container>
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
Video.displayName = 'Video';
|
|
40
|
+
|
|
41
|
+
export default Video;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export { default as Alert } from './Alert';
|
|
2
|
+
export { default as Avatar } from './Avatar';
|
|
3
|
+
export { default as AvatarSkeleton } from './AvatarSkeleton';
|
|
4
|
+
export { default as Breadcrumb } from './Breadcrumb';
|
|
5
|
+
export { default as BreadcrumbItem } from './BreadcrumbItem';
|
|
6
|
+
export { default as Button } from './Button';
|
|
7
|
+
export { default as Checkbox } from './Checkbox';
|
|
8
|
+
export { default as CheckboxSkeleton } from './CheckboxSkeleton';
|
|
9
|
+
export { default as DatePicker } from './DatePicker';
|
|
10
|
+
export { default as Drawer } from './Drawer';
|
|
11
|
+
export { default as Form } from './Form';
|
|
12
|
+
export { default as FormDivider } from './FormDivider';
|
|
13
|
+
export { default as FormItem } from './FormItem';
|
|
14
|
+
export { default as Gallery } from './Gallery';
|
|
15
|
+
export { default as GlobalStyles } from './GlobalStyles';
|
|
16
|
+
export { default as HeaderSkeleton } from './HeaderSkeleton';
|
|
17
|
+
export { default as Image } from './Image';
|
|
18
|
+
export { default as ImageSkeleton } from './ImageSkeleton';
|
|
19
|
+
export { default as Input } from './Input';
|
|
20
|
+
export { default as InputNumber } from './InputNumber';
|
|
21
|
+
export { default as InputPassword } from './InputPassword';
|
|
22
|
+
export { default as InputSearch } from './InputSearch';
|
|
23
|
+
export { default as InputSkeleton } from './InputSkeleton';
|
|
24
|
+
export { default as Layout } from './Layout';
|
|
25
|
+
export { default as Link } from './Link';
|
|
26
|
+
export { default as LinkButton } from './LinkButton';
|
|
27
|
+
export { default as List } from './List';
|
|
28
|
+
export { default as ListItem } from './ListItem';
|
|
29
|
+
export { default as ListItemActions } from './ListItemActions';
|
|
30
|
+
export { default as ListItemLink } from './ListItemLink';
|
|
31
|
+
export { default as ListSkeleton } from './ListSkeleton';
|
|
32
|
+
export { default as LogoLink } from './LogoLink';
|
|
33
|
+
export { default as message } from './message';
|
|
34
|
+
export { default as Menu } from './Menu';
|
|
35
|
+
export { default as MenuDivider } from './MenuDivider';
|
|
36
|
+
export { default as MenuGroup } from './MenuGroup';
|
|
37
|
+
export { default as MenuItem } from './MenuItem';
|
|
38
|
+
export { default as Modal } from './Modal';
|
|
39
|
+
export { default as Navigation } from './Navigation';
|
|
40
|
+
export { default as NavigationItem } from './NavigationItem';
|
|
41
|
+
export { default as PageContent } from './PageContent';
|
|
42
|
+
export { default as PageHeader } from './PageHeader';
|
|
43
|
+
export { default as PageHeaderInputSearch } from './PageHeaderInputSearch';
|
|
44
|
+
export { default as PageHeaderSkeleton } from './PageHeaderSkeleton';
|
|
45
|
+
export { default as ParagraphSkeleton } from './ParagraphSkeleton';
|
|
46
|
+
export { default as Popover } from './Popover';
|
|
47
|
+
export { default as Progress } from './Progress';
|
|
48
|
+
export { default as RadioGroup } from './RadioGroup';
|
|
49
|
+
export { default as Result } from './Result';
|
|
50
|
+
export { default as ScrollButton } from './ScrollButton';
|
|
51
|
+
export { default as Select } from './Select';
|
|
52
|
+
export { default as Skeleton } from './Skeleton';
|
|
53
|
+
export { default as Switch } from './Switch';
|
|
54
|
+
export { default as SwitchSkeleton } from './SwitchSkeleton';
|
|
55
|
+
export { default as Tag } from './Tag';
|
|
56
|
+
export { default as TagLink } from './TagLink';
|
|
57
|
+
export { default as TagList } from './TagList';
|
|
58
|
+
export { default as TagListSkeleton } from './TagListSkeleton';
|
|
59
|
+
export { default as TagSkeleton } from './TagSkeleton';
|
|
60
|
+
export { default as TextArea } from './TextArea';
|
|
61
|
+
export { default as TextAreaSkeleton } from './TextAreaSkeleton';
|
|
62
|
+
export { default as ThemeSwitcher } from './ThemeSwitcher';
|
|
63
|
+
export { default as Video } from './Video';
|
|
64
|
+
|
|
65
|
+
export * from './Alert';
|
|
66
|
+
export * from './Avatar';
|
|
67
|
+
export * from './AvatarSkeleton';
|
|
68
|
+
export * from './Breadcrumb';
|
|
69
|
+
export * from './BreadcrumbItem';
|
|
70
|
+
export * from './Button';
|
|
71
|
+
export * from './Checkbox';
|
|
72
|
+
export * from './CheckboxSkeleton';
|
|
73
|
+
export * from './DatePicker';
|
|
74
|
+
export * from './Drawer';
|
|
75
|
+
export * from './Form';
|
|
76
|
+
export * from './FormDivider';
|
|
77
|
+
export * from './FormItem';
|
|
78
|
+
export * from './Gallery';
|
|
79
|
+
export * from './HeaderSkeleton';
|
|
80
|
+
export * from './Image';
|
|
81
|
+
export * from './ImageSkeleton';
|
|
82
|
+
export * from './Input';
|
|
83
|
+
export * from './InputNumber';
|
|
84
|
+
export * from './InputPassword';
|
|
85
|
+
export * from './InputSearch';
|
|
86
|
+
export * from './InputSkeleton';
|
|
87
|
+
export * from './Layout';
|
|
88
|
+
export * from './Link';
|
|
89
|
+
export * from './LinkButton';
|
|
90
|
+
export * from './List';
|
|
91
|
+
export * from './ListItem';
|
|
92
|
+
export * from './ListItemActions';
|
|
93
|
+
export * from './ListItemLink';
|
|
94
|
+
export * from './ListSkeleton';
|
|
95
|
+
export * from './LogoLink';
|
|
96
|
+
export * from './Menu';
|
|
97
|
+
export * from './MenuDivider';
|
|
98
|
+
export * from './MenuGroup';
|
|
99
|
+
export * from './MenuItem';
|
|
100
|
+
export * from './Modal';
|
|
101
|
+
export * from './Navigation';
|
|
102
|
+
export * from './NavigationItem';
|
|
103
|
+
export * from './PageContent';
|
|
104
|
+
export * from './PageHeader';
|
|
105
|
+
export * from './PageHeaderInputSearch';
|
|
106
|
+
export * from './PageHeaderSkeleton';
|
|
107
|
+
export * from './ParagraphSkeleton';
|
|
108
|
+
export * from './Popover';
|
|
109
|
+
export * from './Progress';
|
|
110
|
+
export * from './RadioGroup';
|
|
111
|
+
export * from './Result';
|
|
112
|
+
export * from './ScrollButton';
|
|
113
|
+
export * from './Select';
|
|
114
|
+
export * from './Skeleton';
|
|
115
|
+
export * from './Switch';
|
|
116
|
+
export * from './SwitchSkeleton';
|
|
117
|
+
export * from './Tag';
|
|
118
|
+
export * from './TagLink';
|
|
119
|
+
export * from './TagList';
|
|
120
|
+
export * from './TagListSkeleton';
|
|
121
|
+
export * from './TagSkeleton';
|
|
122
|
+
export * from './TextArea';
|
|
123
|
+
export * from './TextAreaSkeleton';
|
|
124
|
+
export * from './ThemeSwitcher';
|
|
125
|
+
export * from './Video';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import styled from '@emotion/styled';
|
|
2
|
+
import { CheckCircle, CloseCircle, InfoCircle } from '@os-design/icons';
|
|
3
|
+
import { Color, clr, light } from '@os-design/theming';
|
|
4
|
+
|
|
5
|
+
import { omitEmotionProps } from '@os-design/utils';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
interface AlertIconProps {
|
|
9
|
+
type: 'info' | 'success' | 'error';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ContainerProps {
|
|
13
|
+
iconColor: Color;
|
|
14
|
+
}
|
|
15
|
+
const Container = styled('i', omitEmotionProps('iconColor'))<ContainerProps>`
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: row;
|
|
18
|
+
align-items: center;
|
|
19
|
+
|
|
20
|
+
margin-right: 0.3em;
|
|
21
|
+
font-size: 1.4em;
|
|
22
|
+
color: ${(p) => clr(p.iconColor)};
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
const AlertIcon: React.FC<AlertIconProps> = ({ type }) => {
|
|
26
|
+
const { Icon, iconColor } = {
|
|
27
|
+
info: {
|
|
28
|
+
Icon: InfoCircle,
|
|
29
|
+
iconColor: light.alertInfoColorIcon,
|
|
30
|
+
},
|
|
31
|
+
success: {
|
|
32
|
+
Icon: CheckCircle,
|
|
33
|
+
iconColor: light.alertSuccessColorIcon,
|
|
34
|
+
},
|
|
35
|
+
error: {
|
|
36
|
+
Icon: CloseCircle,
|
|
37
|
+
iconColor: light.alertErrorColorIcon,
|
|
38
|
+
},
|
|
39
|
+
}[type];
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Container iconColor={iconColor}>
|
|
43
|
+
<Icon />
|
|
44
|
+
</Container>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
AlertIcon.displayName = 'AlertIcon';
|
|
49
|
+
|
|
50
|
+
export default AlertIcon;
|