Commit a95447b1 authored by Lyza Danger Gardner's avatar Lyza Danger Gardner Committed by Lyza Gardner

Convert Excerpt component to Tailwind

parent 2e26f163
......@@ -20,20 +20,32 @@ import { applyTheme } from '../helpers/theme';
* @param {InlineControlsProps} props
*/
function InlineControls({ isCollapsed, setCollapsed, linkStyle = {} }) {
const toggleLabel = isCollapsed ? 'More' : 'Less';
return (
<div className="Excerpt__inline-controls">
<div className="Excerpt__toggle-container">
<div
className={classnames(
// Position these controls at the bottom right of the excerpt
'absolute block right-0 bottom-0',
// Give extra width for larger tap target and gradient fade
// Fade transparent-to-white left-to-right to make the toggle
// control text (More/Less) more readable above other text.
// This gradient is implemented to-left to take advantage of Tailwind's
// automatic to-transparent calculation: this avoids Safari's problem
// with transparents in gradients:
// https://bugs.webkit.org/show_bug.cgi?id=150940
// https://tailwindcss.com/docs/gradient-color-stops#fading-to-transparent
'w-20 bg-gradient-to-l from-white'
)}
>
<div className="flex justify-end">
<LinkButton
classes="InlineLinkButton InlineLinkButton--underlined"
classes="inline underline"
onClick={() => setCollapsed(!isCollapsed)}
expanded={!isCollapsed}
title="Toggle visibility of full excerpt text"
style={linkStyle}
variant="dark"
>
{toggleLabel}
{isCollapsed ? 'More' : 'Less'}
</LinkButton>
</div>
</div>
......@@ -86,14 +98,13 @@ function Excerpt({
const [collapsedByInlineControls, setCollapsedByInlineControls] =
useState(true);
// Container for the excerpt's content.
const contentElement = /** @type {{ current: HTMLDivElement }} */ (useRef());
// Measured height of `contentElement` in pixels.
// Measured height of `contentElement` in pixels
const [contentHeight, setContentHeight] = useState(0);
// Update the measured height of the content after the initial render and
// when the size of the content element changes.
// Update the measured height of the content container after initial render,
// and when the size of the content element changes.
const updateContentHeight = useCallback(() => {
const newContentHeight = contentElement.current.clientHeight;
setContentHeight(newContentHeight);
......@@ -133,18 +144,48 @@ function Excerpt({
: onToggleCollapsed(collapsed);
return (
<div className="Excerpt" style={contentStyle}>
<div className="Excerpt__content" ref={contentElement}>
<div
data-testid="excerpt-container"
className={classnames(
'relative overflow-hidden',
'transition-[max-height] ease-in duration-150'
)}
style={contentStyle}
>
<div
className={classnames(
// Establish new block-formatting context to prevent margin-collapsing
// in descendent elements from potentially "leaking out" and pushing
// this element down from the top of the container.
// See https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context
// See https://github.com/hypothesis/client/issues/1518
'inline-block w-full'
)}
data-testid="excerpt-content"
ref={contentElement}
>
{children}
</div>
<div
data-testid="excerpt-expand"
role="presentation"
onClick={() => setCollapsed(false)}
className={classnames({
Excerpt__shadow: true,
'Excerpt__shadow--transparent': inlineControls,
'is-hidden': !isExpandable,
})}
className={classnames(
// This element provides a clickable area at the bottom of an
// expandable excerpt to expand it.
'transition-[opacity] duration-150 ease-linear',
'absolute w-full bottom-0 h-touch-minimum',
{
// For expandable excerpts not using inlineControls, style this
// element with a custom shadow-like gradient
'bg-gradient-to-b from-excerptStop1 via-excerptStop2 to-excerptStop3':
!inlineControls && isExpandable,
'bg-none': inlineControls,
// Don't make this shadow visible OR clickable if there's nothing
// to do here (the excerpt isn't expandable)
'opacity-0 pointer-events-none': !isExpandable,
}
)}
title="Show the full excerpt"
/>
{isOverflowing && inlineControls && (
......
......@@ -50,12 +50,14 @@ describe('Excerpt', () => {
});
function getExcerptHeight(wrapper) {
return wrapper.find('.Excerpt').prop('style')['max-height'];
return wrapper.find('[data-testid="excerpt-container"]').prop('style')[
'max-height'
];
}
it('renders content in container', () => {
const wrapper = createExcerpt();
const contentEl = wrapper.find('.Excerpt__content');
const contentEl = wrapper.find('[data-testid="excerpt-content"]');
assert.include(contentEl.html(), 'default content');
});
......@@ -109,7 +111,7 @@ describe('Excerpt', () => {
it('calls `onToggleCollapsed` when user clicks in bottom area to expand excerpt', () => {
const onToggleCollapsed = sinon.stub();
const wrapper = createExcerpt({ onToggleCollapsed }, TALL_DIV);
const control = wrapper.find('.Excerpt__shadow');
const control = wrapper.find('[data-testid="excerpt-expand"]');
assert.equal(getExcerptHeight(wrapper), 40);
control.simulate('click');
assert.called(onToggleCollapsed);
......
@use '../../variables' as var;
// the distance by which the shadow indicating a collapsed
// annotation expands beyond the left/right edges of the card.
// This value is chosen such that the shadow expands to the full width of
// the card
$shadow-h-offset: -12px;
$expand-duration: 0.15s;
.Excerpt {
transition: max-height $expand-duration ease-in;
overflow: hidden;
position: relative;
}
.Excerpt__content {
// Create a new block-formatting context. This prevents any margins on
// elements inside the excerpt from "leaking out" due to margin-collapsing,
// which would push this container element away from the top of the excerpt.
//
// See https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context
// and https://github.com/hypothesis/client/issues/1518.
display: inline-block;
width: 100%;
}
// inline controls for expanding and collapsing
// the excerpt
.Excerpt__inline-controls {
display: block;
position: absolute;
right: 0;
bottom: 0;
}
// A container for a button with a gradient background; this gives partial
// opacity behind the More/Less button so that it is easier to read
.Excerpt__toggle-container {
padding-left: var.$layout-space;
// See https://stackoverflow.com/a/56548711/9788954 regarding
// rgba(255, 255, 255, 0) used here instead of `transparent` (for Safari)
background-image: linear-gradient(
to right,
rgba(255, 255, 255, 0) 0px,
var.$color-background var.$layout-space
);
}
// a shadow displayed at the bottom of an <excerpt>s with inline controls
// disabled, which provides a hint that the excerpt is collapsed
.Excerpt__shadow {
position: absolute;
left: $shadow-h-offset;
right: $shadow-h-offset;
bottom: 0;
height: var.$touch-target-size;
background-image: linear-gradient(
to bottom,
rgba(255, 255, 255, 0) 50%,
rgba(0, 0, 0, 0.08) 95%,
rgba(0, 0, 0, 0.13) 100%
);
transition: opacity $expand-duration linear;
}
.Excerpt__shadow--transparent {
background-image: none;
}
.Excerpt__shadow.is-hidden {
opacity: 0;
pointer-events: none;
}
......@@ -14,7 +14,6 @@
// ----------
@use './AnnotationShareControl';
@use './AutocompleteList';
@use './Excerpt';
@use './FilterSelect';
@use './FilterStatus';
@use './GroupList';
......
......@@ -75,6 +75,13 @@ export default {
'px-tiny': ['10px'],
'px-base': ['16px'],
},
gradientColorStops: {
// These gradient stops define a custom gradient shown at the bottom of
// long annotation body excerpts.
excerptStop1: 'rgba(255, 255, 255, 0) 50%',
excerptStop2: 'rgba(0, 0, 0, 0.08) 95%',
excerptStop3: 'rgba(0, 0, 0, 0.13) 100%',
},
keyframes: {
adderPopDown: {
'0%': {
......
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