diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 1a3527b0..8166ce16 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -48,6 +48,9 @@ - [Textarea](/components/textarea.md) - [Tooltip](/components/tooltip.md) +- Utility Components + - [Format Bytes](/components/format-bytes.md) + - Design Tokens - [Typography](/tokens/typography.md) - [Color](/tokens/color.md) diff --git a/docs/components/format-bytes.md b/docs/components/format-bytes.md new file mode 100644 index 00000000..227d1691 --- /dev/null +++ b/docs/components/format-bytes.md @@ -0,0 +1,59 @@ +# Format Bytes + +[component-header:sl-format-bytes] + +Formats a number as a human readable bytes value. + +```html preview +
+ The file is in size. +

+ +
+ + +``` + +## Examples + + +### Formatting Bytes + +Set the `value` attribute to a number to get the value in bytes. + +```html preview +
+
+
+ +``` + +### Formatting Bits + +To get the value in bits, set the `unit` attribute to `bits`. + +```html preview +
+
+
+ +``` + +### Localization + +Use the `locale` attribute to set the number formatting locale. + +```html preview +
+
+
+ +``` + +[component-metadata:sl-format-bytes] diff --git a/src/components.d.ts b/src/components.d.ts index a5fda988..e468f157 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -401,6 +401,20 @@ export namespace Components { */ "submit": () => Promise; } + interface SlFormatBytes { + /** + * The locale to use when formatting the number. + */ + "locale": string; + /** + * The unit to display. + */ + "unit": 'bytes' | 'bits'; + /** + * The number to format in bytes. + */ + "value": number; + } interface SlIcon { /** * An alternative description to use for accessibility. If omitted, the name or src will be used to generate it. @@ -1088,6 +1102,12 @@ declare global { prototype: HTMLSlFormElement; new (): HTMLSlFormElement; }; + interface HTMLSlFormatBytesElement extends Components.SlFormatBytes, HTMLStencilElement { + } + var HTMLSlFormatBytesElement: { + prototype: HTMLSlFormatBytesElement; + new (): HTMLSlFormatBytesElement; + }; interface HTMLSlIconElement extends Components.SlIcon, HTMLStencilElement { } var HTMLSlIconElement: { @@ -1241,6 +1261,7 @@ declare global { "sl-drawer": HTMLSlDrawerElement; "sl-dropdown": HTMLSlDropdownElement; "sl-form": HTMLSlFormElement; + "sl-format-bytes": HTMLSlFormatBytesElement; "sl-icon": HTMLSlIconElement; "sl-icon-button": HTMLSlIconButtonElement; "sl-image-comparer": HTMLSlImageComparerElement; @@ -1714,6 +1735,20 @@ declare namespace LocalJSX { */ "onSlSubmit"?: (event: CustomEvent) => void; } + interface SlFormatBytes { + /** + * The locale to use when formatting the number. + */ + "locale"?: string; + /** + * The unit to display. + */ + "unit"?: 'bytes' | 'bits'; + /** + * The number to format in bytes. + */ + "value"?: number; + } interface SlIcon { /** * An alternative description to use for accessibility. If omitted, the name or src will be used to generate it. @@ -2370,6 +2405,7 @@ declare namespace LocalJSX { "sl-drawer": SlDrawer; "sl-dropdown": SlDropdown; "sl-form": SlForm; + "sl-format-bytes": SlFormatBytes; "sl-icon": SlIcon; "sl-icon-button": SlIconButton; "sl-image-comparer": SlImageComparer; @@ -2413,6 +2449,7 @@ declare module "@stencil/core" { "sl-drawer": LocalJSX.SlDrawer & JSXBase.HTMLAttributes; "sl-dropdown": LocalJSX.SlDropdown & JSXBase.HTMLAttributes; "sl-form": LocalJSX.SlForm & JSXBase.HTMLAttributes; + "sl-format-bytes": LocalJSX.SlFormatBytes & JSXBase.HTMLAttributes; "sl-icon": LocalJSX.SlIcon & JSXBase.HTMLAttributes; "sl-icon-button": LocalJSX.SlIconButton & JSXBase.HTMLAttributes; "sl-image-comparer": LocalJSX.SlImageComparer & JSXBase.HTMLAttributes; diff --git a/src/components/format-bytes/format-bytes.tsx b/src/components/format-bytes/format-bytes.tsx new file mode 100644 index 00000000..3b5af8dd --- /dev/null +++ b/src/components/format-bytes/format-bytes.tsx @@ -0,0 +1,29 @@ +import { Component, Prop } from '@stencil/core'; +import { formatBytes } from '../../utilities/number'; + +/** + * @since 2.0 + * @status stable + */ + +@Component({ + tag: 'sl-format-bytes', + shadow: true +}) +export class FormatBytes { + /** The number to format in bytes. */ + @Prop() value = 0; + + /** The unit to display. */ + @Prop() unit: 'bytes' | 'bits' = 'bytes'; + + /** The locale to use when formatting the number. */ + @Prop() locale: string; + + render() { + return formatBytes(this.value, { + unit: this.unit, + locale: this.locale + }); + } +} diff --git a/src/utilities/number.ts b/src/utilities/number.ts new file mode 100644 index 00000000..ce8b4033 --- /dev/null +++ b/src/utilities/number.ts @@ -0,0 +1,32 @@ +// +// Formats a number to a human-readable string of bytes or bits such as "100 MB" +// +export function formatBytes(bytes: number, options: FormatBytesOptions) { + options = Object.assign( + { + unit: 'bytes', + locale: undefined + }, + options + ); + + const byteUnits = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const bitUnits = ['b', 'kbit', 'Mbit', 'Gbit', 'Tbit', 'Pbit', 'Ebit', 'Zbit', 'Ybit']; + const units = options.unit === 'bytes' ? byteUnits : bitUnits; + const isNegative = bytes < 0; + + bytes = Math.abs(bytes); + if (bytes === 0) return '0 B'; + + const i = Math.min(Math.floor(Math.log10(bytes) / 3), units.length - 1); + const num = Number((bytes / Math.pow(1000, i)).toPrecision(3)); + const numString = num.toLocaleString(options.locale); + const prefix = isNegative ? '-' : ''; + + return `${prefix}${numString} ${units[i]}`; +} + +interface FormatBytesOptions { + unit?: 'bytes' | 'bits'; + locale?: string; +}