Commit 41105c91 authored by Lyza Danger Gardner's avatar Lyza Danger Gardner Committed by Lyza Gardner

Modernize styling of `AdderToolbar`

Convert the `AdderToolbar` component to tailwind utility-first styles.

This component has some styling complexity that requires the continued
use of some external styles for now:

* It serves as the root element in a shadow DOM and has a couple of
  reset rules not serviceable by tailwind utility classes
* The hover behavior of the adder toolbar buttons is too complex to
  implement with utility styles alone.

Visual differences before/after are negligible. The adder is 1px taller
due to using standardized spacing.
parent 429d0ea6
......@@ -3,6 +3,58 @@ import { LabeledButton, Icon } from '@hypothesis/frontend-shared';
import { useShortcut } from '../../shared/shortcut';
/**
* Render an inverted light-on-dark "pill" with the given `badgeCount`
* (annotation count). This is rendered instead of an icon on the toolbar
* button for "show"-ing associated annotations for the current selection.
*
* @param {object} props
* @param {number} props.badgeCount
*/
function NumberIcon({ badgeCount }) {
return (
<span
className={classnames(
'rounded px-1 py-0.5',
'text-color-text-inverted font-bold bg-grey-7',
'dim-bg'
)}
>
{badgeCount}
</span>
);
}
/**
* Render an arrow pointing up or down from the AdderToolbar. This arrow
* should point roughly to the end of the user selection in the document.
*
* @param {object} props
* @param {'up'|'down'} props.arrowDirection
*/
function AdderToolbarArrow({ arrowDirection }) {
return (
<Icon
name="pointer"
classes={classnames(
// Position the arrow in the horizontal center at the bottom of the
// container (toolbar). Note: the arrow is pointing up at this point.
'absolute left-1/2 -translate-x-1/2',
// Override `1em` width/height rules in `Icon` to size the arrow as
// its SVG dimensions dictate
'h-auto w-auto z-10',
'text-grey-3 fill-white',
{
// Down arrow: transform to point the arrow down
'rotate-180': arrowDirection === 'down',
// Up arrow: position vertically above the toolbar
'top-0 -translate-y-full': arrowDirection === 'up',
}
)}
/>
);
}
/**
* @param {object} props
* @param {number} [props.badgeCount]
......@@ -18,17 +70,18 @@ function ToolbarButton({ badgeCount, icon, label, onClick, shortcut }) {
return (
<LabeledButton
classes="LabeledIconButton AdderToolbar__button"
className={classnames(
'flex flex-col gap-y-1 items-center py-2.5 px-2',
'text-annotator-sm leading-none text-grey-7',
'transition-colors duration-200',
'dim-item'
)}
icon={icon}
onClick={onClick}
title={title}
>
{typeof badgeCount === 'number' && (
<span className="hyp-u-bg-color--grey-7 AdderToolbar__badge">
{badgeCount}
</span>
)}
<span className="LabeledIconButton__label">{label}</span>
{typeof badgeCount === 'number' && <NumberIcon badgeCount={badgeCount} />}
<span className="font-normal">{label}</span>
</LabeledButton>
);
}
......@@ -54,8 +107,8 @@ function ToolbarButton({ badgeCount, icon, label, onClick, shortcut }) {
*/
/**
* The toolbar that is displayed above selected text in the document providing
* options to create annotations or highlights.
* The toolbar that is displayed above or below selected text in the document,
* providing options to create annotations or highlights.
*
* @param {AdderToolbarProps} props
*/
......@@ -82,17 +135,25 @@ export default function AdderToolbar({
return (
<div
className={classnames(
'hyp-u-border hyp-u-bg-color--white',
'AdderToolbar',
'absolute select-none bg-white rounded shadow-adderToolbar',
// Because `.AdderToolbar` rules reset `all:initial`, we cannot use
// default border values from Tailwind and have to be explicit about
// all border attributes
'border border-solid border-grey-3',
// Start at a very low opacity as we're going to fade in in the animation
'opacity-5',
{
'AdderToolbar--down': arrowDirection === 'up',
'AdderToolbar--up': arrowDirection === 'down',
'is-active': isVisible,
'animate-adderPopUp': arrowDirection === 'up' && isVisible,
'animate-adderPopDown': arrowDirection === 'down' && isVisible,
}
)}
style={{ visibility: isVisible ? 'visible' : 'hidden' }}
dir="ltr"
style={{
visibility: isVisible ? 'visible' : 'hidden',
}}
>
<div className="hyp-u-layout-row AdderToolbar__actions">
<div className="flex dim-items-on-hover">
<ToolbarButton
icon="annotate"
onClick={() => onCommand('annotate')}
......@@ -106,24 +167,23 @@ export default function AdderToolbar({
shortcut={highlightShortcut}
/>
{annotationCount > 0 && (
<div className="hyp-u-margin--2 AdderToolbar__separator" />
)}
{annotationCount > 0 && (
<ToolbarButton
badgeCount={annotationCount}
onClick={() => onCommand('show')}
label="Show"
shortcut={showShortcut}
/>
<>
<div
className={classnames(
// Style a vertical separator line
'm-1.5 border-r border-grey-4 border-solid'
)}
/>
<ToolbarButton
badgeCount={annotationCount}
onClick={() => onCommand('show')}
label="Show"
shortcut={showShortcut}
/>
</>
)}
</div>
<Icon
name="pointer"
classes={classnames('AdderToolbar__arrow', {
'AdderToolbar__arrow--down': arrowDirection === 'down',
'AdderToolbar__arrow--up': arrowDirection === 'up',
})}
/>
<AdderToolbarArrow arrowDirection={arrowDirection} />
</div>
);
}
@use '../variables' as var;
$adder-transition-duration: 80ms;
// Main class for the root element of the "adder" toolbar that appears when the
// user makes a text selection.
.AdderToolbar {
// Reset all inherited properties to their initial values. This prevents CSS
// property values from the host page being inherited by elements of the
// Adder, even when using Shadow DOM.
all: initial;
position: absolute;
box-sizing: border-box;
direction: ltr;
border-radius: var.$annotator-border-radius;
// Prevent the browser from selecting text in the adder itself during text
// selection.
//
// See https://github.com/hypothesis/product-backlog/issues/878
user-select: none;
// The adder toolbar has a distinctive, broad-spreading drop shadow
box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.25);
// Give the adder a very low opacity initially. It will then fade-in when
// shown.
opacity: 0.05;
// Adder entry animation settings
animation-duration: $adder-transition-duration;
animation-timing-function: ease-in;
animation-fill-mode: forwards;
&--down.is-active {
animation-name: adder-fade-in, adder-pop-down;
}
&--up.is-active {
animation-name: adder-fade-in, adder-pop-up;
}
}
.AdderToolbar__arrow {
position: absolute;
z-index: 2;
color: var.$color-border;
fill: var.$color-background;
// Horizontal centering, part 1
left: 50%;
// Override `Icon` default 1em sizing
height: auto;
width: auto;
&--down {
// These transforms:
// 1) Translate -50% (left) on the X axis to complete horizontal centering
// 2) Translate up (Y) by 1px to eliminate a faint border line visible in Safari
transform: rotateX(180deg) translateX(-50%) translateY(1px);
}
&--up {
// Align at top edge of parent element...
top: 0;
// Translate on X axis -50% (left) to complete horizontal centering
// Translate -100% on Y axis (up) to position arrow above parent
transform: translate(-50%, -100%);
}
}
.AdderToolbar__badge {
// The badge should be vertically aligned with icons in other toolbar buttons
// and the label underneath should also align with labels in other buttons.
border-radius: var.$annotator-border-radius;
color: var.$color-text--inverted;
font-weight: bold;
// For positioning the number appropriately within the badge background
padding: 2px 4px;
}
.AdderToolbar__separator {
// Override border color to be a little darker
border-right: 1px solid var.$grey-4;
}
// The toolbar has full contrast when not hovered. When the toolbar is hovered,
// the buttons are dimmed except for the one which is currently hovered.
.AdderToolbar__actions:hover {
.AdderToolbar__button:not(:hover) {
color: var.$grey-semi;
.AdderToolbar__badge {
background-color: var.$grey-semi;
@layer components {
// Main class for the root element of the "adder" toolbar that appears when the
// user makes a text selection.
.AdderToolbar {
// Reset all inherited properties to their initial values. This prevents CSS
// property values from the host page being inherited by elements of the
// Adder, even when using Shadow DOM.
all: initial;
// Provide some hover-related behavior that cannot be feasibly duplicated
// using utility styles.
//
// When the container/parent element containing the annotator toolbar
// buttons is hovered, dim the buttons except the specifically-hovered
// button, which should be darkened. The "annotate" and "highlight" buttons
// use icons and a label, so dimming/darkening their text color suffices,
// but the badge-count pill needs its background-color dimmed/darkened as
// well (`.dim-bg`).
.dim-items-on-hover:hover {
.dim-item:not(:hover) {
@apply text-grey-5;
.dim-bg {
@apply bg-grey-5;
}
}
.dim-item:hover {
@apply text-grey-9;
.dim-bg {
@apply bg-grey-9;
}
}
}
}
}
@keyframes adder-fade-in {
0% {
opacity: 0.05;
}
20% {
opacity: 0.7;
}
100% {
opacity: 1;
}
}
@keyframes adder-pop-up {
from {
transform: scale(0.8) translateY(10px);
}
to {
transform: scale(1) translateY(0px);
}
}
@keyframes adder-pop-down {
from {
transform: scale(0.8) translateY(-10px);
}
to {
transform: scale(1) translateY(0px);
}
}
......@@ -33,32 +33,6 @@
}
}
// Used in the AdderToolbar, a transparent button that has an icon and a small
// label below the icon
.LabeledIconButton {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
&:hover:not([disabled]) {
background-color: transparent;
}
cursor: pointer;
font-weight: normal;
font-size: var.$annotator-adder-font-size;
line-height: var.$annotator-adder-line-height;
padding: var.$layout-space--small;
transition: color 80ms;
background: transparent;
&__label {
margin-top: var.$layout-space--xxsmall;
transition: color 80ms;
}
}
// Override any background color on a LabeledButton
.TransparentButton {
background-color: transparent;
......
......@@ -123,7 +123,6 @@ $sidebar--theme-clean: '.theme-clean';
// -----------------------------------
$annotator-toolbar-width: 33px;
$annotator-base-font-size: 14px;
$annotator-base-line-height: 20px;
$annotator-adder-font-size: 12px;
......
......@@ -10,11 +10,21 @@ export default {
],
theme: {
extend: {
animation: {
adderPopUp: 'adderPopUp 0.08s ease-in forwards',
adderPopDown: 'adderPopDown 0.08s ease-in forwards',
},
boxShadow: {
adderToolbar: '0px 2px 10px 0px rgba(0, 0, 0, 0.25)',
},
colors: {
slate: {
// TODO remove when available from upstream preset
1: '#e3e3e5',
},
'color-text': {
inverted: '#f2f2f2',
},
},
fontFamily: {
sans: [
......@@ -31,9 +41,41 @@ export default {
fontSize: {
tiny: ['10px'],
sm: ['11px', '1.4'],
'annotator-sm': ['12px'],
base: ['13px', '1.4'],
lg: ['14px'],
xl: ['16px'],
annotator: {
sm: ['12px'],
},
},
keyframes: {
adderPopDown: {
'0%': {
opacity: '0.05',
transform: 'scale(0.8) translateY(10px)',
},
'20%': {
opacity: '0.7',
},
'100%': {
opacity: '1',
transform: 'scale(1) translateY(0px)',
},
},
adderPopUp: {
'0%': {
opacity: '0.05',
transform: 'scale(0.8) translateY(-10px)',
},
'20%': {
opacity: '0.7',
},
'100%': {
opacity: '1',
transform: 'scale(1) translateY(0px)',
},
},
},
},
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment