From c1a079ec498a2f9973941bd2a6f3974c4f21ff39 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Thu, 2 Jul 2026 10:53:46 -0300 Subject: [PATCH 1/4] feat(base): let SelectableCard host arbitrary content Add an optional content slot (children), make selected optional, and pass through className, so SelectableCard can back a navigational card and not just a selection tile. Additive: existing consumers are unaffected. Co-Authored-By: Claude Opus 4.8 --- .../base/SelectableCard/SelectableCard.scss | 2 ++ .../base/SelectableCard/SelectableCard.tsx | 22 +++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/frontend/web/components/base/SelectableCard/SelectableCard.scss b/frontend/web/components/base/SelectableCard/SelectableCard.scss index 0315b3f4a852..72eae4590544 100644 --- a/frontend/web/components/base/SelectableCard/SelectableCard.scss +++ b/frontend/web/components/base/SelectableCard/SelectableCard.scss @@ -36,6 +36,8 @@ display: flex; flex-direction: column; gap: 4px; + flex: 1; + min-width: 0; } &__icon { diff --git a/frontend/web/components/base/SelectableCard/SelectableCard.tsx b/frontend/web/components/base/SelectableCard/SelectableCard.tsx index 3a614f228702..a88d42577312 100644 --- a/frontend/web/components/base/SelectableCard/SelectableCard.tsx +++ b/frontend/web/components/base/SelectableCard/SelectableCard.tsx @@ -1,11 +1,14 @@ import React, { FC, ReactNode } from 'react' +import cn from 'classnames' import BareButton from 'components/base/forms/BareButton' import './SelectableCard.scss' type BadgeVariant = 'primary' | 'secondary' type SelectableCardProps = { - selected: boolean + // Selection state. Omit for a card that acts on click without a chosen state + // (e.g. a card that navigates). + selected?: boolean onClick: () => void icon?: ReactNode title: string @@ -13,10 +16,15 @@ type SelectableCardProps = { badge?: { label: string; variant: BadgeVariant } tags?: string[] disabled?: boolean + className?: string + // Extra content below the description (e.g. an illustrative preview). + children?: ReactNode } const SelectableCard: FC = ({ badge, + children, + className, description, disabled, icon, @@ -27,9 +35,14 @@ const SelectableCard: FC = ({ }) => { return ( @@ -46,6 +59,7 @@ const SelectableCard: FC = ({ ))} )} + {children} {badge && (
From 4581b155748597422b1d91c50137cd14aceae667 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Thu, 2 Jul 2026 10:54:00 -0300 Subject: [PATCH 2/4] feat(onboarding): make the "next quest" cards actionable The three next-quest cards (gradual rollout, experiment, remote config) are built on SelectableCard and each deep-links to the flag's real config: rollout to the segment overrides tab, remote config to the value tab, experiment to the experiments page. The tab param is the slugified tab label. Locked and dimmed (inert) until the app connects. Closes #7739 Co-Authored-By: Claude Opus 4.8 --- .../OnboardingNextSteps.stories.tsx | 30 +++++ .../OnboardingFlow/OnboardingFlow.tsx | 26 ++++ .../OnboardingNextSteps.scss | 90 +++++++++++++ .../OnboardingNextSteps.tsx | 120 ++++++++++++++++++ .../onboarding/OnboardingNextSteps/index.ts | 5 + .../onboarding/hooks/useOnboardingFlag.ts | 2 + 6 files changed, 273 insertions(+) create mode 100644 frontend/documentation/pages/onboarding/OnboardingNextSteps.stories.tsx create mode 100644 frontend/web/components/pages/onboarding/OnboardingNextSteps/OnboardingNextSteps.scss create mode 100644 frontend/web/components/pages/onboarding/OnboardingNextSteps/OnboardingNextSteps.tsx create mode 100644 frontend/web/components/pages/onboarding/OnboardingNextSteps/index.ts diff --git a/frontend/documentation/pages/onboarding/OnboardingNextSteps.stories.tsx b/frontend/documentation/pages/onboarding/OnboardingNextSteps.stories.tsx new file mode 100644 index 000000000000..bbbf3e0802fa --- /dev/null +++ b/frontend/documentation/pages/onboarding/OnboardingNextSteps.stories.tsx @@ -0,0 +1,30 @@ +import type { Meta, StoryObj } from 'storybook' + +import OnboardingNextSteps from 'components/pages/onboarding/OnboardingNextSteps' + +const meta: Meta = { + args: { + locked: false, + onSelect: () => {}, + }, + component: OnboardingNextSteps, + parameters: { + docs: { + description: { + component: + 'The "Choose your next quest" section at the bottom of the onboarding flow: the three ways the demo flag can level up (gradual rollout, experiment, remote config), each linking to its real config. Locked and dimmed until the app connects.', + }, + }, + layout: 'padded', + }, + title: 'Pages/Onboarding/OnboardingNextSteps', +} +export default meta + +type Story = StoryObj + +export const Connected: Story = {} + +export const Locked: Story = { + args: { locked: true }, +} diff --git a/frontend/web/components/pages/onboarding/OnboardingFlow/OnboardingFlow.tsx b/frontend/web/components/pages/onboarding/OnboardingFlow/OnboardingFlow.tsx index b3dcecf573bc..ad32dc2ede47 100644 --- a/frontend/web/components/pages/onboarding/OnboardingFlow/OnboardingFlow.tsx +++ b/frontend/web/components/pages/onboarding/OnboardingFlow/OnboardingFlow.tsx @@ -7,6 +7,9 @@ import ThemeToggle from 'components/pages/onboarding/ThemeToggle' import OnboardingConnectPanel from 'components/pages/onboarding/OnboardingConnectPanel' import OnboardingTerminal from 'components/pages/onboarding/OnboardingTerminal' import OnboardingFlagsTable from 'components/pages/onboarding/OnboardingFlagsTable' +import OnboardingNextSteps, { + OnboardingNextStep, +} from 'components/pages/onboarding/OnboardingNextSteps' import { useEnsureOnboardingResources } from 'components/pages/onboarding/hooks/useEnsureOnboardingResources' import { useOnboardingFlagRename } from 'components/pages/onboarding/hooks/useOnboardingFlagRename' import { useOnboardingFlag } from 'components/pages/onboarding/hooks/useOnboardingFlag' @@ -64,6 +67,7 @@ const OnboardingFlow: FC = () => { const [snippetCopied, setSnippetCopied] = useState(false) const { enabled: flagEnabled, + flagId, isToggling, ready: flagStateReady, tags: flagTags, @@ -117,6 +121,24 @@ const OnboardingFlow: FC = () => { } } + // Each next-step card deep-links to the flag's real config; nothing faked. + const goToNextStep = (step: OnboardingNextStep) => { + if (projectId === null) { + return + } + const base = `/project/${projectId}/environment/${environmentKey}` + if (step === 'experiment') { + history.push(`${base}/experiments`) + return + } + if (flagId === null) { + return + } + // Tab param is the slugified tab label (see TabMenu/Tabs urlParam). + const tab = step === 'rollout' ? 'segment-overrides' : 'value' + history.push(`${base}/features?feature=${flagId}&tab=${tab}`) + } + if (status === 'creating') { return (
@@ -178,6 +200,10 @@ const OnboardingFlow: FC = () => { togglingFlag={isToggling ? featureName : null} togglesReady={flagStateReady} /> +