@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.
Files changed (99) hide show
  1. package/package.json +21 -13
  2. package/src/@types/emotion.d.ts +7 -0
  3. package/src/Alert/index.tsx +112 -0
  4. package/src/Avatar/index.tsx +173 -0
  5. package/src/Avatar/utils/nameToInitials.ts +12 -0
  6. package/src/Avatar/utils/strToHue.ts +13 -0
  7. package/src/AvatarSkeleton/index.tsx +29 -0
  8. package/src/Breadcrumb/index.tsx +93 -0
  9. package/src/BreadcrumbItem/index.tsx +83 -0
  10. package/src/Button/ButtonContent.tsx +91 -0
  11. package/src/Button/index.tsx +225 -0
  12. package/src/Button/utils/useButtonColors.ts +84 -0
  13. package/src/Checkbox/index.tsx +225 -0
  14. package/src/CheckboxSkeleton/index.tsx +50 -0
  15. package/src/DatePicker/DatePickerCalendar.tsx +220 -0
  16. package/src/DatePicker/index.tsx +568 -0
  17. package/src/Drawer/index.tsx +212 -0
  18. package/src/Form/FormConfigContext.ts +16 -0
  19. package/src/Form/index.tsx +49 -0
  20. package/src/FormDivider/index.tsx +74 -0
  21. package/src/FormItem/index.tsx +118 -0
  22. package/src/Gallery/Status.tsx +62 -0
  23. package/src/Gallery/index.tsx +290 -0
  24. package/src/GlobalStyles/index.tsx +17 -0
  25. package/src/GlobalStyles/resetStyles.ts +17 -0
  26. package/src/GlobalStyles/typographyStyles.ts +78 -0
  27. package/src/HeaderSkeleton/index.tsx +64 -0
  28. package/src/Image/index.tsx +104 -0
  29. package/src/ImageSkeleton/index.tsx +22 -0
  30. package/src/Input/index.tsx +330 -0
  31. package/src/Input/utils/getFocusableElements.ts +8 -0
  32. package/src/InputNumber/index.tsx +208 -0
  33. package/src/InputNumber/utils/defaultLocale.ts +9 -0
  34. package/src/InputPassword/index.tsx +201 -0
  35. package/src/InputPassword/utils/defaultLocale.ts +11 -0
  36. package/src/InputSearch/index.tsx +111 -0
  37. package/src/InputSearch/utils/defaultLocale.ts +9 -0
  38. package/src/InputSkeleton/index.tsx +28 -0
  39. package/src/Layout/LayoutContext.ts +21 -0
  40. package/src/Layout/index.tsx +44 -0
  41. package/src/Link/index.tsx +129 -0
  42. package/src/LinkButton/index.tsx +100 -0
  43. package/src/List/WindowScroller.tsx +53 -0
  44. package/src/List/index.tsx +255 -0
  45. package/src/List/utils/bodyPointerEvents.ts +24 -0
  46. package/src/List/utils/frameTimeout.ts +36 -0
  47. package/src/List/utils/useRWLoadNext.ts +38 -0
  48. package/src/ListItem/index.tsx +92 -0
  49. package/src/ListItemActions/index.tsx +207 -0
  50. package/src/ListItemLink/index.tsx +63 -0
  51. package/src/ListSkeleton/index.tsx +115 -0
  52. package/src/LogoLink/index.tsx +93 -0
  53. package/src/LogoLink/logo.example.svg +18 -0
  54. package/src/Menu/index.tsx +128 -0
  55. package/src/Menu/utils/useFocusWithArrows.ts +50 -0
  56. package/src/MenuDivider/index.tsx +22 -0
  57. package/src/MenuGroup/index.tsx +190 -0
  58. package/src/MenuItem/index.tsx +108 -0
  59. package/src/Modal/index.tsx +411 -0
  60. package/src/Modal/utils/defaultLocale.ts +9 -0
  61. package/src/Navigation/index.tsx +214 -0
  62. package/src/Navigation/utils/useScrollFlags.ts +39 -0
  63. package/src/NavigationItem/index.tsx +136 -0
  64. package/src/PageContent/index.tsx +99 -0
  65. package/src/PageHeader/index.tsx +246 -0
  66. package/src/PageHeader/utils/defaultLocale.ts +9 -0
  67. package/src/PageHeaderInputSearch/index.tsx +145 -0
  68. package/src/PageHeaderInputSearch/utils/defaultLocale.ts +16 -0
  69. package/src/PageHeaderSkeleton/index.tsx +33 -0
  70. package/src/ParagraphSkeleton/index.tsx +65 -0
  71. package/src/Popover/index.tsx +243 -0
  72. package/src/Popover/utils/usePopoverPosition.ts +216 -0
  73. package/src/Progress/index.tsx +100 -0
  74. package/src/RadioGroup/index.tsx +165 -0
  75. package/src/RadioGroupSkeleton/index.tsx +36 -0
  76. package/src/Result/index.tsx +109 -0
  77. package/src/ScrollButton/index.tsx +159 -0
  78. package/src/ScrollButton/utils/useContainerPosition.ts +41 -0
  79. package/src/ScrollButton/utils/useVisibility.ts +56 -0
  80. package/src/Select/index.tsx +970 -0
  81. package/src/Select/utils/defaultLocale.ts +11 -0
  82. package/src/Skeleton/index.tsx +52 -0
  83. package/src/Switch/index.tsx +217 -0
  84. package/src/SwitchSkeleton/index.tsx +30 -0
  85. package/src/Tag/index.tsx +75 -0
  86. package/src/TagLink/index.tsx +53 -0
  87. package/src/TagList/index.tsx +95 -0
  88. package/src/TagListSkeleton/index.tsx +38 -0
  89. package/src/TagSkeleton/index.tsx +40 -0
  90. package/src/TextArea/index.tsx +231 -0
  91. package/src/TextAreaSkeleton/index.tsx +20 -0
  92. package/src/ThemeSwitcher/index.tsx +39 -0
  93. package/src/TimePicker/index.tsx +142 -0
  94. package/src/Video/index.tsx +41 -0
  95. package/src/index.ts +125 -0
  96. package/src/message/AlertIcon.tsx +50 -0
  97. package/src/message/Message.tsx +108 -0
  98. package/src/message/index.tsx +64 -0
  99. package/src/message/styles.ts +25 -0
@@ -0,0 +1,11 @@
1
+ export interface SelectLocale {
2
+ deleteLabel: string;
3
+ clearLabel: string;
4
+ }
5
+
6
+ const defaultLocale: SelectLocale = {
7
+ deleteLabel: 'Delete',
8
+ clearLabel: 'Clear',
9
+ };
10
+
11
+ export default defaultLocale;
@@ -0,0 +1,52 @@
1
+ import { keyframes } from '@emotion/react';
2
+ import styled from '@emotion/styled';
3
+ import { clr } from '@os-design/theming';
4
+ import { omitEmotionProps } from '@os-design/utils';
5
+ import React, { forwardRef } from 'react';
6
+
7
+ type JsxDivProps = Omit<JSX.IntrinsicElements['div'], 'ref'>;
8
+ export interface SkeletonProps extends JsxDivProps {
9
+ /**
10
+ * The width of the skeleton.
11
+ * @default 100%
12
+ */
13
+ width?: string;
14
+ }
15
+
16
+ const SkeletonAnimation = keyframes`
17
+ from { background-position: 100% 50%; }
18
+ to { background-position: 0 50%; }
19
+ `;
20
+
21
+ type StyledSkeletonProps = Required<Pick<SkeletonProps, 'width'>>;
22
+ const StyledSkeleton = styled(
23
+ 'div',
24
+ omitEmotionProps('width', 'size')
25
+ )<StyledSkeletonProps>`
26
+ width: ${(p) => p.width};
27
+ height: 1em;
28
+
29
+ background: linear-gradient(
30
+ 90deg,
31
+ ${(p) => clr(p.theme.skeletonColorBgFrom)} 25%,
32
+ ${(p) => clr(p.theme.skeletonColorBgTo)} 37%,
33
+ ${(p) => clr(p.theme.skeletonColorBgFrom)} 63%
34
+ );
35
+ background-size: 400% 100%;
36
+
37
+ border-radius: ${(p) => p.theme.borderRadius}em;
38
+ animation: ${SkeletonAnimation} 1.4s ease infinite;
39
+ `;
40
+
41
+ /**
42
+ * Provides a basic placeholder while a user waits for the content to load.
43
+ */
44
+ const Skeleton = forwardRef<HTMLDivElement, SkeletonProps>(
45
+ ({ width = '100%', ...rest }, ref) => (
46
+ <StyledSkeleton width={width} aria-busy {...rest} ref={ref} />
47
+ )
48
+ );
49
+
50
+ Skeleton.displayName = 'Skeleton';
51
+
52
+ export default Skeleton;
@@ -0,0 +1,217 @@
1
+ import { css } from '@emotion/react';
2
+ import styled from '@emotion/styled';
3
+ import {
4
+ resetButtonStyles,
5
+ sizeStyles,
6
+ transitionStyles,
7
+ WithSize,
8
+ } from '@os-design/styles';
9
+ import { clr } from '@os-design/theming';
10
+ import { omitEmotionProps, useForwardedState } from '@os-design/utils';
11
+ import React, { forwardRef, useCallback } from 'react';
12
+
13
+ type JsxButtonProps = Omit<
14
+ JSX.IntrinsicElements['button'],
15
+ 'value' | 'defaultValue' | 'onChange' | 'onClick' | 'ref'
16
+ >;
17
+ export interface SwitchProps extends JsxButtonProps, WithSize {
18
+ /**
19
+ * Whether the switch is disabled.
20
+ * @default false
21
+ */
22
+ disabled?: boolean;
23
+ /**
24
+ * Whether the switch is checked.
25
+ * @default false
26
+ */
27
+ value?: boolean;
28
+ /**
29
+ * The default value.
30
+ * @default undefined
31
+ */
32
+ defaultValue?: boolean;
33
+ /**
34
+ * The change event handler.
35
+ * @default undefined
36
+ */
37
+ onChange?: (value: boolean) => void;
38
+ }
39
+
40
+ const FRACTION_DIGITS = 4;
41
+
42
+ const uncheckedStyles = (p) =>
43
+ !p.checked &&
44
+ css`
45
+ background-color: ${clr(p.theme.switchUncheckedColorBg)};
46
+ &::after {
47
+ left: ${+((p.theme.switchHeight - p.theme.switchCircleSize) / 2).toFixed(
48
+ FRACTION_DIGITS
49
+ )}em;
50
+ }
51
+ `;
52
+
53
+ const checkedStyles = (p) =>
54
+ p.checked &&
55
+ css`
56
+ background-color: ${clr(p.theme.switchCheckedColorBg)};
57
+ &::after {
58
+ left: ${+(
59
+ p.theme.switchWidth -
60
+ p.theme.switchCircleSize -
61
+ (p.theme.switchHeight - p.theme.switchCircleSize) / 2
62
+ ).toFixed(FRACTION_DIGITS)}em;
63
+ }
64
+ `;
65
+
66
+ const hoverUncheckedStyles = (p) =>
67
+ !p.checked &&
68
+ css`
69
+ background-color: ${clr(p.theme.switchUncheckedColorBgHover)};
70
+ `;
71
+
72
+ const hoverCheckedStyles = (p) =>
73
+ p.checked &&
74
+ css`
75
+ background-color: ${clr(p.theme.switchCheckedColorBgHover)};
76
+ `;
77
+
78
+ const hoverStyles = (p) =>
79
+ !p.disabled &&
80
+ css`
81
+ @media (hover: hover) {
82
+ &:hover,
83
+ &:focus {
84
+ ${hoverUncheckedStyles(p)};
85
+ ${hoverCheckedStyles(p)};
86
+ }
87
+ }
88
+ `;
89
+
90
+ const disabledUncheckedStyles = (p) =>
91
+ !p.checked &&
92
+ css`
93
+ background-color: ${clr(p.theme.switchDisabledUncheckedColorBg)};
94
+ `;
95
+
96
+ const disabledCheckedStyles = (p) =>
97
+ p.checked &&
98
+ css`
99
+ background-color: ${clr(p.theme.switchDisabledCheckedColorBg)};
100
+ `;
101
+
102
+ const disabledStyles = (p) =>
103
+ p.disabled &&
104
+ css`
105
+ cursor: not-allowed;
106
+ ${disabledUncheckedStyles(p)};
107
+ ${disabledCheckedStyles(p)};
108
+ `;
109
+
110
+ interface StyledSwitchProps extends Pick<SwitchProps, 'disabled' | 'size'> {
111
+ checked: SwitchProps['value'];
112
+ }
113
+ const StyledSwitch = styled(
114
+ 'button',
115
+ omitEmotionProps('size', 'checked')
116
+ )<StyledSwitchProps>`
117
+ ${resetButtonStyles};
118
+ position: relative;
119
+ cursor: pointer;
120
+ user-select: none;
121
+ display: block;
122
+
123
+ width: ${(p) => p.theme.switchWidth}em;
124
+ height: ${(p) => p.theme.switchHeight}em;
125
+ border-radius: ${(p) => p.theme.switchHeight / 2}em;
126
+
127
+ &::after {
128
+ position: absolute;
129
+ top: ${(p) =>
130
+ +((p.theme.switchHeight - p.theme.switchCircleSize) / 2).toFixed(
131
+ FRACTION_DIGITS
132
+ )}em;
133
+
134
+ width: ${(p) => p.theme.switchCircleSize}em;
135
+ height: ${(p) => p.theme.switchCircleSize}em;
136
+ border-radius: 50%;
137
+
138
+ background-color: ${(p) => clr(p.theme.switchColorCircleBg)};
139
+ content: ' ';
140
+
141
+ ${transitionStyles('left')};
142
+ }
143
+
144
+ ${uncheckedStyles};
145
+ ${checkedStyles};
146
+ ${hoverStyles};
147
+ ${disabledStyles};
148
+
149
+ ${sizeStyles};
150
+ ${transitionStyles('background-color')};
151
+ `;
152
+
153
+ /**
154
+ * The switch that can be enabled or disabled.
155
+ */
156
+ const Switch = forwardRef<HTMLButtonElement, SwitchProps>(
157
+ (
158
+ {
159
+ disabled = false,
160
+ value,
161
+ defaultValue,
162
+ onChange = () => {},
163
+ size,
164
+ onMouseDown = () => {},
165
+ onKeyDown = () => {},
166
+ ...rest
167
+ },
168
+ ref
169
+ ) => {
170
+ const [forwardedValue, setForwardedValue] = useForwardedState({
171
+ value,
172
+ defaultValue,
173
+ onChange,
174
+ });
175
+
176
+ const clickHandler = useCallback(() => {
177
+ if (disabled) return;
178
+ setForwardedValue(!forwardedValue);
179
+ }, [disabled, forwardedValue, setForwardedValue]);
180
+
181
+ const mouseDownHandler = useCallback(
182
+ (e) => {
183
+ onMouseDown(e);
184
+ e.preventDefault();
185
+ },
186
+ [onMouseDown]
187
+ );
188
+
189
+ const keyDownHandler = useCallback(
190
+ (e) => {
191
+ if (e.key === ' ') setForwardedValue(!forwardedValue);
192
+ onKeyDown(e);
193
+ e.preventDefault();
194
+ },
195
+ [forwardedValue, onKeyDown, setForwardedValue]
196
+ );
197
+
198
+ return (
199
+ <StyledSwitch
200
+ disabled={disabled}
201
+ checked={forwardedValue}
202
+ size={size}
203
+ onClick={clickHandler}
204
+ onMouseDown={mouseDownHandler}
205
+ onKeyDown={keyDownHandler}
206
+ role='switch'
207
+ aria-checked={forwardedValue}
208
+ {...rest}
209
+ ref={ref}
210
+ />
211
+ );
212
+ }
213
+ );
214
+
215
+ Switch.displayName = 'Switch';
216
+
217
+ export default Switch;
@@ -0,0 +1,30 @@
1
+ import styled from '@emotion/styled';
2
+
3
+ import { sizeStyles, WithSize } from '@os-design/styles';
4
+ import { omitEmotionProps } from '@os-design/utils';
5
+ import React, { forwardRef } from 'react';
6
+
7
+ import Skeleton, { SkeletonProps } from '../Skeleton';
8
+
9
+ export type SwitchSkeletonProps = Omit<SkeletonProps, 'width'> & WithSize;
10
+
11
+ const StyledSwitchSkeleton = styled(
12
+ Skeleton,
13
+ omitEmotionProps('size')
14
+ )<WithSize>`
15
+ width: ${(p) => p.theme.switchWidth}em;
16
+ height: ${(p) => p.theme.switchHeight}em;
17
+ border-radius: ${(p) => p.theme.switchHeight / 2}em;
18
+ ${sizeStyles};
19
+ `;
20
+
21
+ /**
22
+ * Provides a switch placeholder while a user waits for the content to load.
23
+ */
24
+ const SwitchSkeleton = forwardRef<HTMLDivElement, SwitchSkeletonProps>(
25
+ (props, ref) => <StyledSwitchSkeleton width='100%' {...props} ref={ref} />
26
+ );
27
+
28
+ SwitchSkeleton.displayName = 'SwitchSkeleton';
29
+
30
+ export default SwitchSkeleton;
@@ -0,0 +1,75 @@
1
+ import styled from '@emotion/styled';
2
+ import {
3
+ ellipsisStyles,
4
+ sizeStyles,
5
+ transitionStyles,
6
+ WithSize,
7
+ } from '@os-design/styles';
8
+ import { clr } from '@os-design/theming';
9
+
10
+ import { omitEmotionProps } from '@os-design/utils';
11
+ import React, { forwardRef } from 'react';
12
+
13
+ type JsxDivProps = Omit<JSX.IntrinsicElements['div'], 'ref'>;
14
+
15
+ export interface BaseTagProps extends WithSize {
16
+ /**
17
+ * The component located on the left side.
18
+ * @default undefined
19
+ */
20
+ left?: React.ReactNode;
21
+ /**
22
+ * The component located on the right side.
23
+ * @default undefined
24
+ */
25
+ right?: React.ReactNode;
26
+ }
27
+
28
+ export type TagProps = JsxDivProps & BaseTagProps;
29
+
30
+ export const TagContainer = styled('div', omitEmotionProps('size'))<WithSize>`
31
+ display: inline-flex;
32
+ align-items: center;
33
+ overflow: hidden;
34
+
35
+ background-color: ${(p) => clr(p.theme.tagColorBg)};
36
+ color: ${(p) => clr(p.theme.tagColorText)};
37
+
38
+ border-radius: ${(p) => p.theme.borderRadius}em;
39
+ padding: ${(p) => p.theme.tagPaddingVertical}em
40
+ ${(p) => p.theme.tagPaddingHorizontal}em;
41
+
42
+ ${sizeStyles};
43
+ ${transitionStyles('background-color')};
44
+ `;
45
+
46
+ const Content = styled.div`
47
+ ${ellipsisStyles};
48
+ `;
49
+
50
+ export const LeftAddon = styled.div`
51
+ display: inherit;
52
+ padding-right: ${(p) => p.theme.tagAddonPaddingHorizontal}em;
53
+ `;
54
+
55
+ export const RightAddon = styled.div`
56
+ display: inherit;
57
+ padding-left: ${(p) => p.theme.tagAddonPaddingHorizontal}em;
58
+ `;
59
+
60
+ /**
61
+ * The component to display a label, tag, or category.
62
+ */
63
+ const Tag = forwardRef<HTMLDivElement, TagProps>(
64
+ ({ left, right, children, ...rest }, ref) => (
65
+ <TagContainer {...rest} ref={ref}>
66
+ {left && <LeftAddon>{left}</LeftAddon>}
67
+ <Content>{children}</Content>
68
+ {right && <RightAddon>{right}</RightAddon>}
69
+ </TagContainer>
70
+ )
71
+ );
72
+
73
+ Tag.displayName = 'Tag';
74
+
75
+ export default Tag;
@@ -0,0 +1,53 @@
1
+ import styled from '@emotion/styled';
2
+ import { clr } from '@os-design/theming';
3
+ import { omitEmotionProps } from '@os-design/utils';
4
+ import React, { forwardRef } from 'react';
5
+
6
+ import { LinkProps, ReactRouterLinkProps } from '../Link';
7
+ import { BaseTagProps, LeftAddon, RightAddon, TagContainer } from '../Tag';
8
+
9
+ type JsxAProps = Omit<JSX.IntrinsicElements['a'], 'ref'>;
10
+ export type TagLinkProps = JsxAProps &
11
+ ReactRouterLinkProps &
12
+ Pick<LinkProps, 'as'> &
13
+ BaseTagProps;
14
+
15
+ const StyledTagLink = styled(
16
+ TagContainer.withComponent('a'),
17
+ omitEmotionProps('as')
18
+ )`
19
+ cursor: pointer;
20
+ text-decoration: none;
21
+
22
+ @media (hover: hover) {
23
+ &:hover,
24
+ &:focus {
25
+ background-color: ${(p) => clr(p.theme.tagColorBgHover)};
26
+ }
27
+ }
28
+ `;
29
+
30
+ /**
31
+ * The tag component with a link.
32
+ */
33
+ const TagLink = forwardRef<HTMLAnchorElement, TagLinkProps>(
34
+ ({ left, right, as, onMouseDown = () => {}, children, ...rest }, ref) => (
35
+ <StyledTagLink
36
+ as={as}
37
+ onMouseDown={(e) => {
38
+ onMouseDown(e);
39
+ e.preventDefault();
40
+ }}
41
+ {...rest}
42
+ ref={ref}
43
+ >
44
+ {left && <LeftAddon>{left}</LeftAddon>}
45
+ {children}
46
+ {right && <RightAddon>{right}</RightAddon>}
47
+ </StyledTagLink>
48
+ )
49
+ );
50
+
51
+ TagLink.displayName = 'TagLink';
52
+
53
+ export default TagLink;
@@ -0,0 +1,95 @@
1
+ import { css } from '@emotion/react';
2
+ import styled from '@emotion/styled';
3
+ import { Down, Up } from '@os-design/icons';
4
+ import { WithSize, sizeStyles } from '@os-design/styles';
5
+ import { omitEmotionProps } from '@os-design/utils';
6
+ import React, { forwardRef, useState } from 'react';
7
+ import Button from '../Button';
8
+
9
+ type JsxDivProps = Omit<JSX.IntrinsicElements['div'], 'ref'>;
10
+ export interface TagListProps extends JsxDivProps, WithSize {
11
+ /**
12
+ * Whether the tag list is collapsible.
13
+ * @default false
14
+ */
15
+ collapsible?: boolean;
16
+ }
17
+
18
+ const collapsibleStyles = (p) =>
19
+ p.collapsible &&
20
+ !p.opened &&
21
+ css`
22
+ // The height of the tag
23
+ height: ${p.theme.lineHeight + p.theme.tagPaddingVertical * 2}em;
24
+ `;
25
+
26
+ interface ContainerProps extends Pick<TagListProps, 'collapsible' | 'size'> {
27
+ opened: boolean;
28
+ }
29
+ const Container = styled(
30
+ 'div',
31
+ omitEmotionProps('opened', 'collapsible', 'size')
32
+ )<ContainerProps>`
33
+ display: flex;
34
+ overflow: hidden;
35
+ ${collapsibleStyles};
36
+ ${sizeStyles};
37
+ `;
38
+
39
+ const Content = styled.div`
40
+ display: flex;
41
+ flex-wrap: wrap;
42
+ flex-grow: 1;
43
+
44
+ margin: ${(p) => -p.theme.tagListGap}em 0 0 ${(p) => -p.theme.tagListGap}em;
45
+ & > * {
46
+ margin: ${(p) => p.theme.tagListGap}em 0 0 ${(p) => p.theme.tagListGap}em;
47
+ }
48
+ `;
49
+
50
+ const Control = styled.div`
51
+ margin-left: 0.2em;
52
+ `;
53
+
54
+ const OpenButton = styled(Button)`
55
+ // The height of the tag
56
+ height: ${(p) => p.theme.lineHeight + p.theme.tagPaddingVertical * 2}em;
57
+ padding: 0 0.5em;
58
+ `;
59
+
60
+ /**
61
+ * The component to display a list of tags.
62
+ */
63
+ const TagList = forwardRef<HTMLDivElement, TagListProps>(
64
+ ({ collapsible = false, size, children, ...rest }, ref) => {
65
+ const [opened, setOpened] = useState(false);
66
+
67
+ return (
68
+ <Container
69
+ opened={opened}
70
+ collapsible={collapsible}
71
+ size={size}
72
+ {...rest}
73
+ ref={ref}
74
+ >
75
+ <Content>{children}</Content>
76
+
77
+ {collapsible && (
78
+ <Control>
79
+ <OpenButton
80
+ type='ghost'
81
+ wide='never'
82
+ onClick={() => setOpened(!opened)}
83
+ >
84
+ {opened ? <Up /> : <Down />}
85
+ </OpenButton>
86
+ </Control>
87
+ )}
88
+ </Container>
89
+ );
90
+ }
91
+ );
92
+
93
+ TagList.displayName = 'TagList';
94
+
95
+ export default TagList;
@@ -0,0 +1,38 @@
1
+ import React, { forwardRef } from 'react';
2
+ import TagList, { TagListProps } from '../TagList';
3
+ import TagSkeleton from '../TagSkeleton';
4
+
5
+ export interface TagListSkeletonProps extends TagListProps {
6
+ /**
7
+ * The width of the tag skeleton.
8
+ * @default undefined
9
+ */
10
+ width?: string;
11
+ /**
12
+ * The number of tag skeletons.
13
+ * @default 3
14
+ */
15
+ tagsCount?: number;
16
+ }
17
+
18
+ let tagIndex = 0;
19
+
20
+ /**
21
+ * Provides a tag list placeholder while a user waits for the content to load.
22
+ */
23
+ const TagListSkeleton = forwardRef<HTMLDivElement, TagListSkeletonProps>(
24
+ ({ width, tagsCount = 3, ...rest }, ref) => (
25
+ <TagList {...rest} ref={ref}>
26
+ {Array(tagsCount)
27
+ .fill({})
28
+ .map(() => {
29
+ tagIndex += 1;
30
+ return <TagSkeleton key={tagIndex} width={width} />;
31
+ })}
32
+ </TagList>
33
+ )
34
+ );
35
+
36
+ TagListSkeleton.displayName = 'TagListSkeleton';
37
+
38
+ export default TagListSkeleton;
@@ -0,0 +1,40 @@
1
+ import styled from '@emotion/styled';
2
+
3
+ import { sizeStyles, WithSize } from '@os-design/styles';
4
+ import { omitEmotionProps } from '@os-design/utils';
5
+ import React, { forwardRef } from 'react';
6
+
7
+ import Skeleton, { SkeletonProps } from '../Skeleton';
8
+
9
+ export interface TagSkeletonProps
10
+ extends Omit<SkeletonProps, 'width'>,
11
+ WithSize {
12
+ /**
13
+ * The width of the skeleton.
14
+ * @default 6em
15
+ */
16
+ width?: string;
17
+ }
18
+
19
+ type StyledTagSkeletonProps = Pick<TagSkeletonProps, 'width' | 'size'>;
20
+ const StyledTagSkeleton = styled(
21
+ Skeleton,
22
+ omitEmotionProps('width', 'size')
23
+ )<StyledTagSkeletonProps>`
24
+ width: ${(p) => p.width};
25
+ height: ${(p) => p.theme.lineHeight + p.theme.tagPaddingVertical * 2}em;
26
+ ${sizeStyles};
27
+ `;
28
+
29
+ /**
30
+ * Provides a tag placeholder while a user waits for the content to load.
31
+ */
32
+ const TagSkeleton = forwardRef<HTMLDivElement, TagSkeletonProps>(
33
+ ({ width = '6em', ...rest }, ref) => (
34
+ <StyledTagSkeleton width={width} {...rest} ref={ref} />
35
+ )
36
+ );
37
+
38
+ TagSkeleton.displayName = 'TagSkeleton';
39
+
40
+ export default TagSkeleton;