GDGT-2454 Badge (#74714)
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
@@ -198,12 +198,7 @@ function FeedbackCard({
|
||||
/>
|
||||
<Text fw={700}>{feedback.positive ? t`Positive` : t`Negative`}</Text>
|
||||
{!feedback.positive && feedback.issue_type && (
|
||||
<Badge
|
||||
variant="light"
|
||||
bg="background_surface-error"
|
||||
c="feedback-negative"
|
||||
ml="xs"
|
||||
>
|
||||
<Badge color="negative" ml="xs" size="sm">
|
||||
{getIssueTypeLabel(feedback.issue_type)}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
@@ -127,7 +127,7 @@ export function ConversationsTable({
|
||||
<td>{convo.user ? getUserName(convo.user) : t`Unknown`}</td>
|
||||
<td>
|
||||
{convo.profile_id && (
|
||||
<Badge size="sm" variant="light">
|
||||
<Badge color="brand" variant="filled">
|
||||
{renderMetabotProfileLabel(convo.profile_id)}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
@@ -66,7 +66,9 @@ export function initializePlugin() {
|
||||
component={ForwardRefLink}
|
||||
to={`${Urls.collection(collection)}/cleanup`}
|
||||
rightSection={
|
||||
hasStaleItems ? <Badge>{t`Recommended`}</Badge> : null
|
||||
hasStaleItems ? (
|
||||
<Badge color="brand">{t`Recommended`}</Badge>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{t`Clear out unused items`}
|
||||
|
||||
@@ -21,12 +21,15 @@ export const DatabaseReplicationStatusInfo = memo(
|
||||
t`Replicating to Metabase Cloud Storage`
|
||||
: // eslint-disable-next-line metabase/no-literal-metabase-strings -- This string only shows for admins.
|
||||
t`Not replicating to Metabase Cloud Storage`;
|
||||
const color = hasConnection ? "success" : "text-disabled";
|
||||
|
||||
return (
|
||||
<Flex align="center" gap="sm" {...flexProps}>
|
||||
<Tooltip label={message}>
|
||||
<Badge size="12" circle bg={color} style={{ flexShrink: 0 }} />
|
||||
<Badge
|
||||
color={hasConnection ? "positive" : "neutral"}
|
||||
flex="0 0 auto"
|
||||
indicator
|
||||
/>
|
||||
</Tooltip>
|
||||
<Text lh="1.25rem">{message}</Text>
|
||||
</Flex>
|
||||
|
||||
@@ -71,7 +71,7 @@ export function BrokenDependentsSection({
|
||||
<Stack role="region" aria-label={getDependentErrorNodesLabel()}>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Badge variant="filled" bg="feedback-negative">
|
||||
<Badge color="negative" size="sm">
|
||||
{count}
|
||||
</Badge>
|
||||
<Title order={5}>
|
||||
|
||||
@@ -72,7 +72,7 @@ function ErrorList({ title, items, "aria-label": ariaLabel }: ErrorListProps) {
|
||||
return (
|
||||
<Stack role="region" aria-label={ariaLabel}>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Badge variant="filled" bg="feedback-negative">
|
||||
<Badge color="negative" size="sm">
|
||||
{items.length}
|
||||
</Badge>
|
||||
<Title order={5}>{title}</Title>
|
||||
|
||||
@@ -34,7 +34,7 @@ export function FieldsSection({ node }: FieldsSectionProps) {
|
||||
return (
|
||||
<Stack role="region" aria-label={t`Fields`}>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Badge variant="filled" bg="core-brand">
|
||||
<Badge color="brand" size="sm">
|
||||
{fields.length}
|
||||
</Badge>
|
||||
<Title order={5}>{getNodeFieldsLabel(fields.length)}</Title>
|
||||
|
||||
@@ -85,14 +85,9 @@ export const GitSyncOptionsDropdown = ({
|
||||
|
||||
{isSwitchBranchDisabled ? (
|
||||
<Badge
|
||||
color="text-primary"
|
||||
bg="background_page-secondary"
|
||||
size="md"
|
||||
fz="12px"
|
||||
py="md"
|
||||
my="xs"
|
||||
style={{ textTransform: "none" }}
|
||||
bdrs="sm"
|
||||
size="sm"
|
||||
variant="light"
|
||||
>{t`Branch set by an environment variable`}</Badge>
|
||||
) : (
|
||||
<Combobox.Option
|
||||
|
||||
@@ -54,7 +54,7 @@ export function DependentsSection({ cardId }: DependentsSectionProps) {
|
||||
<Stack role="region" aria-label={label}>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Badge variant="filled" bg="core-brand">
|
||||
<Badge color="brand" size="sm">
|
||||
{allDependents.length}
|
||||
</Badge>
|
||||
<Title order={5}>{label}</Title>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
.severityCritical {
|
||||
background-color: var(--mb-color-feedback-negative);
|
||||
color: var(--mb-color-core-white);
|
||||
}
|
||||
|
||||
.severityHigh {
|
||||
background-color: var(--mb-color-feedback-warning);
|
||||
color: var(--mb-color-core-white);
|
||||
}
|
||||
|
||||
.severityMedium {
|
||||
background-color: var(--mb-color-feedback-warning);
|
||||
color: var(--mb-color-text-primary);
|
||||
}
|
||||
|
||||
.severityLow {
|
||||
background-color: var(--mb-color-feedback-positive);
|
||||
color: var(--mb-color-core-white);
|
||||
}
|
||||
|
||||
.severityNeutral {
|
||||
background-color: var(--mb-color-background_surface-disabled);
|
||||
color: var(--mb-color-text-disabled);
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { match } from "ts-pattern";
|
||||
import { t } from "ttag";
|
||||
|
||||
import {
|
||||
Anchor,
|
||||
Badge,
|
||||
type BadgeColor,
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
@@ -22,15 +24,6 @@ import type {
|
||||
|
||||
import { isAcknowledged } from "../../utils";
|
||||
|
||||
import S from "./AdvisoryCard.module.css";
|
||||
|
||||
const SEVERITY_CLASS: Record<AdvisorySeverity, string> = {
|
||||
critical: S.severityCritical,
|
||||
high: S.severityHigh,
|
||||
medium: S.severityMedium,
|
||||
low: S.severityLow,
|
||||
};
|
||||
|
||||
interface AdvisoryCardProps {
|
||||
advisory: Advisory;
|
||||
isAffecting: boolean;
|
||||
@@ -49,13 +42,18 @@ export function AdvisoryCard({
|
||||
<Stack gap="md">
|
||||
<Group gap="sm" justify="space-between" align="flex-start">
|
||||
<Badge
|
||||
className={
|
||||
isAffecting
|
||||
? SEVERITY_CLASS[advisory.severity]
|
||||
: S.severityNeutral
|
||||
color={getSeverityColor({
|
||||
isAffecting,
|
||||
severity: advisory.severity,
|
||||
})}
|
||||
tt={
|
||||
isAffecting && advisory.severity === "critical"
|
||||
? "uppercase"
|
||||
: undefined
|
||||
}
|
||||
size="sm"
|
||||
>
|
||||
{advisory.severity}
|
||||
{getSeverityLabel(advisory.severity)}
|
||||
</Badge>
|
||||
{!isAffecting && (
|
||||
<Group gap="xs" align="center">
|
||||
@@ -130,3 +128,31 @@ export function AdvisoryCard({
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function getSeverityColor({
|
||||
isAffecting,
|
||||
severity,
|
||||
}: {
|
||||
isAffecting: boolean;
|
||||
severity: AdvisorySeverity;
|
||||
}): BadgeColor {
|
||||
if (!isAffecting) {
|
||||
return "neutral";
|
||||
}
|
||||
|
||||
return match(severity)
|
||||
.with("critical", () => "negative" as const)
|
||||
.with("high", () => "negative" as const)
|
||||
.with("medium", () => "warning" as const)
|
||||
.with("low", () => "positive" as const)
|
||||
.exhaustive();
|
||||
}
|
||||
|
||||
function getSeverityLabel(severity: AdvisorySeverity): string {
|
||||
return match(severity)
|
||||
.with("critical", () => t`Critical`)
|
||||
.with("high", () => t`High`)
|
||||
.with("medium", () => t`Medium`)
|
||||
.with("low", () => t`Low`)
|
||||
.exhaustive();
|
||||
}
|
||||
|
||||
@@ -16,8 +16,3 @@
|
||||
min-height: 50vh;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.countBadge {
|
||||
background-color: var(--mb-color-text-primary);
|
||||
color: var(--mb-color-core-white);
|
||||
}
|
||||
|
||||
@@ -70,9 +70,7 @@ export function AdvisoryList({
|
||||
{affecting.length > 0 && (
|
||||
<Box>
|
||||
<Group gap="sm" mb="md" align="center">
|
||||
<Badge size="sm" className={S.countBadge}>
|
||||
{affecting.length}
|
||||
</Badge>
|
||||
<Badge size="sm">{affecting.length}</Badge>
|
||||
<Title order={4}>{t`Issues impacting your instance`}</Title>
|
||||
</Group>
|
||||
<AdvisorySection
|
||||
|
||||
@@ -74,8 +74,9 @@ export const AccessGrantList = (props: AccessGrantListProps) => {
|
||||
) : (
|
||||
<Group gap="sm">
|
||||
<Badge
|
||||
m={0}
|
||||
color="brand"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
title={effectiveEndDate.format("lll")}
|
||||
>
|
||||
{c("{0} is the time until expiration")
|
||||
|
||||
@@ -20,9 +20,9 @@ const _AddBadgeListItem = (
|
||||
}}
|
||||
bg="background_page-secondary"
|
||||
tt="capitalize"
|
||||
size="lg"
|
||||
size="sm"
|
||||
variant="transparent"
|
||||
c="text-brand"
|
||||
color="brand"
|
||||
pr="sm"
|
||||
pl="xs"
|
||||
leftSection={
|
||||
|
||||
@@ -18,7 +18,7 @@ const _BadgeListItem = (
|
||||
) => (
|
||||
<Badge
|
||||
ref={ref}
|
||||
size="lg"
|
||||
size="sm"
|
||||
tt="capitalize"
|
||||
variant="light"
|
||||
classNames={{
|
||||
@@ -27,6 +27,7 @@ const _BadgeListItem = (
|
||||
}}
|
||||
pr={0}
|
||||
pl="sm"
|
||||
color="brand"
|
||||
rightSection={
|
||||
<ActionIcon
|
||||
radius="xl"
|
||||
|
||||
@@ -39,13 +39,8 @@ export function AIProviderSettingsSection({ id }: { id?: string }) {
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
{connectedProvider && (
|
||||
<Badge
|
||||
circle
|
||||
size="12"
|
||||
bg={
|
||||
hasCredentialsError
|
||||
? "feedback-negative"
|
||||
: "feedback-positive"
|
||||
}
|
||||
color={hasCredentialsError ? "negative" : "positive"}
|
||||
indicator
|
||||
mr="sm"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -18,6 +18,7 @@ import CS from "metabase/css/core/index.css";
|
||||
import type { WithRouterProps } from "metabase/router";
|
||||
import {
|
||||
Badge,
|
||||
type BadgeColor,
|
||||
Box,
|
||||
Card,
|
||||
Ellipsified,
|
||||
@@ -27,7 +28,6 @@ import {
|
||||
type TreeTableColumnDef,
|
||||
useTreeTableInstance,
|
||||
} from "metabase/ui";
|
||||
import type { MetabaseColorKey } from "metabase/ui/colors/types";
|
||||
import type {
|
||||
OAuthAuthorization,
|
||||
OAuthClientEventType,
|
||||
@@ -45,10 +45,10 @@ const ALL_EVENT_TYPES = "all";
|
||||
|
||||
type EventTypeFilter = OAuthClientEventType | typeof ALL_EVENT_TYPES;
|
||||
|
||||
const EVENT_COLORS: Record<OAuthClientEventType, MetabaseColorKey> = {
|
||||
registered: "text-secondary",
|
||||
approved: "success",
|
||||
denied: "error",
|
||||
const EVENT_COLORS: Record<OAuthClientEventType, BadgeColor> = {
|
||||
registered: "neutral",
|
||||
approved: "positive",
|
||||
denied: "negative",
|
||||
};
|
||||
|
||||
type UrlState = {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { match } from "ts-pattern";
|
||||
import { t } from "ttag";
|
||||
|
||||
import { useGetDatabaseHealthQuery } from "metabase/api";
|
||||
import { Badge, Flex, type FlexProps, Text, Tooltip } from "metabase/ui";
|
||||
import { Badge, Flex, type FlexProps, Text, Tooltip, rem } from "metabase/ui";
|
||||
import type { DatabaseConnectionType, DatabaseId } from "metabase-types/api";
|
||||
|
||||
interface DatabaseConnectionHealthInfoProps extends FlexProps {
|
||||
@@ -26,7 +26,7 @@ export const DatabaseConnectionHealthInfo = ({
|
||||
return match(healthQuery)
|
||||
.with(
|
||||
{ currentData: { status: "ok" } },
|
||||
() => ({ message: t`Connected`, color: "feedback-positive" }) as const,
|
||||
() => ({ message: t`Connected`, color: "positive" }) as const,
|
||||
)
|
||||
.with(
|
||||
{ isUninitialized: true },
|
||||
@@ -35,7 +35,7 @@ export const DatabaseConnectionHealthInfo = ({
|
||||
() =>
|
||||
({
|
||||
message: t`Loading...`,
|
||||
color: "tooltip-text",
|
||||
color: "neutral",
|
||||
}) as const,
|
||||
)
|
||||
.with(
|
||||
@@ -43,7 +43,7 @@ export const DatabaseConnectionHealthInfo = ({
|
||||
(q) =>
|
||||
({
|
||||
message: q.currentData.message,
|
||||
color: "feedback-negative",
|
||||
color: "negative",
|
||||
}) as const,
|
||||
)
|
||||
.with(
|
||||
@@ -51,22 +51,19 @@ export const DatabaseConnectionHealthInfo = ({
|
||||
() =>
|
||||
({
|
||||
message: t`Failed to retrieve database health status.`,
|
||||
color: "tooltip-text",
|
||||
color: "neutral",
|
||||
}) as const,
|
||||
)
|
||||
.exhaustive();
|
||||
}, [healthQuery]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
align="center"
|
||||
gap="sm"
|
||||
{...props}
|
||||
data-testid="database-connection-health-info"
|
||||
>
|
||||
<Tooltip label={health.message}>
|
||||
<Badge size="12" circle bg={health.color} style={{ flexShrink: 0 }} />
|
||||
</Tooltip>
|
||||
<Flex gap="sm" {...props} data-testid="database-connection-health-info">
|
||||
<Flex py={rem(6)}>
|
||||
<Tooltip label={health.message}>
|
||||
<Badge flex="0 0 auto" indicator color={health.color} />
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
{displayText === "inline" && <Text lh="1.4">{health.message}</Text>}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -52,10 +52,9 @@ const SlackConnectionStatus = ({
|
||||
<Flex justify="space-between" align="center">
|
||||
<Flex align="center" gap="sm">
|
||||
<Badge
|
||||
circle
|
||||
size="12"
|
||||
bg={isValid ? "feedback-positive" : "feedback-negative"}
|
||||
style={{ flexShrink: 0 }}
|
||||
color={isValid ? "positive" : "negative"}
|
||||
flex="0 0 auto"
|
||||
indicator
|
||||
/>
|
||||
<Text>
|
||||
{isValid ? t`Slack app is working` : t`Slack app is not working.`}
|
||||
|
||||
@@ -116,9 +116,8 @@ export function TablePicker({
|
||||
|
||||
{filtersCount > 0 && (
|
||||
<Badge
|
||||
bg="core-brand"
|
||||
circle
|
||||
size="8"
|
||||
color="brand"
|
||||
indicator
|
||||
pos="absolute"
|
||||
top={-6}
|
||||
right={-8}
|
||||
|
||||
@@ -212,8 +212,8 @@ const CodeEditDataPart = ({
|
||||
<Icon name="document" c="text-secondary" />
|
||||
<Text fw="bold">{formatPartType(type)}</Text>
|
||||
<Text c="text-secondary">{t`Buffer ID: ${value.buffer_id}`}</Text>
|
||||
<Badge variant="light" size="sm">
|
||||
{value.mode}
|
||||
<Badge color="brand" size="sm" variant="light">
|
||||
{getModeLabel(value.mode)}
|
||||
</Badge>
|
||||
</Flex>
|
||||
<ActionIcon
|
||||
@@ -237,3 +237,9 @@ const CodeEditDataPart = ({
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
function getModeLabel(mode: MetabotCodeEdit["mode"]): string {
|
||||
return match(mode)
|
||||
.with("rewrite", () => t`Rewrite`)
|
||||
.exhaustive();
|
||||
}
|
||||
|
||||
@@ -57,9 +57,9 @@ const ToolCallDetailsModal = ({
|
||||
onClose={onClose}
|
||||
size="lg"
|
||||
title={
|
||||
<Flex align="center">
|
||||
<Flex align="center" gap="sm">
|
||||
{t`Tool Call: ${message.name}`}
|
||||
<Badge ml="sm" color="text-primary">
|
||||
<Badge color="brand" size="sm" variant="light">
|
||||
{message.id}
|
||||
</Badge>
|
||||
</Flex>
|
||||
@@ -84,14 +84,10 @@ const ToolCallDetailsModal = ({
|
||||
{message.result && (
|
||||
<Stack gap="xs">
|
||||
<Flex gap="xs">
|
||||
<Flex align="center">
|
||||
<Flex align="center" gap="sm">
|
||||
<Text fw="bold">{t`Response`}</Text>
|
||||
{message.is_error && (
|
||||
<Badge
|
||||
ml="sm"
|
||||
bg="feedback-negative"
|
||||
c="text-primary-inverse"
|
||||
>
|
||||
<Badge color="negative" size="sm">
|
||||
{t`Errored`}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.badge {
|
||||
height: auto;
|
||||
min-height: 2rem;
|
||||
min-height: var(--badge-height);
|
||||
}
|
||||
|
||||
/* override Badge's single-line ellipsis truncation */
|
||||
|
||||
@@ -20,16 +20,9 @@ export const LabeledValue = ({ children, label }: Props) => {
|
||||
|
||||
<Badge
|
||||
aria-labelledby={id}
|
||||
bg="accent-gray-light"
|
||||
c="text-primary"
|
||||
classNames={{ root: S.badge, label: S.badgeLabel }}
|
||||
fw="normal"
|
||||
p="sm"
|
||||
radius="md"
|
||||
size="lg"
|
||||
tt="none"
|
||||
variant="default"
|
||||
lh="sm"
|
||||
size="sm"
|
||||
variant="light"
|
||||
>
|
||||
{children}
|
||||
</Badge>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
.metricAccordionItem {
|
||||
overflow: hidden;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.metricAccordionControl {
|
||||
|
||||
@@ -42,9 +42,11 @@ export function MetricAccordionItem({
|
||||
</Text>
|
||||
{showOccurrenceCount && (
|
||||
<Badge
|
||||
className={S.occurrenceCountBadge}
|
||||
circle
|
||||
c="text-brand-hover"
|
||||
className={S.occurrenceCountBadge}
|
||||
color="brand"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
>
|
||||
{group.occurrenceCount}
|
||||
</Badge>
|
||||
|
||||
@@ -24,7 +24,7 @@ export function MetricDimensionSelect({
|
||||
{row.metricName}
|
||||
</Text>
|
||||
{showOccurrenceCount && (
|
||||
<Badge circle c="text-brand-hover">
|
||||
<Badge circle color="brand" variant="light">
|
||||
{row.occurrenceCount}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
@@ -222,7 +222,7 @@ function MetricGroupList({
|
||||
<Flex align="center" gap="xs" fw={700}>
|
||||
<span>{group.metricName}</span>
|
||||
{(group.metricCount ?? 0) > 1 && (
|
||||
<Badge circle c="text-brand-hover">
|
||||
<Badge circle color="brand" variant="light">
|
||||
{group.metricCount}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
@@ -145,9 +145,10 @@ export function MetricExpressionPill({
|
||||
if (typeof segment === "number") {
|
||||
return (
|
||||
<Badge
|
||||
key={i}
|
||||
circle
|
||||
c="text-brand-hover"
|
||||
color="brand"
|
||||
key={i}
|
||||
variant="light"
|
||||
style={{ marginInlineStart: "0.2em" }}
|
||||
>
|
||||
{segment}
|
||||
|
||||
@@ -79,6 +79,7 @@ export function MetricsFilterPillPopover({
|
||||
// override background from Badge.config.tsx
|
||||
styles={{ root: { background: "var(--badge-bg)" } }}
|
||||
ml="xs"
|
||||
variant="light"
|
||||
>
|
||||
{metricCount}
|
||||
</Badge>
|
||||
|
||||
@@ -95,6 +95,7 @@ export function MetricsSegmentFilterPillPopover({
|
||||
// override background from Badge.config.tsx
|
||||
styles={{ root: { background: "var(--badge-bg)" } }}
|
||||
ml="xs"
|
||||
variant="light"
|
||||
>
|
||||
{metricCount}
|
||||
</Badge>
|
||||
|
||||
@@ -8,7 +8,11 @@ type TaskRunStatusBadgeProps = {
|
||||
};
|
||||
|
||||
export const TaskRunStatusBadge = ({ taskRun }: TaskRunStatusBadgeProps) => (
|
||||
<Badge color={getTaskRunStatusColor(taskRun.status)}>
|
||||
<Badge
|
||||
color={getTaskRunStatusColor(taskRun.status)}
|
||||
size="sm"
|
||||
variant="light"
|
||||
>
|
||||
{formatTaskRunStatus(taskRun.status)}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ type TaskStatusProps = {
|
||||
};
|
||||
|
||||
export const TaskStatusBadge = ({ task }: TaskStatusProps) => (
|
||||
<Badge color={getTaskStatusColor(task.status)}>
|
||||
<Badge color={getTaskStatusColor(task.status)} size="sm" variant="light">
|
||||
{formatTaskStatus(task.status)}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
@@ -63,20 +63,7 @@ export const NotificationRunSummaryLog = ({
|
||||
</Text>
|
||||
</Tooltip>
|
||||
<Tooltip label={run.error} disabled={!hasError}>
|
||||
<Badge
|
||||
color={isFailing ? "feedback-negative" : undefined}
|
||||
variant={isFailing ? "light" : "outline"}
|
||||
radius="lg"
|
||||
tt="none"
|
||||
fw="normal"
|
||||
c={isFailing ? undefined : "text-secondary"}
|
||||
bd={
|
||||
isFailing
|
||||
? undefined
|
||||
: "1px solid var(--mb-color-border-neutral)"
|
||||
}
|
||||
style={hasError ? { cursor: "pointer" } : undefined}
|
||||
>
|
||||
<Badge color={isFailing ? "negative" : "positive"} variant="light">
|
||||
{isFailing ? t`Failed` : t`Successful`}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
|
||||
@@ -81,20 +81,7 @@ export const NotificationsTable = ({
|
||||
accessorFn: (notification) => notification.id,
|
||||
cell: ({ row }) => (
|
||||
<Flex justify="center">
|
||||
<Badge
|
||||
variant="default"
|
||||
bg="background_page-secondary"
|
||||
c="text-primary"
|
||||
tt="none"
|
||||
bd="1px solid var(--mb-color-border-neutral)"
|
||||
h="auto"
|
||||
px="sm"
|
||||
py="xs"
|
||||
fz="md"
|
||||
lh="xs"
|
||||
fw={400}
|
||||
miw={29}
|
||||
>
|
||||
<Badge variant="outline" size="sm" miw={29}>
|
||||
{row.original.id}
|
||||
</Badge>
|
||||
</Flex>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { match } from "ts-pattern";
|
||||
import { t } from "ttag";
|
||||
|
||||
import type { BadgeColor } from "metabase/ui";
|
||||
import * as Urls from "metabase/urls";
|
||||
import type {
|
||||
Task,
|
||||
@@ -55,18 +56,18 @@ export const getEntityUrl = (
|
||||
.with("dashboard", () => Urls.dashboard({ id: entityId }))
|
||||
.exhaustive();
|
||||
|
||||
export const getTaskRunStatusColor = (status: TaskRunStatus) =>
|
||||
export const getTaskRunStatusColor = (status: TaskRunStatus): BadgeColor =>
|
||||
match(status)
|
||||
.with("success", () => "success" as const)
|
||||
.with("failed", () => "error" as const)
|
||||
.with("success", () => "positive" as const)
|
||||
.with("failed", () => "negative" as const)
|
||||
.with("abandoned", () => "warning" as const)
|
||||
.otherwise(() => "text-primary" as const);
|
||||
.otherwise(() => "neutral" as const);
|
||||
|
||||
export const getTaskStatusColor = (status: TaskStatus) =>
|
||||
export const getTaskStatusColor = (status: TaskStatus): BadgeColor =>
|
||||
match(status)
|
||||
.with("success", () => "success" as const)
|
||||
.with("failed", () => "error" as const)
|
||||
.otherwise(() => "text-primary" as const);
|
||||
.with("success", () => "positive" as const)
|
||||
.with("failed", () => "negative" as const)
|
||||
.otherwise(() => "neutral" as const);
|
||||
|
||||
export const getFilename = (task: Task | undefined) =>
|
||||
task ? `task-${task.id}.json` : "task.json";
|
||||
|
||||
@@ -114,9 +114,11 @@ export const ChartTypeOption = ({
|
||||
label={t`This is a development version of the visualization`}
|
||||
>
|
||||
<Badge
|
||||
color="brand"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
aria-label={t`This is a development version of the visualization`}
|
||||
>{t`dev`}</Badge>
|
||||
>{t`Dev`}</Badge>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -87,7 +87,7 @@ export const JobRunSidebar = memo(function JobRunSidebar({
|
||||
<JobRunInfoSection run={run} />
|
||||
<Stack gap="md">
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Badge variant="filled" bg="core-brand">
|
||||
<Badge color="brand" size="sm" variant="filled">
|
||||
{transformRuns.length}
|
||||
</Badge>
|
||||
<Title order={5}>{t`Transforms`}</Title>
|
||||
|
||||
@@ -49,7 +49,8 @@ export const DERIVED_COLOR_KEYS = [
|
||||
"core-filter",
|
||||
"core-summarize",
|
||||
] as const;
|
||||
type DerivedColorKey = (typeof DERIVED_COLOR_KEYS)[number];
|
||||
|
||||
export type DerivedColorKey = (typeof DERIVED_COLOR_KEYS)[number];
|
||||
|
||||
/**
|
||||
* Metabase color keys without colors that are automatically derived.
|
||||
|
||||
@@ -1,15 +1,116 @@
|
||||
import { Badge } from "@mantine/core";
|
||||
export type { BadgeProps } from "@mantine/core";
|
||||
|
||||
import type { ColorName } from "metabase/ui/colors/types";
|
||||
import { color } from "metabase/ui/utils/colors";
|
||||
|
||||
import BadgeStyles from "./Badge.module.css";
|
||||
|
||||
const BADGE_COLORS = [
|
||||
"neutral",
|
||||
"brand",
|
||||
"negative",
|
||||
"warning",
|
||||
"positive",
|
||||
] as const;
|
||||
|
||||
type BadgeColor = (typeof BADGE_COLORS)[number];
|
||||
|
||||
const FILLED_BG: Record<BadgeColor, ColorName> = {
|
||||
neutral: "feedback-neutral-strong",
|
||||
brand: "background_surface-brand-strong",
|
||||
negative: "feedback-negative-strong",
|
||||
warning: "feedback-warning-strong",
|
||||
positive: "feedback-positive-strong",
|
||||
};
|
||||
|
||||
const FILLED_TEXT: Record<BadgeColor, ColorName> = {
|
||||
neutral: "core-white_constant",
|
||||
brand: "text-primary-inverse",
|
||||
negative: "text-primary-inverse",
|
||||
warning: "text-primary-inverse",
|
||||
positive: "text-primary-inverse",
|
||||
};
|
||||
|
||||
const ACCENT_TEXT: Record<BadgeColor, ColorName> = {
|
||||
neutral: "text-primary",
|
||||
brand: "text-brand-strong",
|
||||
negative: "feedback-negative-strong",
|
||||
warning: "feedback-warning-strong",
|
||||
positive: "feedback-positive-strong",
|
||||
};
|
||||
|
||||
const OUTLINE_BORDER: Record<BadgeColor, ColorName> = {
|
||||
neutral: "feedback-neutral-strong",
|
||||
brand: "core-brand",
|
||||
negative: "feedback-negative-strong",
|
||||
warning: "feedback-warning-strong",
|
||||
positive: "feedback-positive-strong",
|
||||
};
|
||||
|
||||
const LIGHT_BG: ColorName = "background_surface-secondary";
|
||||
|
||||
const INDICATOR_BG: Record<BadgeColor, ColorName> = {
|
||||
neutral: "icon-disabled",
|
||||
brand: "core-brand",
|
||||
negative: "feedback-negative",
|
||||
warning: "feedback-warning",
|
||||
positive: "feedback-positive",
|
||||
};
|
||||
|
||||
const SIZES = {
|
||||
xs: { height: "1rem", paddingX: "0.375rem" }, // 16px / 6px
|
||||
sm: { height: "1.5rem", paddingX: "0.5rem" }, // 24px / 8px
|
||||
} as const;
|
||||
|
||||
const isBadgeColor = (value: unknown): value is BadgeColor =>
|
||||
BADGE_COLORS.includes(value as BadgeColor);
|
||||
|
||||
type BadgeRootVars = Partial<
|
||||
Record<
|
||||
| "--badge-height"
|
||||
| "--badge-padding-x"
|
||||
| "--badge-fz"
|
||||
| "--badge-bg"
|
||||
| "--badge-color"
|
||||
| "--badge-bd",
|
||||
string
|
||||
>
|
||||
>;
|
||||
|
||||
export const badgeOverrides = {
|
||||
Badge: Badge.extend({
|
||||
defaultProps: {
|
||||
variant: "light",
|
||||
},
|
||||
classNames: {
|
||||
root: BadgeStyles.root,
|
||||
},
|
||||
vars: (_theme, props) => {
|
||||
const { size, variant, color: badgeColor } = props;
|
||||
const isIndicator = "data-indicator" in props;
|
||||
const { height, paddingX } = SIZES[size === "sm" ? "sm" : "xs"];
|
||||
const root: BadgeRootVars = {
|
||||
"--badge-height": height,
|
||||
"--badge-padding-x": paddingX,
|
||||
"--badge-fz": "0.6875rem", // 11px
|
||||
};
|
||||
|
||||
// Non-BadgeColor values fall through to Mantine's default resolver so
|
||||
// colors like "core-filter" can still work.
|
||||
if (isBadgeColor(badgeColor)) {
|
||||
if (isIndicator) {
|
||||
root["--badge-bg"] = color(INDICATOR_BG[badgeColor]);
|
||||
} else if (variant === "filled") {
|
||||
root["--badge-bg"] = color(FILLED_BG[badgeColor]);
|
||||
root["--badge-color"] = color(FILLED_TEXT[badgeColor]);
|
||||
} else if (variant === "light") {
|
||||
root["--badge-bg"] = color(LIGHT_BG);
|
||||
root["--badge-color"] = color(ACCENT_TEXT[badgeColor]);
|
||||
} else if (variant === "outline") {
|
||||
root["--badge-bg"] = "transparent";
|
||||
root["--badge-color"] = color(ACCENT_TEXT[badgeColor]);
|
||||
root["--badge-bd"] = `1px solid ${color(OUTLINE_BORDER[badgeColor])}`;
|
||||
}
|
||||
}
|
||||
|
||||
return { root };
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
.root {
|
||||
background-color: var(--mb-color-background_surface-brand-subtle);
|
||||
--badge-radius: 1rem; /* 16px */
|
||||
|
||||
text-transform: none;
|
||||
font-weight: 600;
|
||||
line-height: 0.9375rem; /* 15px */
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.root[data-indicator] {
|
||||
width: 0.5rem; /* 8px */
|
||||
height: 0.5rem;
|
||||
min-width: 0.5rem;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import { Fragment, type ReactNode } from "react";
|
||||
|
||||
import {
|
||||
Badge,
|
||||
type BadgeColor,
|
||||
type BadgeProps,
|
||||
Box,
|
||||
Group,
|
||||
Text,
|
||||
} from "metabase/ui";
|
||||
import {
|
||||
StoryJsx,
|
||||
StorySection,
|
||||
StoryShowcase,
|
||||
} from "metabase/ui/stories/showcase";
|
||||
|
||||
const VARIANTS = ["filled", "light", "outline"] as const;
|
||||
const COLORS: BadgeColor[] = [
|
||||
"neutral",
|
||||
"brand",
|
||||
"negative",
|
||||
"warning",
|
||||
"positive",
|
||||
];
|
||||
const SIZES = ["xs", "sm"] as const;
|
||||
|
||||
const args = {
|
||||
variant: "light",
|
||||
color: "neutral",
|
||||
size: "xs",
|
||||
children: "Badge",
|
||||
};
|
||||
|
||||
const argTypes = {
|
||||
variant: {
|
||||
options: VARIANTS,
|
||||
control: { type: "inline-radio" },
|
||||
},
|
||||
color: {
|
||||
options: COLORS,
|
||||
control: { type: "inline-radio" },
|
||||
},
|
||||
size: {
|
||||
options: SIZES,
|
||||
control: { type: "inline-radio" },
|
||||
},
|
||||
circle: {
|
||||
control: { type: "boolean" },
|
||||
},
|
||||
children: {
|
||||
control: { type: "text" },
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
title: "Components/Data display/Badge",
|
||||
component: Badge,
|
||||
args,
|
||||
argTypes,
|
||||
};
|
||||
|
||||
export const Default = {
|
||||
render: (args: BadgeProps) => <Badge {...args} />,
|
||||
};
|
||||
|
||||
const gridStyle = {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "24rem 4rem 4rem",
|
||||
columnGap: "1rem",
|
||||
rowGap: "0.75rem",
|
||||
alignItems: "center",
|
||||
} as const;
|
||||
|
||||
const SizeHeader = () => (
|
||||
<>
|
||||
<div />
|
||||
{SIZES.map((size) => (
|
||||
<Text key={size} size="sm" fw="bold" c="text-secondary">
|
||||
{size}
|
||||
</Text>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
const VariantSection = ({
|
||||
variant,
|
||||
children,
|
||||
}: {
|
||||
variant: (typeof VARIANTS)[number];
|
||||
children: ReactNode;
|
||||
}) => (
|
||||
<StorySection title={variant[0].toUpperCase() + variant.slice(1)}>
|
||||
<Box style={gridStyle}>
|
||||
{COLORS.map((color) => (
|
||||
<Fragment key={color}>
|
||||
<StoryJsx>{`<Badge variant="${variant}" color="${color}" />`}</StoryJsx>
|
||||
{SIZES.map((size) => (
|
||||
<Badge key={size} variant={variant} color={color} size={size}>
|
||||
{children}
|
||||
</Badge>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</Box>
|
||||
</StorySection>
|
||||
);
|
||||
|
||||
const Overview = ({ children }: BadgeProps) => (
|
||||
<StoryShowcase title="Badge">
|
||||
<StorySection title="Sizes" description="Note: the default size is “xs”.">
|
||||
<Box style={gridStyle}>
|
||||
<SizeHeader />
|
||||
<StoryJsx>{`<Badge size="…" />`}</StoryJsx>
|
||||
{SIZES.map((size) => (
|
||||
<Badge key={size} size={size}>
|
||||
{children}
|
||||
</Badge>
|
||||
))}
|
||||
</Box>
|
||||
</StorySection>
|
||||
|
||||
{VARIANTS.map((variant) => (
|
||||
<VariantSection key={variant} variant={variant}>
|
||||
{children}
|
||||
</VariantSection>
|
||||
))}
|
||||
|
||||
<StorySection title="Circle">
|
||||
<Box style={gridStyle}>
|
||||
<StoryJsx>{`<Badge variant="filled" color="neutral" circle />`}</StoryJsx>
|
||||
{SIZES.map((size) => (
|
||||
<Badge key={size} variant="filled" color="neutral" circle size={size}>
|
||||
1
|
||||
</Badge>
|
||||
))}
|
||||
</Box>
|
||||
</StorySection>
|
||||
|
||||
<StorySection title="Indicator">
|
||||
<Box style={{ ...gridStyle, gridTemplateColumns: "24rem 8rem" }}>
|
||||
<StoryJsx>{`<Badge indicator color="…" />`}</StoryJsx>
|
||||
<Group gap="sm">
|
||||
{COLORS.map((color) => (
|
||||
<Badge key={color} indicator color={color} />
|
||||
))}
|
||||
</Group>
|
||||
</Box>
|
||||
</StorySection>
|
||||
</StoryShowcase>
|
||||
);
|
||||
|
||||
export const SizesAndVariants = {
|
||||
render: Overview,
|
||||
name: "Sizes and variants",
|
||||
args: { theme: "light" },
|
||||
parameters: {
|
||||
controls: { include: ["theme", "children"] },
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
Badge as MantineBadge,
|
||||
type BadgeProps as MantineBadgeProps,
|
||||
} from "@mantine/core";
|
||||
import { type ComponentPropsWithoutRef, forwardRef } from "react";
|
||||
|
||||
import type { DerivedColorKey } from "metabase/ui/colors/types/theme";
|
||||
|
||||
export type BadgeColor =
|
||||
| "neutral"
|
||||
| "brand"
|
||||
| "negative"
|
||||
| "warning"
|
||||
| "positive";
|
||||
|
||||
export interface BadgeProps
|
||||
extends
|
||||
Omit<MantineBadgeProps, "color">,
|
||||
Omit<ComponentPropsWithoutRef<"div">, keyof MantineBadgeProps | "color"> {
|
||||
color?: BadgeColor | DerivedColorKey;
|
||||
/** Render as a small solid dot. Used as a status indicator. */
|
||||
indicator?: boolean;
|
||||
}
|
||||
|
||||
export const Badge = forwardRef<HTMLDivElement, BadgeProps>(function Badge(
|
||||
{
|
||||
color = "neutral",
|
||||
size = "xs",
|
||||
variant = "filled",
|
||||
indicator,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
return (
|
||||
<MantineBadge
|
||||
color={color as MantineBadgeProps["color"]}
|
||||
ref={ref}
|
||||
size={size}
|
||||
variant={variant}
|
||||
{...(indicator ? { "data-indicator": true } : {})}
|
||||
{...props}
|
||||
>
|
||||
{indicator ? null : children}
|
||||
</MantineBadge>
|
||||
);
|
||||
});
|
||||
@@ -1,2 +1,2 @@
|
||||
export { Badge } from "@mantine/core";
|
||||
export * from "./Badge.config";
|
||||
export { Badge, type BadgeColor, type BadgeProps } from "./Badge";
|
||||
export { badgeOverrides } from "./Badge.config";
|
||||
|
||||
@@ -35,16 +35,7 @@ const ChartSettingsWidgetList = ({
|
||||
return (
|
||||
<div key={`group-${groupIndex}`}>
|
||||
{group && (
|
||||
<Badge
|
||||
mb="lg"
|
||||
ml="lg"
|
||||
fz="0.75rem"
|
||||
px="0.5rem"
|
||||
tt="none"
|
||||
radius="xs"
|
||||
size="lg"
|
||||
c="text-primary"
|
||||
>
|
||||
<Badge color="brand" mb="lg" ml="lg" size="sm" variant="light">
|
||||
{group}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
@@ -132,7 +132,7 @@ export function PreviousValueComparison({
|
||||
</VariationDetails>
|
||||
|
||||
{showsOtherValuesInTooltip && (
|
||||
<Badge px="xs" size="xs" variant="light" w={ELLIPSIS_BADGE_WIDTH}>
|
||||
<Badge size="xs" variant="light" w={ELLIPSIS_BADGE_WIDTH}>
|
||||
<Group align="center" h="100%">
|
||||
<Icon name="ellipsis" size={12} />
|
||||
</Group>
|
||||
|
||||
@@ -16,6 +16,7 @@ module.exports = {
|
||||
"^Components/Documents",
|
||||
"^Components/Feedback/Alert",
|
||||
"^Components/Ask Before Using/Chip Overview",
|
||||
"^Components/Data display/Badge Sizes and variants",
|
||||
"^Components/Data display/KeyboardShortcut Overview",
|
||||
"^Components/Table",
|
||||
"^App/Palette",
|
||||
|
||||
Reference in New Issue
Block a user