@soul-refiner/design-system 0.1.12 → 0.1.14
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 +1 -1
- package/src/components/actions/button/button.tsx +14 -2
- package/src/components/actions/dropdown/dropdown.tsx +1 -1
- package/src/components/iconography/icon/icon.tsx +6 -1
- package/src/components/input/index.ts +2 -0
- package/src/components/input/search/index.ts +2 -0
- package/src/components/input/search/search.tsx +223 -0
- package/src/components/surfaces/comment/comment.tsx +243 -0
- package/src/components/surfaces/comment/helpers.ts +35 -0
- package/src/components/surfaces/comment/index.ts +2 -0
- package/src/components/surfaces/index.ts +2 -0
- package/src/emoji/emojis.json +17577 -0
- package/tsconfig.json +1 -0
package/package.json
CHANGED
|
@@ -173,8 +173,6 @@ const base = css<{
|
|
|
173
173
|
color: var(--color-grey-7) !important;
|
|
174
174
|
cursor: unset !important;
|
|
175
175
|
}
|
|
176
|
-
|
|
177
|
-
${({ $sx }) => ($sx ? $sx : '')}
|
|
178
176
|
`;
|
|
179
177
|
|
|
180
178
|
const Primary = styled(Box)<{
|
|
@@ -199,6 +197,8 @@ const Primary = styled(Box)<{
|
|
|
199
197
|
${base}
|
|
200
198
|
${Styles.flex}
|
|
201
199
|
${Styles.padding}
|
|
200
|
+
|
|
201
|
+
${({ $sx }) => ($sx ? $sx : '')}
|
|
202
202
|
`;
|
|
203
203
|
|
|
204
204
|
const Secondary = styled(Box)<{
|
|
@@ -222,6 +222,8 @@ const Secondary = styled(Box)<{
|
|
|
222
222
|
${base}
|
|
223
223
|
${Styles.flex}
|
|
224
224
|
${Styles.padding}
|
|
225
|
+
|
|
226
|
+
${({ $sx }) => ($sx ? $sx : '')}
|
|
225
227
|
`;
|
|
226
228
|
|
|
227
229
|
const Outlined = styled(Box)<{
|
|
@@ -240,6 +242,8 @@ const Outlined = styled(Box)<{
|
|
|
240
242
|
${base}
|
|
241
243
|
${Styles.flex}
|
|
242
244
|
${Styles.padding}
|
|
245
|
+
|
|
246
|
+
${({ $sx }) => ($sx ? $sx : '')}
|
|
243
247
|
`;
|
|
244
248
|
|
|
245
249
|
const Danger = styled(Box)<{
|
|
@@ -264,6 +268,8 @@ const Danger = styled(Box)<{
|
|
|
264
268
|
${base}
|
|
265
269
|
${Styles.flex}
|
|
266
270
|
${Styles.padding}
|
|
271
|
+
|
|
272
|
+
${({ $sx }) => ($sx ? $sx : '')}
|
|
267
273
|
`;
|
|
268
274
|
|
|
269
275
|
const Text = styled(Box)<{
|
|
@@ -291,6 +297,8 @@ const Text = styled(Box)<{
|
|
|
291
297
|
${base}
|
|
292
298
|
${Styles.flex}
|
|
293
299
|
${Styles.padding}
|
|
300
|
+
|
|
301
|
+
${({ $sx }) => ($sx ? $sx : '')}
|
|
294
302
|
`;
|
|
295
303
|
|
|
296
304
|
const Raised = styled(Box)<{
|
|
@@ -320,6 +328,8 @@ const Raised = styled(Box)<{
|
|
|
320
328
|
${base}
|
|
321
329
|
${Styles.flex}
|
|
322
330
|
${Styles.padding}
|
|
331
|
+
|
|
332
|
+
${({ $sx }) => ($sx ? $sx : '')}
|
|
323
333
|
`;
|
|
324
334
|
|
|
325
335
|
const Special = styled(Box)<{
|
|
@@ -347,4 +357,6 @@ const Special = styled(Box)<{
|
|
|
347
357
|
${base}
|
|
348
358
|
${Styles.flex}
|
|
349
359
|
${Styles.padding}
|
|
360
|
+
|
|
361
|
+
${({ $sx }) => ($sx ? $sx : '')}
|
|
350
362
|
`;
|
|
@@ -119,7 +119,7 @@ const Dropdown = ({
|
|
|
119
119
|
const handleChange = multiple ? handleChangeMultiple : handleChangeSingle;
|
|
120
120
|
|
|
121
121
|
const isSelected = (value: string) =>
|
|
122
|
-
maintainState && multiple ? selected.includes(value) : value === selected;
|
|
122
|
+
maintainState && (multiple ? selected.includes(value) : value === selected);
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
125
|
* allow for the API to accept children as options
|
|
@@ -30,6 +30,7 @@ export interface IconProps extends NativeProps {
|
|
|
30
30
|
as?: React.ElementType;
|
|
31
31
|
color?: (typeof COLORS)[number];
|
|
32
32
|
size?: 16 | 20 | 24;
|
|
33
|
+
sx?: string;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export const Icon = ({
|
|
@@ -37,6 +38,7 @@ export const Icon = ({
|
|
|
37
38
|
children,
|
|
38
39
|
color = 'grey-3',
|
|
39
40
|
size = 16,
|
|
41
|
+
sx,
|
|
40
42
|
...rest
|
|
41
43
|
}: IconProps) => {
|
|
42
44
|
return (
|
|
@@ -47,6 +49,7 @@ export const Icon = ({
|
|
|
47
49
|
viewBox="0 0 16 16"
|
|
48
50
|
fill={colorReference[color]}
|
|
49
51
|
xmlns="http://www.w3.org/2000/svg"
|
|
52
|
+
$sx={sx}
|
|
50
53
|
{...rest}
|
|
51
54
|
>
|
|
52
55
|
{children}
|
|
@@ -54,4 +57,6 @@ export const Icon = ({
|
|
|
54
57
|
);
|
|
55
58
|
};
|
|
56
59
|
|
|
57
|
-
const SVG = styled.svg
|
|
60
|
+
const SVG = styled.svg<{ $sx?: string }>`
|
|
61
|
+
${({ $sx }) => ($sx ? $sx : '')}
|
|
62
|
+
`;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import { Styles } from 'common';
|
|
4
|
+
import { BoxProps } from 'components';
|
|
5
|
+
import { Close, Search as SearchIcon } from 'icons';
|
|
6
|
+
|
|
7
|
+
type NativeProps = Pick<
|
|
8
|
+
React.HTMLAttributes<HTMLInputElement>,
|
|
9
|
+
'placeholder' | 'onChange' | 'onSubmit'
|
|
10
|
+
>;
|
|
11
|
+
|
|
12
|
+
type SearchSize = 'collapsed' | 'small' | 'medium' | 'large';
|
|
13
|
+
|
|
14
|
+
export interface SearchProps extends NativeProps {
|
|
15
|
+
dropdownFilter?: React.ReactElement;
|
|
16
|
+
onClearText?: () => void;
|
|
17
|
+
size: SearchSize;
|
|
18
|
+
sx?: string;
|
|
19
|
+
value: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const Search = ({
|
|
23
|
+
dropdownFilter,
|
|
24
|
+
onChange,
|
|
25
|
+
onClearText,
|
|
26
|
+
onSubmit,
|
|
27
|
+
placeholder,
|
|
28
|
+
size,
|
|
29
|
+
sx,
|
|
30
|
+
value,
|
|
31
|
+
}: SearchProps) => {
|
|
32
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
33
|
+
const [focused, setFocused] = React.useState(false);
|
|
34
|
+
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
35
|
+
|
|
36
|
+
const isCollapsed = !expanded && size === 'collapsed';
|
|
37
|
+
const iconSize = isCollapsed || size === 'large' ? 24 : 16;
|
|
38
|
+
|
|
39
|
+
const handleExpand = () => {
|
|
40
|
+
setExpanded(true);
|
|
41
|
+
inputRef?.current?.focus();
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleCollapse = () => {
|
|
45
|
+
setExpanded(false);
|
|
46
|
+
setFocused(false);
|
|
47
|
+
inputRef?.current?.blur();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleFocus = () => {
|
|
51
|
+
inputRef?.current?.focus();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Wrapper
|
|
56
|
+
$align="center"
|
|
57
|
+
$dir="row"
|
|
58
|
+
$isCollapsed={isCollapsed}
|
|
59
|
+
$isFocused={focused}
|
|
60
|
+
$borderRadius="large"
|
|
61
|
+
$size={size}
|
|
62
|
+
$sx={sx}
|
|
63
|
+
onClick={
|
|
64
|
+
dropdownFilter && size === 'large'
|
|
65
|
+
? undefined
|
|
66
|
+
: isCollapsed
|
|
67
|
+
? handleExpand
|
|
68
|
+
: handleFocus
|
|
69
|
+
}
|
|
70
|
+
>
|
|
71
|
+
<SearchIcon
|
|
72
|
+
color={size === 'large' ? 'blue-7' : 'grey-3'}
|
|
73
|
+
size={iconSize}
|
|
74
|
+
onClick={isCollapsed ? undefined : handleCollapse}
|
|
75
|
+
sx={
|
|
76
|
+
isCollapsed || size !== 'collapsed'
|
|
77
|
+
? undefined
|
|
78
|
+
: `
|
|
79
|
+
&:hover {
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
background-color: var(--color-grey-10);
|
|
82
|
+
border-radius: 50%;
|
|
83
|
+
}`
|
|
84
|
+
}
|
|
85
|
+
/>
|
|
86
|
+
<Input
|
|
87
|
+
$isCollapsed={isCollapsed}
|
|
88
|
+
onBlur={() => setFocused(false)}
|
|
89
|
+
onChange={onChange}
|
|
90
|
+
onFocus={() => setFocused(true)}
|
|
91
|
+
onSubmit={onSubmit}
|
|
92
|
+
placeholder={placeholder}
|
|
93
|
+
ref={inputRef}
|
|
94
|
+
value={value}
|
|
95
|
+
/>
|
|
96
|
+
{!isCollapsed && onClearText && (
|
|
97
|
+
<Close
|
|
98
|
+
size={16}
|
|
99
|
+
onClick={onClearText}
|
|
100
|
+
sx={`
|
|
101
|
+
${value.length === 0 ? 'visibility: hidden;' : ''}
|
|
102
|
+
padding: 4px;
|
|
103
|
+
&:hover {
|
|
104
|
+
cursor: pointer;
|
|
105
|
+
background-color: var(--color-grey-10);
|
|
106
|
+
border-radius: 50%;
|
|
107
|
+
}`}
|
|
108
|
+
/>
|
|
109
|
+
)}
|
|
110
|
+
{size === 'large' && dropdownFilter && (
|
|
111
|
+
<>
|
|
112
|
+
<Separator />
|
|
113
|
+
{dropdownFilter}
|
|
114
|
+
</>
|
|
115
|
+
)}
|
|
116
|
+
</Wrapper>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const Wrapper = styled.div<
|
|
121
|
+
BoxProps & {
|
|
122
|
+
$isCollapsed: boolean;
|
|
123
|
+
$isFocused: boolean;
|
|
124
|
+
$size: SearchSize;
|
|
125
|
+
$sx?: string;
|
|
126
|
+
}
|
|
127
|
+
>`
|
|
128
|
+
${Styles.flex}
|
|
129
|
+
${Styles.padding}
|
|
130
|
+
display: flex;
|
|
131
|
+
background-color: var(--color-white);
|
|
132
|
+
|
|
133
|
+
& > * {
|
|
134
|
+
width: fit-content;
|
|
135
|
+
flex-shrink: 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
${({ $isCollapsed, $size }) =>
|
|
139
|
+
$isCollapsed
|
|
140
|
+
? `
|
|
141
|
+
padding: 12px;
|
|
142
|
+
border-radius: 50%;
|
|
143
|
+
&:hover {
|
|
144
|
+
background-color: var(--color-grey-10);
|
|
145
|
+
cursor: pointer;
|
|
146
|
+
}`
|
|
147
|
+
: `
|
|
148
|
+
border-radius: 8px;
|
|
149
|
+
${
|
|
150
|
+
$size === 'collapsed'
|
|
151
|
+
? // collapsed
|
|
152
|
+
`
|
|
153
|
+
gap: 8px;
|
|
154
|
+
padding: 10px 16px;
|
|
155
|
+
border: 1px solid var(--color-grey-8);
|
|
156
|
+
|
|
157
|
+
&:hover {
|
|
158
|
+
border: 1px solid var(--color-grey-6);
|
|
159
|
+
}`
|
|
160
|
+
: $size === 'small'
|
|
161
|
+
? // small
|
|
162
|
+
`
|
|
163
|
+
gap: 12px;
|
|
164
|
+
padding: 10px 16px;
|
|
165
|
+
border: 1px solid var(--color-grey-8);
|
|
166
|
+
|
|
167
|
+
&:hover {
|
|
168
|
+
border: 1px solid var(--color-grey-6);
|
|
169
|
+
}`
|
|
170
|
+
: $size === 'medium'
|
|
171
|
+
? // medium
|
|
172
|
+
`
|
|
173
|
+
gap: 12px;
|
|
174
|
+
padding: 14px 16px;
|
|
175
|
+
border: 1px solid var(--color-grey-8);
|
|
176
|
+
|
|
177
|
+
&:hover {
|
|
178
|
+
border: 1px solid var(--color-grey-6);
|
|
179
|
+
}`
|
|
180
|
+
: // large
|
|
181
|
+
`
|
|
182
|
+
gap: 16px;
|
|
183
|
+
padding: 16px;
|
|
184
|
+
box-shadow: var(--elevation-1);
|
|
185
|
+
|
|
186
|
+
&:hover {
|
|
187
|
+
border: 1px solid var(--color-grey-6);
|
|
188
|
+
}`
|
|
189
|
+
}`}
|
|
190
|
+
|
|
191
|
+
${({ $isFocused, $isCollapsed }) =>
|
|
192
|
+
$isFocused && !$isCollapsed
|
|
193
|
+
? 'border: 1px solid var(--color-primary-blue-2) !important;'
|
|
194
|
+
: ''}
|
|
195
|
+
|
|
196
|
+
${({ $sx }) => ($sx ? $sx : '')}
|
|
197
|
+
`;
|
|
198
|
+
|
|
199
|
+
const Input = styled.input.attrs(({ ref }) => ({
|
|
200
|
+
ref: ref,
|
|
201
|
+
}))<{
|
|
202
|
+
$isCollapsed: boolean;
|
|
203
|
+
}>`
|
|
204
|
+
flex: 1;
|
|
205
|
+
border-radius: 8px;
|
|
206
|
+
outline: none;
|
|
207
|
+
border: none;
|
|
208
|
+
font-size: 14px;
|
|
209
|
+
font-family: Inter;
|
|
210
|
+
line-height: 20px;
|
|
211
|
+
color: var(--color-grey-2);
|
|
212
|
+
|
|
213
|
+
${({ $isCollapsed }) =>
|
|
214
|
+
$isCollapsed
|
|
215
|
+
? 'width: 0px; height: 0px; padding: 0px !important; border: none !important;'
|
|
216
|
+
: ''}
|
|
217
|
+
`;
|
|
218
|
+
|
|
219
|
+
const Separator = styled.div`
|
|
220
|
+
width: 1px;
|
|
221
|
+
height: 20px;
|
|
222
|
+
background-color: var(--color-grey-9);
|
|
223
|
+
`;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import Picker from '@emoji-mart/react';
|
|
4
|
+
import { Avatar, Box, BoxProps, P } from 'components';
|
|
5
|
+
import emojis from 'emoji/emojis.json';
|
|
6
|
+
import { ThumbsUp, Comment as CommentIcon } from 'icons';
|
|
7
|
+
import { abbrTimeBeforeDate } from './helpers';
|
|
8
|
+
import { Styles } from 'common';
|
|
9
|
+
|
|
10
|
+
export interface CommentProps {
|
|
11
|
+
content: string;
|
|
12
|
+
createdDate: Date;
|
|
13
|
+
onCommentClick: () => void;
|
|
14
|
+
onReact: (reaction: string) => void;
|
|
15
|
+
reactions?: Record<string, number>;
|
|
16
|
+
replies?: CommentProps[];
|
|
17
|
+
userImageUri: string;
|
|
18
|
+
userName: string;
|
|
19
|
+
userReactions?: string[];
|
|
20
|
+
userProfileUri: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const Comment = ({
|
|
24
|
+
content,
|
|
25
|
+
createdDate,
|
|
26
|
+
onCommentClick,
|
|
27
|
+
onReact,
|
|
28
|
+
reactions,
|
|
29
|
+
replies,
|
|
30
|
+
userImageUri,
|
|
31
|
+
userName,
|
|
32
|
+
userReactions,
|
|
33
|
+
userProfileUri,
|
|
34
|
+
}: CommentProps) => {
|
|
35
|
+
const id = React.useId();
|
|
36
|
+
const [showPicker, setShowPicker] = React.useState(false);
|
|
37
|
+
|
|
38
|
+
const handleReaction = ({ native }: Record<string, string>) => {
|
|
39
|
+
onReact(native);
|
|
40
|
+
setShowPicker(false);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handlePicker = (event: React.MouseEvent) => {
|
|
44
|
+
event.stopPropagation();
|
|
45
|
+
setShowPicker((prev) => !prev);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handlePickerBlur = () => setShowPicker(false);
|
|
49
|
+
|
|
50
|
+
const createdTime = abbrTimeBeforeDate(createdDate, { message: true });
|
|
51
|
+
|
|
52
|
+
const reactionElements =
|
|
53
|
+
reactions &&
|
|
54
|
+
Object.entries(reactions)
|
|
55
|
+
.sort((a, b) => (a[1] < b[1] ? 1 : -1))
|
|
56
|
+
.map((reaction) => (
|
|
57
|
+
<Reaction
|
|
58
|
+
key={id + reaction[0]}
|
|
59
|
+
$align="center"
|
|
60
|
+
$dir="row"
|
|
61
|
+
$gap={5}
|
|
62
|
+
$userSelected={userReactions?.includes(reaction[0])}
|
|
63
|
+
>
|
|
64
|
+
{reaction[0]}
|
|
65
|
+
<P $bold $fontSize={10} $lineHeight={14}>
|
|
66
|
+
{reaction[1]}
|
|
67
|
+
</P>
|
|
68
|
+
</Reaction>
|
|
69
|
+
));
|
|
70
|
+
|
|
71
|
+
const hasReplies = !!replies?.length && replies.length > 0;
|
|
72
|
+
const replyElements =
|
|
73
|
+
hasReplies &&
|
|
74
|
+
replies.map((reply, index) => (
|
|
75
|
+
<ReplyWrapper
|
|
76
|
+
$isNotLastReply={index !== replies.length - 1}
|
|
77
|
+
key={id + reply.createdDate.getTime().toString(36)}
|
|
78
|
+
>
|
|
79
|
+
<Comment {...reply} />
|
|
80
|
+
<ReplyIndicator />
|
|
81
|
+
</ReplyWrapper>
|
|
82
|
+
));
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Wrapper>
|
|
86
|
+
<Avatar href={userProfileUri} image={userImageUri} size={32} />
|
|
87
|
+
<Box $flex={1} $dir="column">
|
|
88
|
+
<ContentWrapper $hasReplies={hasReplies}>
|
|
89
|
+
<CommentBox>
|
|
90
|
+
<P $bold $color="grey-2" $fontSize={14} $lineHeight={20}>
|
|
91
|
+
{userName}
|
|
92
|
+
</P>
|
|
93
|
+
<P sx="letter-spacing: -0.176px;">{content}</P>
|
|
94
|
+
</CommentBox>
|
|
95
|
+
<Box
|
|
96
|
+
$align="flex-start"
|
|
97
|
+
$dir="row"
|
|
98
|
+
$display="flex"
|
|
99
|
+
$gap={16}
|
|
100
|
+
sx="margin: 8px 0px; position: relative;"
|
|
101
|
+
>
|
|
102
|
+
<ThumbsUp
|
|
103
|
+
color="primary-blue-2"
|
|
104
|
+
onClick={handlePicker}
|
|
105
|
+
size={24}
|
|
106
|
+
sx="border-radius: 50%; cursor: pointer; flex-shrink: 0;"
|
|
107
|
+
/>
|
|
108
|
+
<CommentIcon
|
|
109
|
+
color="primary-blue-2"
|
|
110
|
+
onClick={onCommentClick}
|
|
111
|
+
size={24}
|
|
112
|
+
sx="border-radius: 50%; cursor: pointer; flex-shrink: 0;"
|
|
113
|
+
/>
|
|
114
|
+
<P $color="grey-7">•</P>
|
|
115
|
+
<P
|
|
116
|
+
$color="grey-4"
|
|
117
|
+
$fontSize={12}
|
|
118
|
+
$lineHeight={24}
|
|
119
|
+
sx="flex-shrink: 0;"
|
|
120
|
+
>
|
|
121
|
+
{createdTime}
|
|
122
|
+
</P>
|
|
123
|
+
<Box
|
|
124
|
+
$display="flex"
|
|
125
|
+
$flow="row wrap"
|
|
126
|
+
$gap={6}
|
|
127
|
+
sx="max-width: calc(100% - 190px); margin-left: auto;"
|
|
128
|
+
>
|
|
129
|
+
{reactionElements}
|
|
130
|
+
</Box>
|
|
131
|
+
{showPicker && (
|
|
132
|
+
<PickerWrapper>
|
|
133
|
+
<Picker
|
|
134
|
+
data={emojis}
|
|
135
|
+
onEmojiSelect={handleReaction}
|
|
136
|
+
onClickOutside={handlePickerBlur}
|
|
137
|
+
maxFrequentRows={1}
|
|
138
|
+
navPosition="none"
|
|
139
|
+
previewPosition="none"
|
|
140
|
+
perLine={9}
|
|
141
|
+
theme="auto"
|
|
142
|
+
/>
|
|
143
|
+
</PickerWrapper>
|
|
144
|
+
)}
|
|
145
|
+
</Box>
|
|
146
|
+
</ContentWrapper>
|
|
147
|
+
{hasReplies && <RepliesWrapper>{replyElements}</RepliesWrapper>}
|
|
148
|
+
</Box>
|
|
149
|
+
</Wrapper>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const Wrapper = styled.div`
|
|
154
|
+
max-width: 700px;
|
|
155
|
+
display: flex;
|
|
156
|
+
flex-direction: row;
|
|
157
|
+
align-items: flex-start;
|
|
158
|
+
gap: 12px;
|
|
159
|
+
justify-content: space-between;
|
|
160
|
+
position: relative;
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
const ContentWrapper = styled.div<{
|
|
164
|
+
$hasReplies: boolean;
|
|
165
|
+
}>`
|
|
166
|
+
display: flex;
|
|
167
|
+
flex-direction: column;
|
|
168
|
+
${({ $hasReplies }) =>
|
|
169
|
+
$hasReplies
|
|
170
|
+
? `
|
|
171
|
+
position: relative;
|
|
172
|
+
|
|
173
|
+
&::before {
|
|
174
|
+
content: "";
|
|
175
|
+
position: absolute;
|
|
176
|
+
top: 32px;
|
|
177
|
+
left: -28px;
|
|
178
|
+
height: calc(100% - 12px);
|
|
179
|
+
border-left: 2px solid var(--color-grey-9);
|
|
180
|
+
}`
|
|
181
|
+
: ''}
|
|
182
|
+
`;
|
|
183
|
+
|
|
184
|
+
const CommentBox = styled.div`
|
|
185
|
+
padding: 16px;
|
|
186
|
+
display: flex;
|
|
187
|
+
flex-direction: column;
|
|
188
|
+
gap: 8px;
|
|
189
|
+
background-color: var(--color-grey-10);
|
|
190
|
+
border-radius: 16px;
|
|
191
|
+
`;
|
|
192
|
+
|
|
193
|
+
const PickerWrapper = styled.div`
|
|
194
|
+
position: absolute;
|
|
195
|
+
z-index: 5;
|
|
196
|
+
`;
|
|
197
|
+
|
|
198
|
+
const Reaction = styled.div<BoxProps & { $userSelected?: boolean }>`
|
|
199
|
+
${Styles.flex}
|
|
200
|
+
display: flex;
|
|
201
|
+
padding: 2px 6px;
|
|
202
|
+
border-radius: 12px;
|
|
203
|
+
${({ $userSelected }) =>
|
|
204
|
+
$userSelected
|
|
205
|
+
? `
|
|
206
|
+
background-color: var(--color-primary-blue-4);
|
|
207
|
+
border: 1px solid var(--color-primary-blue-3);
|
|
208
|
+
`
|
|
209
|
+
: ''}
|
|
210
|
+
`;
|
|
211
|
+
|
|
212
|
+
const RepliesWrapper = styled.div`
|
|
213
|
+
padding-top: 12px;
|
|
214
|
+
`;
|
|
215
|
+
|
|
216
|
+
const ReplyWrapper = styled.div<{
|
|
217
|
+
$isNotLastReply?: boolean;
|
|
218
|
+
}>`
|
|
219
|
+
position: relative;
|
|
220
|
+
${({ $isNotLastReply }) =>
|
|
221
|
+
$isNotLastReply
|
|
222
|
+
? `
|
|
223
|
+
&::before {
|
|
224
|
+
content: "";
|
|
225
|
+
position: absolute;
|
|
226
|
+
left: -28px;
|
|
227
|
+
height: 100%;
|
|
228
|
+
border-left: 2px solid var(--color-grey-9);
|
|
229
|
+
}`
|
|
230
|
+
: ''}
|
|
231
|
+
`;
|
|
232
|
+
|
|
233
|
+
const ReplyIndicator = styled.div`
|
|
234
|
+
position: absolute;
|
|
235
|
+
top: 0;
|
|
236
|
+
left: -28px;
|
|
237
|
+
width: 28px;
|
|
238
|
+
height: 16px;
|
|
239
|
+
border-bottom-left-radius: 16px;
|
|
240
|
+
border: 2px solid var(--color-grey-9);
|
|
241
|
+
border-top: none;
|
|
242
|
+
border-right: none;
|
|
243
|
+
`;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
type Options = {
|
|
2
|
+
message?: boolean;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export const abbrTimeBeforeDate = (date: Date, options?: Options) => {
|
|
6
|
+
const { message } = options ?? {};
|
|
7
|
+
const now = new Date();
|
|
8
|
+
const diff = now.getTime() - date.getTime();
|
|
9
|
+
|
|
10
|
+
if (diff < 60000) {
|
|
11
|
+
// under 1 min
|
|
12
|
+
if (message) return 'Now';
|
|
13
|
+
return 'Just now';
|
|
14
|
+
} else if (diff < 3600000) {
|
|
15
|
+
// under one hour
|
|
16
|
+
const minutes = Math.floor(diff / 60000);
|
|
17
|
+
if (message) return `${minutes}m`;
|
|
18
|
+
return `${minutes} ${minutes === 1 ? 'min' : 'mins'}`;
|
|
19
|
+
} else if (diff < 86400000) {
|
|
20
|
+
// under one day
|
|
21
|
+
const hours = Math.floor(diff / 3600000);
|
|
22
|
+
if (message) return `${hours}h`;
|
|
23
|
+
return `${hours} ${hours === 1 ? 'hour' : 'hours'}`;
|
|
24
|
+
} else if (diff < 604800000) {
|
|
25
|
+
// under one week
|
|
26
|
+
const days = Math.floor(diff / 86400000);
|
|
27
|
+
if (message) return `${days}d`;
|
|
28
|
+
return `${days} ${days === 1 ? 'day' : 'days'}`;
|
|
29
|
+
} else {
|
|
30
|
+
if (message) {
|
|
31
|
+
return `${Math.floor(diff / 604800000)}w`;
|
|
32
|
+
}
|
|
33
|
+
return date.toDateString().slice(4);
|
|
34
|
+
}
|
|
35
|
+
};
|