Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/scrollarea-fade-mask.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"reshaped": minor
---

ScrollArea: Added fade prop that displays a fade mask on the sides of the area that can be scrolled towards
5 changes: 5 additions & 0 deletions .changeset/table-fade-scroll-driven.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"reshaped": patch
---

Table: Fade mask for the horizontally scrollable content is now rendered with CSS scroll-driven animations instead of JS scroll tracking
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,3 @@ Read our [contribution guide](CONTRIBUTING.md) to learn about our principles, de
## License

This project is licensed under the terms of the MIT license.


120 changes: 120 additions & 0 deletions packages/reshaped/src/components/ScrollArea/ScrollArea.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
/* stylelint-disable plugin/browser-compat -- graceful degradation for Firefox */
@property --rs-scroll-area-fade-block-start {
syntax: "<length>";
initial-value: 0;
inherits: false;
}

@property --rs-scroll-area-fade-block-end {
syntax: "<length>";
initial-value: 0;
inherits: false;
}

@property --rs-scroll-area-fade-inline-start {
syntax: "<length>";
initial-value: 0;
inherits: false;
}

@property --rs-scroll-area-fade-inline-end {
syntax: "<length>";
initial-value: 0;
inherits: false;
}
/* stylelint-enable plugin/browser-compat */

.root {
--rs-scroll-area-thumb-size: var(--rs-unit-x1);
--rs-scroll-area-thumb-offset: var(--rs-unit-x1);
--rs-scroll-area-fade-size: var(--rs-unit-x8);

overflow: hidden;
height: 100%;
Expand All @@ -26,6 +53,99 @@
}
}

@keyframes fade-block-start {
from {
--rs-scroll-area-fade-block-start: 0px;
}

to {
--rs-scroll-area-fade-block-start: var(--rs-scroll-area-fade-size);
}
}

@keyframes fade-block-end {
from {
--rs-scroll-area-fade-block-end: var(--rs-scroll-area-fade-size);
}

to {
--rs-scroll-area-fade-block-end: 0px;
}
}

@keyframes fade-inline-start {
from {
--rs-scroll-area-fade-inline-start: 0px;
}

to {
--rs-scroll-area-fade-inline-start: var(--rs-scroll-area-fade-size);
}
}

@keyframes fade-inline-end {
from {
--rs-scroll-area-fade-inline-end: var(--rs-scroll-area-fade-size);
}

to {
--rs-scroll-area-fade-inline-end: 0px;
}
}

@supports (animation-timeline: scroll(self block)) {
.--fade {
--rs-scroll-area-fade-inline-direction: to right;

mask-image:
linear-gradient(
to bottom,
rgb(0 0 0 / 0%) 0,
rgb(0 0 0 / 10%) calc(var(--rs-scroll-area-fade-block-start) * 0.2),
rgb(0 0 0 / 35%) calc(var(--rs-scroll-area-fade-block-start) * 0.4),
rgb(0 0 0 / 65%) calc(var(--rs-scroll-area-fade-block-start) * 0.6),
rgb(0 0 0 / 90%) calc(var(--rs-scroll-area-fade-block-start) * 0.8),
rgb(0 0 0 / 100%) var(--rs-scroll-area-fade-block-start),
rgb(0 0 0 / 100%) calc(100% - var(--rs-scroll-area-fade-block-end)),
rgb(0 0 0 / 90%) calc(100% - var(--rs-scroll-area-fade-block-end) * 0.8),
rgb(0 0 0 / 65%) calc(100% - var(--rs-scroll-area-fade-block-end) * 0.6),
rgb(0 0 0 / 35%) calc(100% - var(--rs-scroll-area-fade-block-end) * 0.4),
rgb(0 0 0 / 10%) calc(100% - var(--rs-scroll-area-fade-block-end) * 0.2),
rgb(0 0 0 / 0%) 100%
),
linear-gradient(
var(--rs-scroll-area-fade-inline-direction),
rgb(0 0 0 / 0%) 0,
rgb(0 0 0 / 10%) calc(var(--rs-scroll-area-fade-inline-start) * 0.2),
rgb(0 0 0 / 35%) calc(var(--rs-scroll-area-fade-inline-start) * 0.4),
rgb(0 0 0 / 65%) calc(var(--rs-scroll-area-fade-inline-start) * 0.6),
rgb(0 0 0 / 90%) calc(var(--rs-scroll-area-fade-inline-start) * 0.8),
rgb(0 0 0 / 100%) var(--rs-scroll-area-fade-inline-start),
rgb(0 0 0 / 100%) calc(100% - var(--rs-scroll-area-fade-inline-end)),
rgb(0 0 0 / 90%) calc(100% - var(--rs-scroll-area-fade-inline-end) * 0.8),
rgb(0 0 0 / 65%) calc(100% - var(--rs-scroll-area-fade-inline-end) * 0.6),
rgb(0 0 0 / 35%) calc(100% - var(--rs-scroll-area-fade-inline-end) * 0.4),
rgb(0 0 0 / 10%) calc(100% - var(--rs-scroll-area-fade-inline-end) * 0.2),
rgb(0 0 0 / 0%) 100%
);
mask-composite: intersect;
animation-name: fade-block-start, fade-block-end, fade-inline-start, fade-inline-end;
animation-timing-function: linear;
animation-fill-mode: both;
animation-timeline:
scroll(self block), scroll(self block), scroll(self inline), scroll(self inline);
animation-range:
0 var(--rs-scroll-area-fade-size),
calc(100% - var(--rs-scroll-area-fade-size)) 100%,
0 var(--rs-scroll-area-fade-size),
calc(100% - var(--rs-scroll-area-fade-size)) 100%;
}

[dir="rtl"] .--fade {
--rs-scroll-area-fade-inline-direction: to left;
}
}

.--overscroll-auto {
overscroll-behavior: auto;
}
Expand Down
4 changes: 3 additions & 1 deletion packages/reshaped/src/components/ScrollArea/ScrollArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ const ScrollArea = forwardRef<HTMLDivElement, T.Props>((props, ref) => {
maxHeight,
scrollbarDisplay = "hover",
overscrollBehavior = "auto",
fade,
onScroll,
className,
attributes,
Expand All @@ -124,7 +125,8 @@ const ScrollArea = forwardRef<HTMLDivElement, T.Props>((props, ref) => {
);
const scrollableClassNames = classNames(
s.scrollable,
overscrollBehavior && s[`--overscroll-${overscrollBehavior}`]
overscrollBehavior && s[`--overscroll-${overscrollBehavior}`],
fade && s["--fade"]
);

const updateScroll = React.useCallback(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export type Props = {
scrollbarDisplay?: "visible" | "hover" | "hidden";
/** Control whether scroll can chain to parent scrollable containers */
overscrollBehavior?: "auto" | "contain";
/** Display a fade mask on the sides of the area that can be scrolled towards */
fade?: boolean;
/** Callback when the scroll area is scrolled */
onScroll?: (args: Coordinates) => void;
/** Height of the scroll area, literal css value or unit token multiplier */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,58 @@ export const overscrollBehavior = {
),
};

export const fade = {
name: "fade",
render: () => (
<Example>
<Example.Item title="fade, vertical scroll">
<ScrollArea height="100px" fade>
<View backgroundColor="neutral-faded" padding={4}>
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum
has been the industry's standard dummy text ever since the 1500s, when an unknown
printer took a galley of type and scrambled it to make a type specimen book. It has
survived not only five centuries, but also the leap into electronic typesetting,
remaining essentially unchanged. It was popularised in the 1960s with the release of
Letraset sheets containing Lorem Ipsum passages, and more recently with desktop
publishing software like Aldus PageMaker including versions of Lorem Ipsum.
</View>
</ScrollArea>
</Example.Item>

<Example.Item title="fade, horizontal scroll">
<ScrollArea height="100px" fade>
<View backgroundColor="neutral-faded" padding={4} width="150%" height="100px">
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum
has been the industry's standard dummy text ever since the 1500s
</View>
</ScrollArea>
</Example.Item>

<Example.Item title="fade, horizontal and vertical scroll">
<ScrollArea height="100px" fade>
<View backgroundColor="neutral-faded" padding={4} width="150%">
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum
has been the industry's standard dummy text ever since the 1500s, when an unknown
printer took a galley of type and scrambled it to make a type specimen book. It has
survived not only five centuries, but also the leap into electronic typesetting,
remaining essentially unchanged. It was popularised in the 1960s with the release of
Letraset sheets containing Lorem Ipsum passages, and more recently with desktop
publishing software like Aldus PageMaker including versions of Lorem Ipsum.
</View>
</ScrollArea>
</Example.Item>

<Example.Item title="fade, no scrollable overflow">
<ScrollArea height="100px" fade>
<View backgroundColor="neutral-faded" padding={4}>
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
</View>
</ScrollArea>
</Example.Item>
</Example>
),
};

export const height = {
name: "height, maxHeight",
render: () => (
Expand Down
74 changes: 55 additions & 19 deletions packages/reshaped/src/components/Table/Table.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,62 @@
/* stylelint-enable plugin/browser-compat */

.root {
--rs-table-fade-size: var(--rs-unit-x8);

overflow: auto;
mask-image: linear-gradient(
to right,
rgb(0 0 0 / 0%) 0,
rgb(0 0 0 / 100%) var(--rs-table-fade-start),
rgb(0 0 0 / 100%) calc(100% - var(--rs-table-fade-end)),
rgb(0 0 0 / 0%) 100%
);
transition: var(--rs-duration-fast) var(--rs-easing-decelerate);
transition-property: --rs-table-fade-start, --rs-table-fade-end;
}

@keyframes fade-start {
from {
--rs-table-fade-start: 0px;
}

to {
--rs-table-fade-start: var(--rs-table-fade-size);
}
}

@keyframes fade-end {
from {
--rs-table-fade-end: var(--rs-table-fade-size);
}

to {
--rs-table-fade-end: 0px;
}
}

@supports (animation-timeline: scroll(self inline)) {
.root {
--rs-table-fade-direction: to right;

mask-image: linear-gradient(
var(--rs-table-fade-direction),
rgb(0 0 0 / 0%) 0,
rgb(0 0 0 / 10%) calc(var(--rs-table-fade-start) * 0.2),
rgb(0 0 0 / 35%) calc(var(--rs-table-fade-start) * 0.4),
rgb(0 0 0 / 65%) calc(var(--rs-table-fade-start) * 0.6),
rgb(0 0 0 / 90%) calc(var(--rs-table-fade-start) * 0.8),
rgb(0 0 0 / 100%) var(--rs-table-fade-start),
rgb(0 0 0 / 100%) calc(100% - var(--rs-table-fade-end)),
rgb(0 0 0 / 90%) calc(100% - var(--rs-table-fade-end) * 0.8),
rgb(0 0 0 / 65%) calc(100% - var(--rs-table-fade-end) * 0.6),
rgb(0 0 0 / 35%) calc(100% - var(--rs-table-fade-end) * 0.4),
rgb(0 0 0 / 10%) calc(100% - var(--rs-table-fade-end) * 0.2),
rgb(0 0 0 / 0%) 100%
);
animation-name: fade-start, fade-end;
animation-timing-function: linear;
animation-fill-mode: both;
animation-timeline: scroll(self inline), scroll(self inline);
animation-range:
0 var(--rs-table-fade-size),
calc(100% - var(--rs-table-fade-size)) 100%;
}

[dir="rtl"] .root {
--rs-table-fade-direction: to left;
}
}

.table {
Expand Down Expand Up @@ -98,13 +144,3 @@
padding-inline-end: calc(var(--rs-unit-x1) * var(--rs-table-p-horizontal-s));
}
}

.--fade-start,
[dir="rtl"] .--fade-end {
--rs-table-fade-start: var(--rs-unit-x4);
}

.--fade-end,
[dir="rtl"] .--fade-start {
--rs-table-fade-end: var(--rs-unit-x4);
}
9 changes: 2 additions & 7 deletions packages/reshaped/src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import React, { isValidElement } from "react";
import { classNames } from "@reshaped/utilities";

import useFadeSide from "@/hooks/_internal/useFadeSide";
import { responsiveVariables } from "@/utilities/props";
import { resolveMixin } from "@/styles/mixin";
import type * as T from "./Table.types";
Expand Down Expand Up @@ -100,23 +99,19 @@ export const TableHead: React.FC<T.HeadProps> = (props) => {

const Table: React.FC<T.Props> = (props) => {
const { children, border, columnBorder, className, attributes } = props;
const rootRef = React.useRef<HTMLDivElement>(null);
const fadeSide = useFadeSide(rootRef);
const rootClassNames = classNames(
s.root,
className,
border && s["--border-outer"],
columnBorder && s["--border-column"],
(fadeSide === "start" || fadeSide === "both") && s["--fade-start"],
(fadeSide === "end" || fadeSide === "both") && s["--fade-end"]
columnBorder && s["--border-column"]
);
const [firstChild] = React.Children.toArray(children);
const isElement = isValidElement(firstChild);
const isBody = isElement && firstChild.type === TableBody;
const isHead = isElement && firstChild.type === TableHead;

return (
<div {...attributes} className={rootClassNames} ref={rootRef}>
<div {...attributes} className={rootClassNames}>
<table className={s.table}>
{isBody || isHead ? children : <TableBody>{children}</TableBody>}
</table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,8 @@ export const edgeCases = {
<Example.Item title="scroll fade">
<Table>
<Table.Row>
<Table.Heading width="500px">Column 1</Table.Heading>
<Table.Heading width="500px">Column 2</Table.Heading>
<Table.Heading minWidth="300px">Column 1</Table.Heading>
<Table.Heading minWidth="300px">Column 2</Table.Heading>
</Table.Row>
<Table.Row>
<Table.Cell>Cell 1</Table.Cell>
Expand Down
2 changes: 1 addition & 1 deletion packages/theming/src/cli/run.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from "node:fs";
import { createRequire } from "node:module";
import path from "node:path";
import process from "node:process";
import { createRequire } from "node:module";
import chalk from "chalk";
import { Command } from "commander";

Expand Down
Loading