Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
0e1bd045
Commit
0e1bd045
authored
Apr 15, 2022
by
Lyza Danger Gardner
Committed by
Lyza Gardner
Apr 26, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Transition `ToastMessages` to tailwind
parent
42b5e11e
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
121 additions
and
219 deletions
+121
-219
ToastMessages.js
src/sidebar/components/ToastMessages.js
+67
-36
ToastMessages-test.js
src/sidebar/components/test/ToastMessages-test.js
+12
-42
molecules.scss
src/styles/mixins/molecules.scss
+0
-77
ToastMessages.scss
src/styles/sidebar/components/ToastMessages.scss
+0
-63
_index.scss
src/styles/sidebar/components/_index.scss
+0
-1
tailwind.config.mjs
tailwind.config.mjs
+42
-0
No files found.
src/sidebar/components/ToastMessages.js
View file @
0e1bd045
import
classnames
from
'classnames'
;
import
{
Icon
}
from
'@hypothesis/frontend-shared'
;
import
{
Card
,
Icon
,
Link
}
from
'@hypothesis/frontend-shared'
;
import
{
useSidebarStore
}
from
'../store'
;
import
{
withServices
}
from
'../service-context'
;
...
...
@@ -15,7 +15,7 @@ import { withServices } from '../service-context';
*/
/**
* An individual toast message
—
a brief and transient success or error message.
* An individual toast message
:
a brief and transient success or error message.
* The message may be dismissed by clicking on it.
* Otherwise, the `toastMessenger` service handles removing messages after a
* certain amount of time.
...
...
@@ -23,8 +23,12 @@ import { withServices } from '../service-context';
* @param {ToastMessageProps} props
*/
function
ToastMessage
({
message
,
onDismiss
})
{
// Capitalize the message type for prepending
const
prefix
=
message
.
type
.
charAt
(
0
).
toUpperCase
()
+
message
.
type
.
slice
(
1
);
// Capitalize the message type for prepending; Don't prepend a message
// type for "notice" messages
const
prefix
=
message
.
type
!==
'notice'
?
`
${
message
.
type
.
charAt
(
0
).
toUpperCase
()
+
message
.
type
.
slice
(
1
)}
: `
:
''
;
const
iconName
=
message
.
type
===
'notice'
?
'cancel'
:
message
.
type
;
/**
* a11y linting is disabled here: There is a click-to-remove handler on a
...
...
@@ -36,41 +40,55 @@ function ToastMessage({ message, onDismiss }) {
*/
return
(
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions */
<
li
className
=
{
classnames
(
'toast-message-container'
,
{
'is-dismissed'
:
message
.
isDismissed
,
<
Card
classes
=
{
classnames
(
'p-0 flex border'
,
{
'border-red-error'
:
message
.
type
===
'error'
,
'border-yellow-notice'
:
message
.
type
===
'notice'
,
'border-green-success'
:
message
.
type
===
'success'
,
})}
onClick
=
{()
=>
onDismiss
(
message
.
id
)}
>
<
div
className
=
{
classnames
(
'flex items-center p-3 text-white'
,
{
'bg-red-error'
:
message
.
type
===
'error'
,
'bg-yellow-notice'
:
message
.
type
===
'notice'
,
'bg-green-success'
:
message
.
type
===
'success'
,
})}
>
<
Icon
name
=
{
iconName
}
classes
=
{
classnames
(
// Adjust alignment of icon to appear more aligned with text
'mt-[2px]'
)}
/
>
<
/div
>
<
div
className
=
{
classnames
(
'toast-message'
,
`toast-message--
${
message
.
type
}
`
// TODO: After re-factoring of Card styling, `mt-0` should not need
// !important
'grow p-3 !mt-0'
)}
data
-
testid
=
"toast-message-text"
>
<
div
className
=
"toast-message__type"
>
<
Icon
name
=
{
iconName
}
classes
=
"toast-message__icon"
/>
<
/div
>
<
div
className
=
"toast-message__message"
>
<
strong
>
{
prefix
}:
<
/strong
>
{
message
.
message
}
{
message
.
moreInfoURL
&&
(
<
div
className
=
"toast-message__link"
>
<
a
href
=
{
message
.
moreInfoURL
}
onClick
=
{
event
=>
event
.
stopPropagation
()
/* consume the event so that it does not dismiss the message */
}
target
=
"_new"
>
More
info
<
/a
>
<
/div
>
)}
<
/div
>
<
strong
>
{
prefix
}
<
/strong
>
{
message
.
message
}
{
message
.
moreInfoURL
&&
(
<
div
className
=
"text-right"
>
<
Link
href
=
{
message
.
moreInfoURL
}
onClick
=
{
event
=>
event
.
stopPropagation
()
/* consume the event so that it does not dismiss the message */
}
target
=
"_new"
>
More
info
<
/Link
>
<
/div
>
)}
<
/div
>
<
/
li
>
<
/
Card
>
);
}
...
...
@@ -88,19 +106,32 @@ function ToastMessage({ message, onDismiss }) {
function
ToastMessages
({
toastMessenger
})
{
const
store
=
useSidebarStore
();
const
messages
=
store
.
getToastMessages
();
// The `ul` containing any toast messages is absolute-positioned and the full
// width of the viewport. Each toast message `li` has its position and width
// constrained by `container` configuration in tailwind.
return
(
<
div
>
<
ul
aria
-
live
=
"polite"
aria
-
relevant
=
"additions"
className
=
"
ToastMessages
"
className
=
"
absolute z-2 left-0 w-full space-y-2
"
>
{
messages
.
map
(
message
=>
(
<
ToastMessage
message
=
{
message
}
<
li
className
=
{
classnames
(
'relative w-full container hover:cursor-pointer'
,
'animate-slide-in-from-right '
,
{
'animate-fade-out'
:
message
.
isDismissed
,
}
)}
key
=
{
message
.
id
}
onDismiss
=
{
id
=>
toastMessenger
.
dismiss
(
id
)}
/
>
>
<
ToastMessage
message
=
{
message
}
onDismiss
=
{
id
=>
toastMessenger
.
dismiss
(
id
)}
/
>
<
/li
>
))}
<
/ul
>
<
/div
>
...
...
src/sidebar/components/test/ToastMessages-test.js
View file @
0e1bd045
...
...
@@ -76,26 +76,15 @@ describe('ToastMessages', () => {
});
describe
(
'`ToastMessage` sub-component'
,
()
=>
{
it
(
'should add `is-dismissed` stateful class name if message has been dismissed'
,
()
=>
{
const
message
=
fakeSuccessMessage
();
message
.
isDismissed
=
true
;
fakeStore
.
getToastMessages
.
returns
([
message
]);
const
wrapper
=
createComponent
();
const
messageContainer
=
wrapper
.
find
(
'ToastMessage li'
);
assert
.
isTrue
(
messageContainer
.
hasClass
(
'is-dismissed'
));
});
it
(
'should dismiss the message when clicked'
,
()
=>
{
fakeStore
.
getToastMessages
.
returns
([
fakeSuccessMessage
()]);
const
wrapper
=
createComponent
();
const
messageContainer
=
wrapper
.
find
(
'ToastMessage
li'
);
const
messageContainer
=
wrapper
.
find
(
'ToastMessage
'
).
getDOMNode
(
);
act
(()
=>
{
messageContainer
.
simulate
(
'click'
);
messageContainer
.
dispatchEvent
(
new
Event
(
'click'
)
);
});
assert
.
calledOnce
(
fakeToastMessenger
.
dismiss
);
...
...
@@ -106,7 +95,7 @@ describe('ToastMessages', () => {
const
wrapper
=
createComponent
();
const
link
=
wrapper
.
find
(
'
.toast-message__link a
'
);
const
link
=
wrapper
.
find
(
'
Link
'
);
act
(()
=>
{
link
.
getDOMNode
().
dispatchEvent
(
new
Event
(
'click'
,
{
bubbles
:
true
}));
...
...
@@ -116,38 +105,19 @@ describe('ToastMessages', () => {
});
[
{
message
:
fakeSuccessMessage
(),
className
:
'toast-message--success
'
},
{
message
:
fakeErrorMessage
(),
className
:
'toast-message--error
'
},
{
message
:
fakeNoticeMessage
(),
className
:
'toast-message--notice
'
},
{
message
:
fakeSuccessMessage
(),
prefix
:
'Success:
'
},
{
message
:
fakeErrorMessage
(),
prefix
:
'Error:
'
},
{
message
:
fakeNoticeMessage
(),
prefix
:
'
'
},
].
forEach
(
testCase
=>
{
it
(
'should
assign a CSS class based on
message type'
,
()
=>
{
it
(
'should
prefix the message with the
message type'
,
()
=>
{
fakeStore
.
getToastMessages
.
returns
([
testCase
.
message
]);
const
wrapper
=
createComponent
();
const
messageWrapper
=
wrapper
.
find
(
'.toast-message'
);
assert
.
isTrue
(
messageWrapper
.
hasClass
(
testCase
.
className
));
});
[
{
message
:
fakeSuccessMessage
(),
prefix
:
'Success'
},
{
message
:
fakeErrorMessage
(),
prefix
:
'Error'
},
].
forEach
(
testCase
=>
{
it
(
'should prefix the message with the message type'
,
()
=>
{
fakeStore
.
getToastMessages
.
returns
([
testCase
.
message
]);
const
wrapper
=
createComponent
();
const
messageContent
=
wrapper
.
find
(
'.toast-message__message'
)
.
first
();
assert
.
equal
(
messageContent
.
text
(),
`
${
testCase
.
prefix
}
:
${
testCase
.
message
.
message
}
`
);
});
assert
.
include
(
wrapper
.
text
(),
`
${
testCase
.
prefix
}${
testCase
.
message
.
message
}
`
);
});
});
...
...
@@ -179,7 +149,7 @@ describe('ToastMessages', () => {
const
wrapper
=
createComponent
();
const
link
=
wrapper
.
find
(
'
.toast-message__link a
'
);
const
link
=
wrapper
.
find
(
'
Link
'
);
assert
.
equal
(
link
.
props
().
href
,
'http://www.example.com'
);
assert
.
equal
(
link
.
text
(),
'More info'
);
});
...
...
src/styles/mixins/molecules.scss
View file @
0e1bd045
...
...
@@ -32,80 +32,3 @@
transform
:
rotateX
(
180deg
);
}
}
/**
* A full-width banner with optional "type" and styled icon at left
*/
@mixin
banner
{
@include
layout
.
row
(
$align
:
center
);
width
:
100%
;
background-color
:
var
.
$color-background
;
border-style
:
solid
;
border-width
:
2px
0
;
&
--success
{
border-color
:
var
.
$color-success
;
}
&
--error
{
border-color
:
var
.
$color-error
;
}
&
--notice
{
border-color
:
var
.
$color-notice
;
}
&
__type
{
@include
layout
.
row
(
$align
:
center
);
padding
:
var
.
$layout-space--small
var
.
$layout-space
;
color
:
white
;
}
&
--success
&
__type
{
background-color
:
var
.
$color-success
;
}
&
--error
&
__type
{
background-color
:
var
.
$color-error
;
}
&
--notice
&
__type
{
background-color
:
var
.
$color-notice
;
}
&
__message
{
padding
:
var
.
$layout-space--small
;
width
:
100%
;
}
}
/**
* A variant of `banner` for use as a toast message container. Narrower,
* lighter border, more padding around message text.
*/
@mixin
toast-message
{
@include
banner
;
@include
layout
.
row
(
$align
:
stretch
);
@include
card-frame
;
position
:
relative
;
margin-bottom
:
var
.
$layout-space--small
;
border-width
:
1px
;
&
__type
{
padding
:
var
.
$layout-space
;
}
&
__icon
{
// Specific adjustments for success and error icons
margin-top
:
2px
;
}
&
__message
{
padding
:
var
.
$layout-space
;
}
&
__link
{
text-align
:
right
;
text-decoration
:
underline
;
}
}
src/styles/sidebar/components/ToastMessages.scss
deleted
100644 → 0
View file @
42b5e11e
@use
'../../mixins/molecules'
;
@use
'../../mixins/layout'
;
@use
'../mixins/responsive'
;
@use
'../../variables'
as
var
;
.ToastMessages
{
position
:
absolute
;
z-index
:
1
;
left
:
0
;
width
:
100%
;
padding
:
0
var
.
$layout-space--xsmall
;
@include
responsive
.
tablet-and-up
{
// When displaying in a wider viewport (desktop/tablet outside of sidebar)
max-width
:
responsive
.
$break-tablet
;
width
:
responsive
.
$break-tablet
;
padding-left
:
2rem
;
padding-right
:
2rem
;
left
:
50%
;
margin-left
:
calc
(
-0
.5
*
#{
responsive
.
$break-tablet
}
);
}
}
.toast-message-container
{
position
:
relative
;
width
:
100%
;
animation
:
slidein
0
.3s
forwards
ease-in-out
;
&
:hover
{
cursor
:
pointer
;
}
&
.is-dismissed
{
animation
:
fadeout
0
.3s
forwards
;
}
}
.toast-message
{
@include
molecules
.
toast-message
;
}
@keyframes
slidein
{
0
%
{
opacity
:
0
;
left
:
100%
;
}
80
%
{
left
:
-10px
;
}
100
%
{
left
:
0
;
opacity
:
1
;
}
}
@keyframes
fadeout
{
from
{
opacity
:
1
;
}
to
{
opacity
:
0
;
}
}
src/styles/sidebar/components/_index.scss
View file @
0e1bd045
...
...
@@ -20,7 +20,6 @@
@use
'./PaginationNavigation'
;
@use
'./SearchInput'
;
@use
'./StyledText'
;
@use
'./ToastMessages'
;
@use
'./VersionInfo'
;
// TODO: Evaluate all classes below after components have been converted to
...
...
tailwind.config.mjs
View file @
0e1bd045
...
...
@@ -19,6 +19,8 @@ export default {
'adder-pop-down'
:
'adder-pop-down 0.08s ease-in forwards'
,
'adder-pop-up'
:
'adder-pop-up 0.08s ease-in forwards'
,
'fade-in-slow'
:
'fade-in 1s ease-in'
,
'fade-out'
:
'fade-out 0.3s forwards'
,
'slide-in-from-right'
:
'slide-in-from-right 0.3s forwards ease-in-out'
,
},
borderRadius
:
{
// Tailwind provides a default set of border-radius utility styles
...
...
@@ -45,6 +47,25 @@ export default {
quote
:
'#58cef4'
,
},
},
// Content in the sidebar should never exceed a max-width of `768px`, and
// that content should be auto-centered
container
:
{
center
:
true
,
// Horizontal padding is larger for wider screens
padding
:
{
DEFAULT
:
'0.5rem'
,
lg
:
'4rem'
,
},
// By default, tailwind will provide appropriately-sized containers at
// every breakpoint available in `screens`, but for the sidebar, only
// one width matters: the width associated with the `lg` breakpoint.
// The content container should never be larger than that. `container`
// has a `max-width:100%` until the `lg` breakpoint, after which it
// never exceeds `768px`.
screens
:
{
lg
:
'768px'
,
},
},
fontFamily
:
{
mono
:
[
'"Open Sans Mono"'
,
'Menlo'
,
'"DejaVu Sans Mono"'
,
'monospace'
],
sans
:
[
...
...
@@ -123,6 +144,27 @@ export default {
opacity
:
'1'
,
},
},
'fade-out'
:
{
'0%'
:
{
opacity
:
'1'
,
},
'100%'
:
{
opacity
:
'0'
,
},
},
'slide-in-from-right'
:
{
'0%'
:
{
opacity
:
'0'
,
left
:
'100%'
,
},
'80%'
:
{
left
:
'-10px'
,
},
'100%'
:
{
left
:
'0'
,
opacity
:
'1'
,
},
},
},
screens
:
{
touch
:
{
raw
:
'(pointer: coarse)'
},
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment