diff --git a/web/lib/opal/src/components/buttons/line-item-button/README.md b/web/lib/opal/src/components/buttons/line-item-button/README.md index f2369b7b75..32cbbf9e7e 100644 --- a/web/lib/opal/src/components/buttons/line-item-button/README.md +++ b/web/lib/opal/src/components/buttons/line-item-button/README.md @@ -73,7 +73,7 @@ meanings in one prop. Use `tooltip` for hover text. | `rounding` | `Rounding` | `3` | Corner radius step (`N / 4` rem, or `"full"`); height is content-driven | | `width` | `WidthVariant` | `"full"` | Container width | | `padding` | `0 \| 0.5 \| 1 \| 2` | `0.5` | Padding around the inner `ContentAction`, as a spacing step (`N / 4` rem) | -| `tooltip` | `string` | — | Tooltip text shown on hover | +| `tooltip` | `string \| RichStr` | — | Tooltip shown on hover; markdown, e.g. a link | | `tooltipSide` | `TooltipSide` | `"top"` | Tooltip side | ### Content (pass-through to ContentAction) @@ -86,6 +86,7 @@ meanings in one prop. Use `tooltip` for hover text. | `description` | `string` | — | Description below the title | | `descriptionMaxLines` | `number` | — | Cap the description at N lines and truncate the rest. Unset wraps without a limit — a row showing user-authored text usually wants `1` | | `suffix` | `string` | — | Muted text beside the title, like "(Default)". Shown only by the `heading` variant with a `main-*` `sizePreset`; the default `headline` row drops it | +| `tag` | `TagProps` | — | Tag beside the title, like a plan badge; `heading` variant only | | `sizePreset` | `SizePreset` | `"headline"` | Content size preset | | `variant` | `ContentVariant` | `"heading"` | Content layout variant | | `rightChildren` | `ReactNode` | — | Content after the label (e.g. action button) | diff --git a/web/lib/opal/src/components/buttons/line-item-button/components.tsx b/web/lib/opal/src/components/buttons/line-item-button/components.tsx index 0ebbdfe793..29d5420f0b 100644 --- a/web/lib/opal/src/components/buttons/line-item-button/components.tsx +++ b/web/lib/opal/src/components/buttons/line-item-button/components.tsx @@ -8,6 +8,7 @@ import type { Rounding, } from "@opal/types"; import { Tooltip, type TooltipSide } from "@opal/components"; +import type { TagProps } from "@opal/components/tag/components"; import { type ContentActionProps, type ContentVariant, @@ -62,6 +63,12 @@ type RowContentProps = { */ suffix?: string; + /** + * Tag beside the title, like a plan badge. Shown only by the `heading` + * variant with a `main-*` size preset. + */ + tag?: TagProps; + /** * Cap the description at N lines and truncate the rest. Unset wraps without * a limit, so a row showing user-authored text usually wants `1`. @@ -137,8 +144,8 @@ type LineItemButtonOwnProps = Pick< /** Container width. @default "full" */ width?: ExtremaSizeVariants; - /** Tooltip text shown on hover. */ - tooltip?: string; + /** Tooltip shown on hover. Accepts markdown, e.g. a link. */ + tooltip?: string | RichStr; /** Which side the tooltip appears on. @default "top" */ tooltipSide?: TooltipSide; @@ -184,6 +191,7 @@ function LineItemButton({ description, descriptionMaxLines, suffix, + tag, rightChildren, sizePreset, variant, @@ -269,6 +277,7 @@ function LineItemButton({ description, descriptionMaxLines, suffix, + tag, rightChildren, sizePreset, variant, diff --git a/web/lib/opal/src/components/dropdown/README.md b/web/lib/opal/src/components/dropdown/README.md index a3674534d2..46d35338f5 100644 --- a/web/lib/opal/src/components/dropdown/README.md +++ b/web/lib/opal/src/components/dropdown/README.md @@ -138,7 +138,7 @@ The element the list positions against and matches in width, when that is not th | Kind | Fields | Enter | | -------- | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -| `option` | `value`, `title`, `description?`, `descriptionMaxLines?`, `suffix?`, `icon?` | `onSelect(option)`; closes (single) | +| `option` | `value`, `title`, `description?`, `descriptionMaxLines?`, `suffix?`, `icon?`, `tag?` (a badge beside the title), `tooltip?` (why a disabled row is off; markdown; shown to the row's right) | `onSelect(option)`; closes (single) | | `action` | `id`, `title`, `description?`, `icon?`, `danger?`, `tooltip?`, `keepOpen?`, and `onSelect(views)` and/or `href` (+ `target?`) | runs `onSelect`; an `href` row is a real link and navigates; closes unless `keepOpen` or the handler moved the stack | | `toggle` | `id`, `title`, `description?`, `icon?`, `checked`, `onCheckedChange` | flips `checked`; stays open | | `custom` | `id`, `render(row)`, `onActivate?(views)`, `onSecondary?(views)`, `keepOpen?` | `onActivate`; closes unless `keepOpen` or the handler moved the stack | diff --git a/web/lib/opal/src/components/dropdown/rows.tsx b/web/lib/opal/src/components/dropdown/rows.tsx index 546c6a40ee..9d7223de3e 100644 --- a/web/lib/opal/src/components/dropdown/rows.tsx +++ b/web/lib/opal/src/components/dropdown/rows.tsx @@ -124,16 +124,20 @@ export const Row = React.memo(function Row({ title: row.title, description: row.description, suffix: row.kind === "option" ? row.suffix : undefined, + tag: row.kind === "option" ? row.tag : undefined, descriptionMaxLines: row.kind === "option" ? row.descriptionMaxLines : undefined, color: row.kind === "action" && row.danger ? "danger" : undefined, - tooltip: row.kind === "action" ? row.tooltip : undefined, + tooltip: + row.kind === "action" || row.kind === "option" ? row.tooltip : undefined, + // Beside the list, so it never covers the row above. + tooltipSide: "end", sizePreset, padding: sizePreset === "secondary" ? 0 : 0.5, - // `body` resolves to `ContentSm`, which has no description or suffix - // slot; a row with either takes the `heading` layout. + // `body` resolves to `ContentSm`, which has no description, suffix or + // tag slot; a row with any of them takes the `heading` layout. variant: - row.description || (row.kind === "option" && row.suffix) + row.description || (row.kind === "option" && (row.suffix || row.tag)) ? "heading" : "body", id, diff --git a/web/lib/opal/src/components/dropdown/types.ts b/web/lib/opal/src/components/dropdown/types.ts index 9af765e9a9..9c052d5c44 100644 --- a/web/lib/opal/src/components/dropdown/types.ts +++ b/web/lib/opal/src/components/dropdown/types.ts @@ -1,4 +1,5 @@ import type { IconFunctionComponent, RichStr } from "@opal/types"; +import type { TagProps } from "@opal/components/tag/components"; import type { InputTypeInProps } from "@opal/components/inputs/texts/input-type-in/components"; // --------------------------------------------------------------------------- @@ -25,6 +26,10 @@ export interface DropdownOption extends DropdownRowBase { icon?: IconFunctionComponent; /** Lines the description may take before it clamps, for text a user wrote. */ descriptionMaxLines?: number; + /** Tag beside the title, like a plan badge. */ + tag?: TagProps; + /** Hover text on the row, for the reason it is disabled. Accepts markdown. */ + tooltip?: string | RichStr; } /** @@ -40,8 +45,8 @@ export type DropdownAction = DropdownRowBase & { icon?: IconFunctionComponent; /** A destructive command: the row reads in the danger colour. */ danger?: boolean; - /** Hover text on the row, for the reason it is disabled. */ - tooltip?: string; + /** Hover text on the row, for the reason it is disabled. Accepts markdown. */ + tooltip?: string | RichStr; keepOpen?: boolean; } & ( | { diff --git a/web/lib/opal/src/components/inputs/dropdowns/types.ts b/web/lib/opal/src/components/inputs/dropdowns/types.ts index 0d907aaf66..98ea214b10 100644 --- a/web/lib/opal/src/components/inputs/dropdowns/types.ts +++ b/web/lib/opal/src/components/inputs/dropdowns/types.ts @@ -1,5 +1,6 @@ import type { RichStr } from "@opal/types"; import type { IconFunctionComponent } from "@opal/types"; +import type { TagProps } from "@opal/components/tag/components"; import type { InputTypeInTagProps } from "@opal/components/inputs/texts/input-type-in-tag/components"; /** @@ -16,6 +17,10 @@ export type SelectOption = { suffix?: string; icon?: IconFunctionComponent; disabled?: boolean; + /** Tag beside the title, like a plan badge. */ + tag?: TagProps; + /** Hover text on the row, for the reason it is disabled. Accepts markdown. */ + tooltip?: string | RichStr; }; /** diff --git a/web/src/app/ee/admin/theme/AppearanceThemeSettings.tsx b/web/src/app/ee/admin/theme/AppearanceThemeSettings.tsx index 42ebd90df6..c4f1f7b5fb 100644 --- a/web/src/app/ee/admin/theme/AppearanceThemeSettings.tsx +++ b/web/src/app/ee/admin/theme/AppearanceThemeSettings.tsx @@ -28,7 +28,7 @@ import type { PreviewHighlightTarget } from "./Preview"; import { SvgEdit } from "@opal/icons"; import { useTierAtLeast } from "@/hooks/useTierAtLeast"; import { Tier } from "@/lib/settings/types"; -import { planTagProps } from "@/lib/tier-badge"; +import { PlanTag } from "@/lib/app/plans"; interface AppearanceThemeSettingsProps { selectedLogo: File | null; @@ -530,9 +530,7 @@ export const AppearanceThemeSettings = forwardRef< > {t("helpLink.label")} - {!enterpriseTier && ( - - )} + {!enterpriseTier && }