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;
+}