Skip to content

Commit

Permalink
Add title and titleAlignment options (#59)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <[email protected]>
  • Loading branch information
Caesarovich and sindresorhus authored Sep 11, 2021
1 parent c6f9801 commit c50dad6
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 29 deletions.
13 changes: 9 additions & 4 deletions example.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,16 @@ console.log('\n\n' + boxen(chalk.black('unicorn'), {
}) + '\n');

const sentences = 'Unbreakable_text_because_it_has_no_spaces '.repeat(5);
console.log('\n\n' + boxen(sentences, {align: 'left'}) + '\n');
console.log('\n\n' + boxen(sentences, {textAlignment: 'left'}) + '\n');

console.log('\n\n' + boxen(sentences, {align: 'center'}) + '\n');
console.log('\n\n' + boxen(sentences, {textAlignment: 'center'}) + '\n');

console.log('\n\n' + boxen(sentences, {align: 'right', padding: {left: 1, right: 1, top: 0, bottom: 0}}) + '\n');
console.log('\n\n' + boxen(sentences, {textAlignment: 'right', padding: {left: 1, right: 1, top: 0, bottom: 0}}) + '\n');

const longWord = 'x'.repeat(process.stdout.columns + 20);
console.log('\n\n' + boxen(longWord, {align: 'center'}) + '\n');
console.log('\n\n' + boxen(longWord, {textAlignment: 'center'}) + '\n');

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');
42 changes: 42 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,50 @@ declare namespace boxen {
Align the text in the box based on the widest line.
@default 'left'
@deprecated Use `textAlignment` instead.
*/
readonly align?: 'left' | 'right' | 'center';

/**
Align the text in the box based on the widest line.
@default 'left'
*/
readonly textAlignment?: 'left' | 'right' | 'center';

/**
Display a title at the top of the box.
If needed, the box will horizontally expand to fit the title.
@example
```
console.log(boxen('foo bar', {title: 'example'}));
// ┌ example ┐
// │foo bar │
// └─────────┘
```
*/
readonly title?: string;

/**
Align the title in the top bar.
@default 'left'
@example
```
console.log(boxen('foo bar foo bar', {title: 'example', textAlignmentTitle: 'center'}));
// ┌─── example ───┐
// │foo bar foo bar│
// └───────────────┘
console.log(boxen('foo bar foo bar', {title: 'example', textAlignmentTitle: 'right'}));
// ┌────── example ┐
// │foo bar foo bar│
// └───────────────┘
```
*/
readonly titleAlignment?: 'left' | 'right' | 'center';
}
}

Expand Down
56 changes: 52 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const wrapAnsi = require('wrap-ansi');

const NL = '\n';
const PAD = ' ';
const BORDERS_WIDTH = 2;

const terminalColumns = () => {
const {env, stdout, stderr} = process;
Expand Down Expand Up @@ -75,6 +74,35 @@ const getBorderChars = borderStyle => {
return chararacters;
};

const makeTitle = (text, horizontal, alignement) => {
let title = '';

const textWidth = stringWidth(text);

switch (alignement) {
case 'left':
title = text + horizontal.slice(textWidth);
break;
case 'right':
title = horizontal.slice(textWidth) + text;
break;
default:
horizontal = horizontal.slice(textWidth);

if (horizontal.length % 2 === 1) { // This is needed in case the length is odd
horizontal = horizontal.slice(Math.floor(horizontal.length / 2));
title = horizontal.slice(1) + text + horizontal; // We reduce the left part of one character to avoid the bar to go beyond its limit
} else {
horizontal = horizontal.slice(horizontal.length / 2);
title = horizontal + text + horizontal;
}

break;
}

return title;
};

const makeContentText = (text, padding, columns, align) => {
text = ansiAlign(text, {align});
let lines = text.split(NL);
Expand Down Expand Up @@ -158,11 +186,19 @@ module.exports = (text, options) => {
padding: 0,
borderStyle: 'single',
dimBorder: false,
align: 'left',
textAlignment: 'left',
float: 'left',
titleAlignment: 'left',
...options
};

// This option is deprecated
if (options.align) {
options.textAlignment = options.align;
}

const BORDERS_WIDTH = 2;

if (options.borderColor && !isColorValid(options.borderColor)) {
throw new Error(`${options.borderColor} is not a valid borderColor`);
}
Expand All @@ -186,6 +222,18 @@ module.exports = (text, options) => {

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

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

if (title) {
title = ` ${title} `;
}

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

if ((margin.left && margin.right) && contentWidth + BORDERS_WIDTH + margin.left + margin.right > columns) {
// Let's assume we have margins: left = 3, right = 5, in total = 8
const spaceForMargins = columns - contentWidth - BORDERS_WIDTH;
Expand All @@ -201,7 +249,7 @@ module.exports = (text, options) => {
// Prevent content from exceeding the console's width
contentWidth = Math.min(contentWidth, columns - BORDERS_WIDTH - margin.left - margin.right);

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

let marginLeft = PAD.repeat(margin.left);

Expand All @@ -214,7 +262,7 @@ module.exports = (text, options) => {
}

const horizontal = chars.horizontal.repeat(contentWidth);
const top = colorizeBorder(NL.repeat(margin.top) + marginLeft + chars.topLeft + horizontal + chars.topRight);
const top = colorizeBorder(NL.repeat(margin.top) + marginLeft + chars.topLeft + (title ? makeTitle(title, horizontal, options.titleAlignment) : horizontal) + chars.topRight);
const bottom = colorizeBorder(marginLeft + chars.bottomLeft + horizontal + chars.bottomRight + NL.repeat(margin.bottom));
const side = colorizeBorder(chars.vertical);

Expand Down
4 changes: 3 additions & 1 deletion index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const spacing: Spacing = {
};

expectType<string>(boxen('unicorns'));
expectType<string>(boxen('unicorns', {title: 'title'}));
expectType<string>(boxen('unicorns', {title: 'title', titleAlignment: 'center'}));
expectType<string>(boxen('unicorns', {borderColor: 'green'}));
expectType<string>(boxen('unicorns', {borderColor: '#ff0000'}));
expectType<string>(boxen('unicorns', {borderStyle: 'double'}));
Expand All @@ -31,4 +33,4 @@ expectType<string>(boxen('unicorns', {margin: spacing}));
expectType<string>(boxen('unicorns', {float: 'center'}));
expectType<string>(boxen('unicorns', {backgroundColor: 'green'}));
expectType<string>(boxen('unicorns', {backgroundColor: '#ff0000'}));
expectType<string>(boxen('unicorns', {align: 'right'}));
expectType<string>(boxen('unicorns', {textAlignment: 'right'}));
59 changes: 58 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ console.log(boxen('unicorn', {padding: 1, margin: 1, borderStyle: 'double'}));
╚═════════════╝
*/

console.log(boxen('unicorns love rainbows', {title: 'magical', titleAlignment: 'center'}));
/*
┌────── magical ───────┐
│unicorns love rainbows│
└──────────────────────┘
*/
```

## API
Expand Down Expand Up @@ -127,6 +134,56 @@ Default: `false`

Reduce opacity of the border.

##### title

Type: `string`

Display a title at the top of the box.
If needed, the box will horizontally expand to fit the title.

Example:
```js
console.log(boxen('foo bar', {title: 'example'}));
/*
┌ example ┐
│foo bar │
└─────────┘
*/
```

##### titleAlignment

Type: `string`\
Default: `'left'`

Align the title in the top bar.

Values:
- `'left'`
```js
/*
┌ example ──────┐
│foo bar foo bar│
└───────────────┘
*/
```
- `'center'`
```js
/*
┌─── example ───┐
│foo bar foo bar│
└───────────────┘
*/
```
- `'right'`
```js
/*
┌────── example ┐
│foo bar foo bar│
└───────────────┘
*/
```

##### padding

Type: `number | object`\
Expand Down Expand Up @@ -160,7 +217,7 @@ Values: `'black'` `'red'` `'green'` `'yellow'` `'blue'` `'magenta'` `'cyan'` `'w

Color of the background.

##### align
##### textAlignment

Type: `string`\
Default: `'left'`\
Expand Down
Loading

0 comments on commit c50dad6

Please sign in to comment.