diff --git a/.vscode/snippets.code-snippets b/.vscode/snippets.code-snippets index f54eb01dab..d1c0d3d6b8 100644 --- a/.vscode/snippets.code-snippets +++ b/.vscode/snippets.code-snippets @@ -1,7 +1,10 @@ { "Btrix Component": { "scope": "javascript,typescript", - "prefix": ["component", "@customElement"], + "prefix": [ + "component", + "@customElement" + ], "isFileTemplate": true, "body": [ "import { localized } from \"@lit/localize\";", @@ -22,7 +25,10 @@ }, "Btrix Component Test": { "scope": "javascript,typescript", - "prefix": ["test","describe"], + "prefix": [ + "test", + "describe" + ], "isFileTemplate": true, "body": [ "import { expect, fixture } from \"@open-wc/testing\";", @@ -54,5 +60,60 @@ "" ], "description": "Unit test for custom component that extends `BtrixComponent`" + }, + "Btrix Storybook Component Render": { + "scope": "javascript,typescript", + "prefix": [ + "renderComponent", + "component" + ], + "isFileTemplate": true, + "body": [ + "import { html } from \"lit\";", + "import { ifDefined } from \"lit/directives/if-defined.js\";", + "", + "import type { ${1:Component} } from \"@/${2:directory}/${3:component}\";", + "", + "import \"@/${2:directory}/${3:component}\";", + "", + "export type RenderProps = ${1:Component};", + "", + "export const renderComponent = (props: Partial) => {", + " return html``;", + "};" + ], + "description": "Component render for Storybook stories" + }, + "Btrix Storybook Component Story": { + "scope": "javascript,typescript", + "prefix": [ + "story", + "stories", + "doc" + ], + "isFileTemplate": true, + "body": [ + "import type { Meta, StoryObj } from \"@storybook/web-components\";", + "", + "import { renderComponent, type RenderProps } from \"./${TM_FILENAME_BASE/\\.stories(.*)/$1/}\";", + "", + "const meta = {", + " title: \"Components/${TM_FILENAME_BASE/\\.stories(.*)/$1/}\",", + " component: \"btrix-${2:component}\",", + " tags: [\"autodocs\"],", + " decorators: [],", + " render: renderComponent,", + " argTypes: {},", + " args: {},", + "} satisfies Meta;", + "", + "export default meta;", + "type Story = StoryObj;", + "", + "export const Basic: Story = {", + " args: {},", + "};" + ], + "description": "Stories for component in Storybook" } -} +} \ No newline at end of file diff --git a/frontend/src/components/ui/badge.ts b/frontend/src/components/ui/badge.ts index e679229211..992601f5de 100644 --- a/frontend/src/components/ui/badge.ts +++ b/frontend/src/components/ui/badge.ts @@ -13,17 +13,15 @@ export type BadgeVariant = | "primary" | "cyan" | "blue" + | "violet" + | "orange" | "high-contrast" | "text" | "text-neutral"; /** - * Show numeric value in a label - * - * Usage example: - * ```ts - * 10 - * ``` + * Badges are compact, non-interactive displays of contextual information. + * They are an unobtrusive way of drawing attention to dynamic data like statuses or counts. */ @customElement("btrix-badge") export class Badge extends TailwindElement { @@ -42,6 +40,12 @@ export class Badge extends TailwindElement { @property({ type: String, reflect: true }) role: string | null = "status"; + /** + * Style as normal text and not data + */ + @property({ type: Boolean }) + asLabel = false; + static styles = css` :host { display: inline-flex; @@ -52,21 +56,28 @@ export class Badge extends TailwindElement { return html` id === this.channelId, ); @@ -36,7 +36,6 @@ export class CrawlerChannelBadge extends TailwindElement { variant=${this.channelId === CrawlerChannelImage.Default ? "neutral" : "blue"} - class="font-monostyle whitespace-nowrap" > ${this.channelId} diff --git a/frontend/src/features/crawls/proxy-badge.ts b/frontend/src/features/crawls/proxy-badge.ts index dabf5082a8..86ce9544ee 100644 --- a/frontend/src/features/crawls/proxy-badge.ts +++ b/frontend/src/features/crawls/proxy-badge.ts @@ -20,16 +20,18 @@ export class ProxyBadge extends TailwindElement { proxyId?: string; render() { - if (!this.proxyId || !this.orgProxies) return; + if (!this.proxyId) return; - const proxy = this.orgProxies.servers.find(({ id }) => id === this.proxyId); + const proxy = this.orgProxies?.servers.find( + ({ id }) => id === this.proxyId, + ); return html` - + ${proxy?.label || this.proxyId} diff --git a/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts b/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts index 0cf0f79332..c2713a4a3d 100644 --- a/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts +++ b/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts @@ -732,9 +732,7 @@ export class ArchivedItemDetail extends BtrixElement { ${msg("Download Item")} ${this.item?.fileSize - ? html` ${this.localize.bytes(this.item.fileSize)}` : nothing} diff --git a/frontend/src/pages/org/archived-items.ts b/frontend/src/pages/org/archived-items.ts index 03e1e2d27b..4eecc8b77f 100644 --- a/frontend/src/pages/org/archived-items.ts +++ b/frontend/src/pages/org/archived-items.ts @@ -792,9 +792,7 @@ export class CrawlsList extends BtrixElement { ${msg("Download Item")} ${item.fileSize - ? html` ${this.localize.bytes(item.fileSize)}` : nothing} diff --git a/frontend/src/pages/org/collection-detail.ts b/frontend/src/pages/org/collection-detail.ts index a49b9d2d30..7a812bc540 100644 --- a/frontend/src/pages/org/collection-detail.ts +++ b/frontend/src/pages/org/collection-detail.ts @@ -562,9 +562,7 @@ export class CollectionDetail extends BtrixElement { ${when( this.collection, (collection) => html` - ${this.localize.bytes( collection.totalSize || 0, )} ${msg("Download Collection")} - ${this.localize.bytes(col.totalSize)} diff --git a/frontend/src/pages/org/crawls.ts b/frontend/src/pages/org/crawls.ts index 685be499cd..6fa9044717 100644 --- a/frontend/src/pages/org/crawls.ts +++ b/frontend/src/pages/org/crawls.ts @@ -658,9 +658,7 @@ export class OrgCrawls extends BtrixElement { ${msg("Download Item")} ${crawl.fileSize - ? html` ${this.localize.bytes(crawl.fileSize)}` : nothing} diff --git a/frontend/src/pages/org/workflow-detail.ts b/frontend/src/pages/org/workflow-detail.ts index 102725c32f..45f4bb751e 100644 --- a/frontend/src/pages/org/workflow-detail.ts +++ b/frontend/src/pages/org/workflow-detail.ts @@ -764,9 +764,7 @@ export class WorkflowDetail extends BtrixElement { ${msg("Item")} ${latestCrawl.fileSize - ? html` ${this.localize.bytes( latestCrawl.fileSize, )}; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = { + args: { + content: "2 URLs", + }, +}; + +const variants = [ + "success", + "warning", + "danger", + "neutral", + "primary", + "cyan", + "blue", + "violet", + "orange", + "high-contrast", + "text", + "text-neutral", +] satisfies RenderProps["variant"][]; + +/** + * Badges can be displayed in different variants. + */ +export const Variant: Story = { + decorators: (story) => + html`
${story()}
`, + render: () => + html`${variants.map((variant) => + renderComponent({ + variant, + content: capitalize(variant), + }), + )}`, +}; + +/** + * Badges can be completely rounded so that they fit rounded + * containers better. + */ +export const Pill: Story = { + decorators: (story) => + html`
${story()}
`, + render: () => + html`${variants.map((variant) => + renderComponent({ + variant, + pill: true, + content: capitalize(variant), + }), + )}`, +}; + +/** + * Badges can be outlined. + */ +export const Outline: Story = { + decorators: (story) => + html`
${story()}
`, + render: () => + html`${variants.map((variant) => + renderComponent({ + variant, + outline: true, + content: capitalize(variant), + }), + )}`, +}; + +/** + * Badges can be displayed with more or less padding. + */ +export const Size: Story = { + decorators: (story) => + html`
${story()}
`, + render: () => { + const sizes = ["medium", "large"] satisfies RenderProps["size"][]; + + return html`${sizes.map((size) => + renderComponent({ + size, + content: capitalize(size), + pill: true, + }), + )}`; + }, +}; + +/** + * By default, badges are displayed using the "monostyle" font to indicate + * that they show contextual, secondary data. + * When used as a label, the badge can be displayed using the default font. + */ +export const AsLabel: Story = { + args: { + content: "Tip", + asLabel: true, + }, +}; + +/** + * These are examples of badges used in features. + */ +export const FeatureBadges: Story = { + render: () => html` + + + `, +}; diff --git a/frontend/src/stories/components/Badge.ts b/frontend/src/stories/components/Badge.ts new file mode 100644 index 0000000000..78a058c2c3 --- /dev/null +++ b/frontend/src/stories/components/Badge.ts @@ -0,0 +1,20 @@ +import { html, type TemplateResult } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import type { Badge } from "@/components/ui/badge"; + +import "@/components/ui/badge"; + +export type RenderProps = Badge & { content: string | TemplateResult }; + +export const renderComponent = (props: Partial) => { + return html` + ${props.content} + `; +};