Skip to content

Commit

Permalink
Add width option (#70)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
Caesarovich and sindresorhus committed Oct 7, 2021
1 parent 5f88b63 commit 9555d3f
Show file tree
Hide file tree
Showing 11 changed files with 227 additions and 31 deletions.
4 changes: 4 additions & 0 deletions example.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,7 @@ const title = 'Beautiful title';
console.log('\n\n' + boxen('This box has a nice title', {title}) + '\n');

console.log('\n\n' + boxen('This box has a centered title', {title, titleAlignment: 'center'}) + '\n');

console.log('\n\n' + boxen('This box has fixed width of 20', {width: 20}) + '\n');

console.log('\n\n' + boxen('This box has fixed width of 50', {width: 50}) + '\n');
17 changes: 17 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,23 @@ export interface Options {
```
*/
readonly titleAlignment?: 'left' | 'right' | 'center';

/**
Set a fixed width for the box.
**Note*: This disables terminal overflow handling and may cause the box to look broken if the user's terminal is not wide enough.
@example
```
import boxen from 'boxen';
console.log(boxen('foo bar', {width: 15}));
// ┌─────────────┐
// │foo bar │
// └─────────────┘
```
*/
readonly width?: number;
}

/**
Expand Down
94 changes: 63 additions & 31 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,66 @@ const boxContent = (content, contentWidth, options) => {
return top + LINE_SEPARATOR + middle + LINE_SEPARATOR + bottom;
};

const determineDimensions = (text, options) => {
const widthOverride = options.width !== undefined;
const columns = terminalColumns();
const maxWidth = columns - options.margin.left - options.margin.right - BORDERS_WIDTH;

// If width is provided, make sure it's not below 1
if (options.width) {
options.width = Math.max(1, options.width - BORDERS_WIDTH);
}

const widest = widestLine(wrapAnsi(text, columns - BORDERS_WIDTH, {hard: true, trim: false})) + options.padding.left + options.padding.right;

// If title and width are provided, title adheres to fixed width
if (options.title && widthOverride) {
options.title = options.title.slice(0, Math.max(0, options.width - 2));
if (options.title) {
options.title = ` ${options.title} `;
}
} else if (options.title) {
options.title = options.title.slice(0, Math.max(0, maxWidth - 2));

// Recheck if title isn't empty now
if (options.title) {
options.title = ` ${options.title} `;
// If the title is larger than content, box adheres to title width
if (stringWidth(options.title) > widest) {
options.width = stringWidth(options.title);
}
}
}

// If fixed width is provided, use it or content width as reference
options.width = options.width ? options.width : widest;

if (!widthOverride) {
if ((options.margin.left && options.margin.right) && options.width > maxWidth) {
// Let's assume we have margins: left = 3, right = 5, in total = 8
const spaceForMargins = columns - options.width - BORDERS_WIDTH;
// Let's assume we have space = 4
const multiplier = spaceForMargins / (options.margin.left + options.margin.right);
// Here: multiplier = 4/8 = 0.5
options.margin.left = Math.max(0, Math.floor(options.margin.left * multiplier));
options.margin.right = Math.max(0, Math.floor(options.margin.right * multiplier));
// Left: 3 * 0.5 = 1.5 -> 1
// Right: 6 * 0.5 = 3
}

// Re-cap width considering the margins after shrinking
options.width = Math.min(options.width, columns - BORDERS_WIDTH - options.margin.left - options.margin.right);
}

// Prevent padding overflow
if (options.width - (options.padding.left + options.padding.right) <= 0) {
options.padding.left = 0;
options.padding.right = 0;
}

return options;
};

const isHex = color => color.match(/^#(?:[0-f]{3}){1,2}$/i);
const isColorValid = color => typeof color === 'string' && ((chalk[color]) || isHex(color));
const getColorFn = color => isHex(color) ? chalk.hex(color) : chalk[color];
Expand Down Expand Up @@ -253,39 +313,11 @@ export default function boxen(text, options) {
options.padding = getObject(options.padding);
options.margin = getObject(options.margin);

const columns = terminalColumns();

let contentWidth = widestLine(wrapAnsi(text, columns - BORDERS_WIDTH, {hard: true, trim: false})) + options.padding.left + options.padding.right;

// This prevents the title bar to exceed the console's width
options.title = options.title && options.title.slice(0, columns - 4 - options.margin.left - options.margin.right);

if (options.title) {
options.title = ` ${options.title} `;
// Make the box larger to fit a larger title
if (stringWidth(options.title) > contentWidth) {
contentWidth = stringWidth(options.title);
}
}

if ((options.margin.left && options.margin.right) && contentWidth + BORDERS_WIDTH + options.margin.left + options.margin.right > columns) {
// Let's assume we have margins: left = 3, right = 5, in total = 8
const spaceForMargins = columns - contentWidth - BORDERS_WIDTH;
// Let's assume we have space = 4
const multiplier = spaceForMargins / (options.margin.left + options.margin.right);
// Here: multiplier = 4/8 = 0.5
options.margin.left = Math.max(0, Math.floor(options.margin.left * multiplier));
options.margin.right = Math.max(0, Math.floor(options.margin.right * multiplier));
// Left: 3 * 0.5 = 1.5 -> 1
// Right: 6 * 0.5 = 3
}

// Prevent content from exceeding the console's width
contentWidth = Math.min(contentWidth, columns - BORDERS_WIDTH - options.margin.left - options.margin.right);
options = determineDimensions(text, options);

text = makeContentText(text, options.padding, contentWidth, options.textAlignment);
text = makeContentText(text, options.padding, options.width, options.textAlignment);

return boxContent(text, contentWidth, options);
return boxContent(text, options.width, options);
}

export const _borderStyles = cliBoxes;
1 change: 1 addition & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ expectType<string>(boxen('unicorns', {float: 'center'}));
expectType<string>(boxen('unicorns', {backgroundColor: 'green'}));
expectType<string>(boxen('unicorns', {backgroundColor: '#ff0000'}));
expectType<string>(boxen('unicorns', {textAlignment: 'right'}));
expectType<string>(boxen('unicorns', {width: 20}));
8 changes: 8 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,14 @@ Values:
*/
```

##### width

Type: `number`

Set a fixed width for the box.

*Note:* This disables terminal overflow handling and may cause the box to look broken if the user's terminal is not wide enough.

##### padding

Type: `number | object`\
Expand Down
22 changes: 22 additions & 0 deletions tests/snapshots/tests/title-option.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,25 @@ Generated by [AVA](https://avajs.dev).
`┌ very long title ┐␊
│foo │␊
└─────────────────┘`

## title + width option

> Snapshot 1
`┌─┐␊
│f│␊
│o│␊
│o│␊
└─┘`

> Snapshot 2
`┌ v ┐␊
│foo│␊
└───┘`

> Snapshot 3
`┌ very long title ─┐␊
│foo │␊
└──────────────────┘`
Binary file modified tests/snapshots/tests/title-option.js.snap
Binary file not shown.
48 changes: 48 additions & 0 deletions tests/snapshots/tests/width-option.js.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Snapshot report for `tests/width-option.js`

The actual snapshot is saved in `width-option.js.snap`.

Generated by [AVA](https://avajs.dev).

## width option works

> Snapshot 1
`┌──────────────────┐␊
│foo │␊
└──────────────────┘`

> Snapshot 2
`┌────────┐␊
│foo bar │␊
│foo bar │␊
└────────┘`

## width option with padding + margin

> Snapshot 1
`␊
┌──────────────────┐␊
│ │␊
│ foo │␊
│ │␊
└──────────────────┘␊
`

## width option with big padding

> Snapshot 1
`┌────┐␊
│ │␊
│ │␊
│ │␊
│foo │␊
│ │␊
│ │␊
│ │␊
└────┘`
Binary file added tests/snapshots/tests/width-option.js.snap
Binary file not shown.
25 changes: 25 additions & 0 deletions tests/title-option.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,28 @@ test('long title expands box', t => {

t.snapshot(box);
});

test('title + width option', t => {
// Not enough space, no title
t.snapshot(
boxen('foo', {
title: 'very long title',
width: 3,
}),
);

// Space for only one character
t.snapshot(
boxen('foo', {
title: 'very long title',
width: 5,
}),
);

t.snapshot(
boxen('foo', {
title: 'very long title',
width: 20,
}),
);
});
39 changes: 39 additions & 0 deletions tests/width-option.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import test from 'ava';
import boxen from '../index.js';

test('width option works', t => {
// Creates a wide box for little text
t.snapshot(
boxen('foo', {
width: 20,
}),
);

// Creates a small box for a lot of text
t.snapshot(
boxen('foo bar foo bar', {
width: 10,
}),
);
});

test('width option with padding + margin', t => {
// Creates a wide box for little text
const box = boxen('foo', {
width: 20,
margin: 2,
padding: 1,
});

t.snapshot(box);
});

test('width option with big padding', t => {
// Should disable the paddings
const box = boxen('foo', {
width: 6,
padding: 3,
});

t.snapshot(box);
});

0 comments on commit 9555d3f

Please sign in to comment.