npm.io
0.0.2 • Published 5 months ago

@pierre/truncate

Licence
apache-2.0
Version
0.0.2
Deps
0
Size
62 kB
Vulns
0
Weekly
0

@pierre/truncate

Quick helpers for implementing custom truncation experiences, most commonly 'middle truncation.'

Install

Install as a dependency using the package manager of your choice.

pnpm add @pierre/truncate

Then include the @pierre/truncate/style.css file in your base css. It's not super large.

The way to do this varies depending on your tools, but in most modern setups (e.g. next/vite), it should just be a single import.

import "@pierre/truncate/style.css";

Lastly, due to the underlying css technique that's used to make this library work, we cannot inherit the background color of your text in order to obscure it with the marker. For this reason, you'll need to set the `--

Usage | React

Truncate

The Truncate component acts much like text-overflow: ellipsis does. It clips text on the end boundary, and injects a marker when text is overflowing. By default that marker is .

import { Truncate } from '@pierre/truncate/react';

export function MyApp() {
  return (
    <Truncate>This end of this text will be truncated if it overflows</Truncate>
  );
}

Fruncate

The Fruncate component is simply the reverse direction version of Truncate. When the contents overflow, it will clip contents on the start (err… front) boundary.

import { Fruncate } from '@pierre/truncate/react';

export function MyApp() {
  return (
    <Fruncate>
      This beginning of this text will be truncated if it overflows
    </Fruncate>
  );
}

MiddleTruncate

The MiddleTruncate component is the combination of both Truncate and Fruncate. The text will be clipped with a marker at a given split point in the contents. The component can help determine the best split point for your content, and also prioritize which segments are clipped first.

By default, the split setting is set to center, and the priority is set to end. This means that the text will begin to clip at the center point of the contents, and will clip the start segment completely before then clipping the end segment. These settings can be configured.

import { MiddleTruncate } from '@pierre/truncate/react';

export function MyApp() {
  return (
    <MiddleTruncate>
      This content will clip from the center. The starting segment will clip
      first and the ending segment will clip second.
    </MiddleTruncate>
  );
}

All settings

Truncate & Fruncate

type TruncateAndFruncateProps = {
  /**
   * @default '…'
   */
  marker?:
    | React.ReactNode
    | ((props: React.PropsWithChildren) => React.ReactNode);
  /**
   * @default 'default'
   */
  variant?: 'default' | 'fade';

  children?: React.ReactNode;
  style?: React.CSSProperties;
  className?: string;
};

MiddleTruncate

type MiddleTruncateProps = TruncateAndFruncateProps & {
  /**
   * @default 12
   */
  minimumLength?: number;

  /**
   * @default 'end'
   */
  priority?: 'start' | 'end' | 'equal';

  /**
   * @default 'center'
   */
  split?:
    | 'center'
    | 'leaf-path'
    | 'extension'
    | ['first', number]
    | ['last', number]
    | number
    | (contents: string, props?: { priority: …, variant: … }) => [string, string];
}

Examples

All | Customize the clipping marker

You can give all of the components static content to override the marker.

<Truncate marker="▸">{text}</Truncate>

Or you can provide a render function for the marker as well.

<Truncate marker={() => <EllipsisIcon />}>{text}</Truncate>

MiddleTruncate | Changing the clipping priority

By default priority is set to end.

If the beginning of your content is more valuable than the end of your content, then you can switch it to start.

// Clip the end segment first, before beginning to clip the start segment
<MiddleTruncate priority="start">{text}</MiddleTruncate>

// Clip both segments equally from the split point (may not be the center)
<MiddleTruncate priority="equal">{text}</MiddleTruncate>

MiddleTruncate | Splitting presets

The library implements a few common splitting presets to help out in common cases. Many edge cases are handled (e.g. short strings, no paths/extensions found, invalid indexes).

// Will split on `here.tsx`
<MiddleTruncate split="leaf-path">/some/file/path/here.tsx</MiddleTruncate>

// Will split on `tsx`
<MiddleTruncate split="extension">/some/file/path/here.tsx</MiddleTruncate>

// Will split at index 5
<MiddleTruncate split={5}>{text}</MiddleTruncate>

// Technically the same as providing just a number, will split on 8
<MiddleTruncate split={['first', 8]}>{text}</MiddleTruncate>

// Will split 8 from the end
<MiddleTruncate split={['last', 8]}>{text}</MiddleTruncate>

// Will split based on what's provided in index 0 and 1 of the return value
<MiddleTruncate split={(contents) => [contents.slice(0, 9), contents.slice(9)]}>
  {text}
</MiddleTruncate>

MiddleTruncate | Provide each segment manually

If you want to manage your own splits outside of the library, use contents instead of children. You cannot provide both.

<MiddleTruncate contents={['first segment', 'second segment']} />

MiddleTruncate | Fallback to regular truncation below N characters

All | Fade text into edges instead of using a marker

By setting variant to fade, the component will no longer render your marker but instead inject a shadow on the clipping edge. You can customize the styles for the shadow using the css custom properties.

<Fruncate variant="fade">{text}</Fruncate>

Customizing styles

All non-structural choices in the default styling use css custom properties that can be overridden. This should allow you to customize the styles to match your application.

Here are the defaults. If you don't want any customization you don't need to set any of these.

/* !Important to set! Background color of the clipping marker, e.g behind the ellipsis */
--truncate-marker-background-color: light-dark(white, black);

/* Width of the fade from default marker to text */
--truncate-marker-fade-width: 2px;

/* Width of the solid color between the fade from the default marker to the text */
--truncate-marker-gap: 0px;

/* Opacity of the marker 'color' property, not of the element itself */
--truncate-marker-opacity: 50%;

/* Opacity of the marker 'color' property specifically for the middle truncate, not opacity of the element itself */
--truncate-middle-marker-opacity: 80%;

/* Duration of the fade out animation for the marker */
--truncate-marker-fade-out-duration: 0ms;

/* Duration of the fade in animation for the marker */
--truncate-marker-fade-in-duration: 100ms;

/* Only used when setting `variant="fade" */
--truncate-fade-marker-color: #000;
--truncate-fade-marker-width: 0.2lh;

Implementation details

The underlying technique for these components utilizes a css grid, a container query, and a hidden copy of your overflowing text. It uses the hidden overflowing text in one cell to trigger a height container query on a different cell in the grid.

This has many benefits. Namely, it requires no javascript, and works on initial render in SSR setups with no flash during resize.

Constraints

This also has several downsides and constraints. Most of the constraints come from the fact that css container queries have a number of rules about what type of changes can be made inside of the query (this is to avoid infinite styling loops). These rules do not allow you to make changes inside the query styles that would change the layout of the page.

This single fact drives most of the more complex design decisions in this library. The clipping marker that is is shown during overflow is set ass position: absolute and overlaid on top of your contents, in order to avoid triggering this limitation. This is also the reason why there is no way to change the style of your contents when text clips, and why there is not anything like an onOverflow event or similar. These could be layered on top of this library to do more things in the browser after initial render, but it is not currently the goal of this library.