New edit-rule page created

This commit is contained in:
adilallo
2026-04-29 16:05:37 -06:00
parent ac1157a172
commit 3a9727bceb
25 changed files with 875 additions and 52 deletions
@@ -19,6 +19,9 @@ export interface InlineTextButtonProps {
disabled?: boolean;
ariaLabel?: string;
type?: "button" | "submit" | "reset";
/** When set, removes the default underline (e.g. inverse surfaces). */
underline?: boolean;
"data-testid"?: string;
}
/**
@@ -37,9 +40,16 @@ function InlineTextButtonComponent({
disabled = false,
ariaLabel,
type = "button",
underline = true,
"data-testid": dataTestId,
}: InlineTextButtonProps) {
const baseClasses =
"cursor-pointer border-none bg-transparent p-0 font-inter font-normal text-[length:inherit] leading-[inherit] text-[color:var(--color-content-default-tertiary,#b4b4b4)] underline decoration-solid underline-offset-[3px] hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-border-invert-primary)] disabled:cursor-not-allowed disabled:opacity-60";
const baseClasses = [
"cursor-pointer border-none bg-transparent p-0",
underline
? "font-inter font-normal text-[length:inherit] leading-[inherit] text-[color:var(--color-content-default-tertiary,#b4b4b4)] underline decoration-solid underline-offset-[3px]"
: "text-[length:inherit] leading-[inherit] text-[color:inherit] no-underline",
"hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-border-invert-primary)] disabled:cursor-not-allowed disabled:opacity-60",
].join(" ");
return (
<button
@@ -47,6 +57,7 @@ function InlineTextButtonComponent({
onClick={onClick}
disabled={disabled}
aria-label={ariaLabel}
data-testid={dataTestId}
className={`${baseClasses} ${className}`.trim()}
>
{children}
@@ -25,6 +25,9 @@ const RuleContainer = memo<RuleProps>(
({
title,
description,
onDescriptionClick,
descriptionEmptyHint,
descriptionEditAriaLabel,
icon,
backgroundColor = "bg-[var(--color-community-teal-100)]",
className = "",
@@ -75,6 +78,9 @@ const RuleContainer = memo<RuleProps>(
<RuleView
title={title}
description={description}
onDescriptionClick={onDescriptionClick}
descriptionEmptyHint={descriptionEmptyHint}
descriptionEditAriaLabel={descriptionEditAriaLabel}
icon={icon}
backgroundColor={backgroundColor}
className={className}
+15
View File
@@ -25,6 +25,18 @@ export interface RuleBottomLink {
export interface RuleProps {
title: string;
description?: string;
/**
* When set, the description row (or {@link descriptionEmptyHint} when there
* is no body text) is clickable — caller handles modal / navigation.
*/
onDescriptionClick?: () => void;
/**
* When {@link onDescriptionClick} is set, forwarded to the controls
* `aria-label` (keyboard / SR).
*/
descriptionEditAriaLabel?: string;
/** Shown when {@link onDescriptionClick} is set and `description` is empty. */
descriptionEmptyHint?: string;
icon?: React.ReactNode;
backgroundColor?: string;
className?: string;
@@ -51,6 +63,9 @@ export interface RuleProps {
export interface RuleViewProps {
title: string;
description?: string;
onDescriptionClick?: () => void;
descriptionEmptyHint?: string;
descriptionEditAriaLabel?: string;
icon?: React.ReactNode;
backgroundColor: string;
className: string;
+39 -10
View File
@@ -3,12 +3,16 @@
import Image from "next/image";
import { useTranslation } from "../../../contexts/MessagesContext";
import MultiSelect from "../../controls/MultiSelect";
import InlineTextButton from "../../buttons/InlineTextButton";
import NavigationLink from "../../navigation/Link";
import type { RuleBottomLink, RuleViewProps } from "./Rule.types";
export function RuleView({
title,
description,
onDescriptionClick,
descriptionEmptyHint,
descriptionEditAriaLabel,
icon,
backgroundColor,
className,
@@ -314,6 +318,41 @@ export function RuleView({
</div>
) : expanded ? (
<>
{(description ||
(onDescriptionClick &&
typeof descriptionEmptyHint === "string")) && (
<div
className={`relative w-full shrink-0 border-b border-solid border-[var(--color-content-invert-primary)] pb-[16px] ${
expanded && (isLarge || isMedium) ? "px-0" : "px-[12px]"
}`}
>
{onDescriptionClick ? (
<InlineTextButton
type="button"
underline={false}
data-testid="rule-description-edit"
ariaLabel={descriptionEditAriaLabel}
className={`${descriptionClass} w-full min-w-0 cursor-pointer whitespace-pre-wrap text-left text-[var(--color-content-invert-primary)] hover:!opacity-100 ${
!description && descriptionEmptyHint ? "opacity-70" : ""
}`.trim()}
onClick={(e) => {
e.stopPropagation();
onDescriptionClick();
}}
>
{description ?? descriptionEmptyHint ?? ""}
</InlineTextButton>
) : (
description && (
<p
className={`${descriptionClass} cursor-inherit text-[var(--color-content-invert-primary)]`}
>
{description}
</p>
)
)}
</div>
)}
{/* Categories Section - Using MultiSelect */}
{categories && categories.length > 0 && (
<div
@@ -352,16 +391,6 @@ export function RuleView({
))}
</div>
)}
{/* Footer: Description */}
{description && (
<div className="border-t border-solid border-[var(--color-content-invert-primary)] pt-[16px] relative shrink-0 w-full">
<p
className={`${descriptionClass} cursor-inherit text-[var(--color-content-invert-primary)]`}
>
{description}
</p>
</div>
)}
</>
) : (
/* Collapsed State: Description */