Keep the template picker in the create flow, make catalog cards real links, and stop showing a fake community when review has no draft.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -35,6 +35,7 @@ const RuleContainer = memo<RuleProps>(
|
||||
backgroundColor = "bg-[var(--color-community-teal-100)]",
|
||||
className = "",
|
||||
onClick,
|
||||
href,
|
||||
expanded = false,
|
||||
size: sizeProp,
|
||||
categories,
|
||||
@@ -95,8 +96,9 @@ const RuleContainer = memo<RuleProps>(
|
||||
icon={icon}
|
||||
backgroundColor={backgroundColor}
|
||||
className={className}
|
||||
href={hasBottomLinks ? undefined : href}
|
||||
onClick={hasBottomLinks ? undefined : handleClick}
|
||||
onKeyDown={hasBottomLinks ? undefined : handleKeyDown}
|
||||
onKeyDown={hasBottomLinks || href ? undefined : handleKeyDown}
|
||||
expanded={expanded}
|
||||
size={size}
|
||||
categories={categories}
|
||||
|
||||
@@ -50,6 +50,11 @@ export interface RuleProps {
|
||||
backgroundColor?: string;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
/**
|
||||
* When set, the card is a real link (focusable, Enter, open in a new tab)
|
||||
* instead of a click-only `role="button"` surface.
|
||||
*/
|
||||
href?: string;
|
||||
expanded?: boolean;
|
||||
size?: RuleSizeValue;
|
||||
categories?: Category[];
|
||||
@@ -93,6 +98,7 @@ export interface RuleViewProps {
|
||||
backgroundColor: string;
|
||||
className: string;
|
||||
onClick?: () => void;
|
||||
href?: string;
|
||||
onKeyDown?: (_event: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
expanded: boolean;
|
||||
size: RuleSizeValue;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import NextLink from "next/link";
|
||||
import MultiSelect from "../../controls/MultiSelect";
|
||||
import InlineTextButton from "../../buttons/InlineTextButton";
|
||||
import NavigationLink from "../../navigation/Link";
|
||||
@@ -19,6 +20,7 @@ export function RuleView({
|
||||
backgroundColor,
|
||||
className,
|
||||
onClick,
|
||||
href,
|
||||
onKeyDown,
|
||||
expanded,
|
||||
size,
|
||||
@@ -264,16 +266,11 @@ export function RuleView({
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${backgroundColor} ${cardPadding} ${cardGap} ${borderRadiusClass} shadow-[0px_0px_48px_0px_rgba(0,0,0,0.1)] ${interactiveCard ? "hover:shadow-[0px_0px_64px_0px_rgba(0,0,0,0.15)] transition-shadow duration-200" : ""} flex flex-col items-start justify-center relative ${cardWidth || "w-full"} ${className || ""}`}
|
||||
tabIndex={interactiveCard ? 0 : undefined}
|
||||
role={interactiveCard ? "button" : "article"}
|
||||
aria-label={ariaLabel}
|
||||
aria-expanded={interactiveCard ? expanded : undefined}
|
||||
onClick={interactiveCard ? onClick : undefined}
|
||||
onKeyDown={interactiveCard ? onKeyDown : undefined}
|
||||
>
|
||||
const cardClassName = `${backgroundColor} ${cardPadding} ${cardGap} ${borderRadiusClass} shadow-[0px_0px_48px_0px_rgba(0,0,0,0.1)] ${interactiveCard ? "hover:shadow-[0px_0px_64px_0px_rgba(0,0,0,0.15)] transition-shadow duration-200" : ""} flex flex-col items-start justify-center relative ${cardWidth || "w-full"} ${href ? "no-underline text-inherit" : ""} ${className || ""}`;
|
||||
const isLinkCard = Boolean(href) && interactiveCard;
|
||||
|
||||
const cardBody = (
|
||||
<>
|
||||
{/* Figma: Header = `border-b` row, `gap-px`, icon `pl-1 pr-2 py-2` + `border-l` on title. */}
|
||||
<div
|
||||
className="
|
||||
@@ -483,6 +480,33 @@ export function RuleView({
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
if (isLinkCard && href) {
|
||||
return (
|
||||
<NextLink
|
||||
href={href}
|
||||
className={cardClassName}
|
||||
aria-label={ariaLabel}
|
||||
onClick={onClick}
|
||||
>
|
||||
{cardBody}
|
||||
</NextLink>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cardClassName}
|
||||
tabIndex={interactiveCard ? 0 : undefined}
|
||||
role={interactiveCard ? "button" : "article"}
|
||||
aria-label={ariaLabel}
|
||||
aria-expanded={interactiveCard ? expanded : undefined}
|
||||
onClick={interactiveCard ? onClick : undefined}
|
||||
onKeyDown={interactiveCard ? onKeyDown : undefined}
|
||||
>
|
||||
{cardBody}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,13 @@ import type { GovernanceTemplateCatalogEntry } from "../../../../lib/templates/g
|
||||
|
||||
export interface GovernanceTemplateGridProps {
|
||||
entries: GovernanceTemplateCatalogEntry[];
|
||||
onTemplateClick: (_slug: string) => void;
|
||||
/**
|
||||
* Real navigation target per card. Cards render as anchors so they are
|
||||
* keyboard-focusable and work in a new tab.
|
||||
*/
|
||||
hrefForTemplate: (_slug: string) => string;
|
||||
/** Optional side effects on activate (analytics, draft reset). */
|
||||
onTemplateClick?: (_slug: string) => void;
|
||||
/**
|
||||
* When true, use project **`md`** (640px) for a 2-column grid (e.g. `/use-cases`).
|
||||
* Default keeps the template shell break at **768px**.
|
||||
@@ -19,6 +25,7 @@ export interface GovernanceTemplateGridProps {
|
||||
|
||||
export function GovernanceTemplateGrid({
|
||||
entries,
|
||||
hrefForTemplate,
|
||||
onTemplateClick,
|
||||
twoColumnsFromMd = false,
|
||||
}: GovernanceTemplateGridProps) {
|
||||
@@ -105,9 +112,14 @@ export function GovernanceTemplateGrid({
|
||||
/>
|
||||
}
|
||||
backgroundColor={card.backgroundColor}
|
||||
onClick={() => {
|
||||
onTemplateClick(card.slug);
|
||||
}}
|
||||
href={hrefForTemplate(card.slug)}
|
||||
onClick={
|
||||
onTemplateClick
|
||||
? () => {
|
||||
onTemplateClick(card.slug);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
*/
|
||||
|
||||
import { memo, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTranslation } from "../../../contexts/MessagesContext";
|
||||
import { logger } from "../../../../lib/logger";
|
||||
import { prepareFreshCreateFlowEntrySync } from "../../../(app)/create/utils/prepareFreshCreateFlowEntry";
|
||||
import { buildTemplateReviewHref } from "../../../(app)/create/utils/flowSteps";
|
||||
import {
|
||||
fetchTemplates,
|
||||
isTemplatesFetchAborted,
|
||||
@@ -34,7 +34,6 @@ declare global {
|
||||
|
||||
const RuleStackContainer = memo<RuleStackProps>(
|
||||
({ className = "", initialGridEntries, translationNamespace, twoColumnsFromMd }) => {
|
||||
const router = useRouter();
|
||||
const namespace = translationNamespace ?? "pages.home.ruleStack";
|
||||
const t = useTranslation(namespace);
|
||||
const [gridEntries, setGridEntries] = useState<TemplateGridCardEntry[] | null>(
|
||||
@@ -101,12 +100,12 @@ const RuleStackContainer = memo<RuleStackProps>(
|
||||
// `signedIn: true` because this surface has no session in hand; DELETE is
|
||||
// best-effort and the sentinel blocks stale-draft hydration.
|
||||
prepareFreshCreateFlowEntrySync({ signedIn: true });
|
||||
router.push(`/create/review-template/${encodeURIComponent(slug)}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<RuleStackView
|
||||
className={className}
|
||||
hrefForTemplate={(slug) => buildTemplateReviewHref(slug)}
|
||||
onTemplateClick={handleTemplateClick}
|
||||
gridEntries={gridEntries}
|
||||
sectionTitle={t("title")}
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface RuleStackProps {
|
||||
export interface RuleStackViewProps {
|
||||
className: string;
|
||||
onTemplateClick: (_slug: string) => void;
|
||||
hrefForTemplate: (_slug: string) => string;
|
||||
/** `null` while loading curated templates from the API. */
|
||||
gridEntries: TemplateGridCardEntry[] | null;
|
||||
sectionTitle: string;
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { RuleStackViewProps } from "./RuleStack.types";
|
||||
export function RuleStackView({
|
||||
className,
|
||||
onTemplateClick,
|
||||
hrefForTemplate,
|
||||
gridEntries,
|
||||
sectionTitle,
|
||||
sectionSubtitle,
|
||||
@@ -46,6 +47,7 @@ export function RuleStackView({
|
||||
) : (
|
||||
<GovernanceTemplateGrid
|
||||
entries={gridEntries}
|
||||
hrefForTemplate={hrefForTemplate}
|
||||
onTemplateClick={onTemplateClick}
|
||||
twoColumnsFromMd={twoColumnsFromMd}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user