feat(cooking-session): déplie une recette de l'onglet Recettes au lieu de rediriger #28
6 changed files with 258 additions and 48 deletions
|
|
@ -186,6 +186,28 @@ function setupPlanFixture() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal `RecipeView` detail for one of `planFixture()`'s recipes — just
|
||||||
|
* enough (name/portions/one step) to prove the right recipe's own detail
|
||||||
|
* rendered inline, not a full catalog-style fixture.
|
||||||
|
*/
|
||||||
|
function recipeDetail(id: number, name: string, portions: number, stepDescription: string) {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description: null,
|
||||||
|
picture: null,
|
||||||
|
portions,
|
||||||
|
authorId: 1,
|
||||||
|
visibility: "PERSONAL",
|
||||||
|
allergens: [],
|
||||||
|
diets: [],
|
||||||
|
isFavorite: false,
|
||||||
|
ingredients: [],
|
||||||
|
steps: [{ id: 1, description: stepDescription, picture: null, order: 1, techSteps: [] }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("Cooking session page", () => {
|
describe("Cooking session page", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.viewport(1400, 900);
|
cy.viewport(1400, 900);
|
||||||
|
|
@ -287,25 +309,59 @@ describe("Cooking session page", () => {
|
||||||
cy.get(".cooking-stepper__node").first().should("have.class", "cooking-stepper__node--done");
|
cy.get(".cooking-stepper__node").first().should("have.class", "cooking-stepper__node--done");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("lists the week's recipes on the Recettes tab, each linking to its own page", () => {
|
it("lists the week's recipes on the Recettes tab, each showing its portions", () => {
|
||||||
cy.intercept("GET", /\/cooking-session\?/, { statusCode: 200, body: planFixture() });
|
cy.intercept("GET", /\/cooking-session\?/, { statusCode: 200, body: planFixture() });
|
||||||
cy.visit("/cuisiner?date=2026-08-17");
|
cy.visit("/cuisiner?date=2026-08-17");
|
||||||
|
|
||||||
cy.contains(".cooking-session-page__tab", "Recettes").click();
|
cy.contains(".cooking-session-page__tab", "Recettes").click();
|
||||||
|
|
||||||
cy.get(".cooking-recipes__item").should("have.length", 2);
|
cy.get(".cooking-recipes__item").should("have.length", 2);
|
||||||
cy.contains(".cooking-recipes__item", "Soupe à l'oignon")
|
cy.contains(".cooking-recipes__item", "Soupe à l'oignon").should("be.visible");
|
||||||
.find("a")
|
cy.contains(".cooking-recipes__item", "Tarte à l'oignon").should("contain.text", "6 portions");
|
||||||
.should("have.attr", "href", "/recettes/1");
|
|
||||||
cy.contains(".cooking-recipes__item", "Tarte à l'oignon")
|
|
||||||
.should("contain.text", "6 portions")
|
|
||||||
.find("a")
|
|
||||||
.should("have.attr", "href", "/recettes/2");
|
|
||||||
|
|
||||||
// The stepper isn't part of this tab.
|
// The stepper isn't part of this tab.
|
||||||
cy.get(".cooking-stepper").should("not.exist");
|
cy.get(".cooking-stepper").should("not.exist");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("expands a recipe's detail inline on the Recettes tab instead of navigating away", () => {
|
||||||
|
cy.intercept("GET", /\/cooking-session\?/, { statusCode: 200, body: planFixture() });
|
||||||
|
cy.intercept("GET", "**/recipes/1", {
|
||||||
|
statusCode: 200,
|
||||||
|
body: recipeDetail(1, "Soupe à l'oignon", 4, "Émincer les oignons."),
|
||||||
|
}).as("getSoupe");
|
||||||
|
cy.intercept("GET", "**/recipes/2", {
|
||||||
|
statusCode: 200,
|
||||||
|
body: recipeDetail(2, "Tarte à l'oignon", 6, "Étaler la pâte."),
|
||||||
|
}).as("getTarte");
|
||||||
|
cy.visit("/cuisiner?date=2026-08-17");
|
||||||
|
cy.contains(".cooking-session-page__tab", "Recettes").click();
|
||||||
|
|
||||||
|
// Nothing unfolded yet, and no more link out to the catalog page —
|
||||||
|
// clicking a row is what unfolds it now.
|
||||||
|
cy.get(".cooking-recipes").find("a").should("not.exist");
|
||||||
|
cy.get(".cooking-recipes__detail").should("not.exist");
|
||||||
|
|
||||||
|
cy.contains(".cooking-recipes__row", "Soupe à l'oignon").click();
|
||||||
|
cy.wait("@getSoupe");
|
||||||
|
cy.location("pathname").should("eq", "/cuisiner"); // no navigation happened
|
||||||
|
cy.get(".cooking-recipes__detail .recipe-detail-panel").within(() => {
|
||||||
|
cy.contains("h2", "Soupe à l'oignon").should("be.visible");
|
||||||
|
cy.contains("Émincer les oignons.").should("be.visible");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only one recipe unfolds at a time — opening Tarte's closes Soupe's.
|
||||||
|
cy.contains(".cooking-recipes__row", "Tarte à l'oignon").click();
|
||||||
|
cy.wait("@getTarte");
|
||||||
|
cy.get(".cooking-recipes__detail").should("have.length", 1);
|
||||||
|
cy.get(".cooking-recipes__detail .recipe-detail-panel")
|
||||||
|
.should("contain.text", "Étaler la pâte.")
|
||||||
|
.and("not.contain.text", "Émincer");
|
||||||
|
|
||||||
|
// Clicking the open one again collapses it back.
|
||||||
|
cy.contains(".cooking-recipes__row", "Tarte à l'oignon").click();
|
||||||
|
cy.get(".cooking-recipes__detail").should("not.exist");
|
||||||
|
});
|
||||||
|
|
||||||
it("renders a merged-setup preheat with its temperature and the oven-conflict note", () => {
|
it("renders a merged-setup preheat with its temperature and the oven-conflict note", () => {
|
||||||
cy.intercept("GET", /\/cooking-session\?/, { statusCode: 200, body: setupPlanFixture() });
|
cy.intercept("GET", /\/cooking-session\?/, { statusCode: 200, body: setupPlanFixture() });
|
||||||
cy.visit("/cuisiner?date=2026-08-17");
|
cy.visit("/cuisiner?date=2026-08-17");
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,16 @@ Feature: Start cooking an optimized plan
|
||||||
When I visit "/cuisiner?date=2026-08-17"
|
When I visit "/cuisiner?date=2026-08-17"
|
||||||
And I open the "Recettes" cooking tab
|
And I open the "Recettes" cooking tab
|
||||||
Then I should see "Soupe à l'oignon"
|
Then I should see "Soupe à l'oignon"
|
||||||
And the recipe "Soupe à l'oignon" should link to "/recettes/1"
|
|
||||||
And the recipe "Tarte à l'oignon" should link to "/recettes/2"
|
Scenario: Clicking a recipe on the Recettes tab unfolds it instead of navigating away
|
||||||
|
Given the planning for this week has recipes "Soupe à l'oignon" and "Tarte à l'oignon"
|
||||||
|
And the cooking plan for "2026-08-17" has a mise en place then a cooking step
|
||||||
|
And recipe 1's detail is available
|
||||||
|
When I visit "/cuisiner?date=2026-08-17"
|
||||||
|
And I open the "Recettes" cooking tab
|
||||||
|
And I click the recipe "Soupe à l'oignon"
|
||||||
|
Then the recipe detail "Soupe à l'oignon" should be expanded
|
||||||
|
And the URL should include "/cuisiner"
|
||||||
|
|
||||||
Scenario: Two recipes preheat the oven to different temperatures
|
Scenario: Two recipes preheat the oven to different temperatures
|
||||||
Given the planning for this week has recipes "Gratin" and "Rôti"
|
Given the planning for this week has recipes "Gratin" and "Rôti"
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,39 @@ Then("the current cooking step should be {string}", (position: string) => {
|
||||||
cy.contains(".cooking-plan__position", position).should("be.visible");
|
cy.contains(".cooking-plan__position", position).should("be.visible");
|
||||||
});
|
});
|
||||||
|
|
||||||
Then("the recipe {string} should link to {string}", (name: string, href: string) => {
|
Given("recipe 1's detail is available", () => {
|
||||||
cy.contains(".cooking-recipes__item", name).find("a").should("have.attr", "href", href);
|
// Minimal `RecipeView` — just enough (name + one step) to prove the
|
||||||
|
// right recipe's own detail unfolded inline.
|
||||||
|
cy.intercept("GET", "**/recipes/1", {
|
||||||
|
statusCode: 200,
|
||||||
|
body: {
|
||||||
|
id: 1,
|
||||||
|
name: "Soupe à l'oignon",
|
||||||
|
description: null,
|
||||||
|
picture: null,
|
||||||
|
portions: 4,
|
||||||
|
authorId: 1,
|
||||||
|
visibility: "PERSONAL",
|
||||||
|
allergens: [],
|
||||||
|
diets: [],
|
||||||
|
isFavorite: false,
|
||||||
|
ingredients: [],
|
||||||
|
steps: [
|
||||||
|
{ id: 1, description: "Émincer les oignons.", picture: null, order: 1, techSteps: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// The task card is a `<label>` wrapping a visually-hidden checkbox — see
|
||||||
|
// "I tick the cooking task" above; a recipe row is a plain `<button>`,
|
||||||
|
// clicked the same straightforward way.
|
||||||
|
When("I click the recipe {string}", (name: string) => {
|
||||||
|
cy.contains(".cooking-recipes__row", name).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
Then("the recipe detail {string} should be expanded", (name: string) => {
|
||||||
|
cy.get(".cooking-recipes__detail .recipe-detail-panel").within(() => {
|
||||||
|
cy.contains("h2", name).should("be.visible");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -361,8 +361,7 @@
|
||||||
"sharedBadge": "Mutualisé"
|
"sharedBadge": "Mutualisé"
|
||||||
},
|
},
|
||||||
"recipesTab": {
|
"recipesTab": {
|
||||||
"portions": "{{n}} portions",
|
"portions": "{{n}} portions"
|
||||||
"viewRecipe": "Voir la recette"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,22 @@
|
||||||
import { DateTime, formatDateOnly, getWeekStart, parseDateOnly } from "@batch-cooking/date-tools";
|
import { DateTime, formatDateOnly, getWeekStart, parseDateOnly } from "@batch-cooking/date-tools";
|
||||||
import type {
|
import {
|
||||||
CookingBackgroundTaskView,
|
type CookingBackgroundTaskView,
|
||||||
CookingPhaseView,
|
type CookingPhaseView,
|
||||||
CookingSessionRecipeRef,
|
type CookingSessionRecipeRef,
|
||||||
CookingTaskView,
|
type CookingTaskView,
|
||||||
OptimizedCookingPlanView,
|
ErrorCode,
|
||||||
|
type OptimizedCookingPlanView,
|
||||||
} from "@batch-cooking/shared";
|
} from "@batch-cooking/shared";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import { apiClient } from "../../api/client";
|
import { ApiError, apiClient } from "../../api/client";
|
||||||
import { CheckboxOption } from "../../components/ui/Checkbox";
|
import { CheckboxOption } from "../../components/ui/Checkbox";
|
||||||
import { WeekNavigator } from "../../features/planning/WeekNavigator";
|
import { WeekNavigator } from "../../features/planning/WeekNavigator";
|
||||||
|
import {
|
||||||
|
RecipeDetailPanel,
|
||||||
|
type RecipeDetailState,
|
||||||
|
} from "../../features/recipes/RecipeDetailPanel";
|
||||||
import { cookingProgress } from "./cooking-progress";
|
import { cookingProgress } from "./cooking-progress";
|
||||||
import {
|
import {
|
||||||
groupTasksByRecipes,
|
groupTasksByRecipes,
|
||||||
|
|
@ -33,7 +38,8 @@ type CookingSessionState =
|
||||||
* Which panel of the cooking view is showing:
|
* Which panel of the cooking view is showing:
|
||||||
* - `"plan"` (default) — the step-by-step cooking plan, one phase at a time.
|
* - `"plan"` (default) — the step-by-step cooking plan, one phase at a time.
|
||||||
* - `"recipes"` — the flat list of the week's planned recipes, for a quick
|
* - `"recipes"` — the flat list of the week's planned recipes, for a quick
|
||||||
* "what am I even making" glance / a jump to a recipe's own page.
|
* "what am I even making" glance — each one expands inline on click (see
|
||||||
|
* `RecipesTab`) rather than navigating to its own page.
|
||||||
*/
|
*/
|
||||||
type CookingTab = "plan" | "recipes";
|
type CookingTab = "plan" | "recipes";
|
||||||
|
|
||||||
|
|
@ -125,15 +131,63 @@ export function CookingSessionPage() {
|
||||||
// from localStorage and resets the stepper to its first phase.
|
// from localStorage and resets the stepper to its first phase.
|
||||||
<PlanTab key={weekKey} plan={state.plan} weekKey={weekKey} />
|
<PlanTab key={weekKey} plan={state.plan} weekKey={weekKey} />
|
||||||
) : (
|
) : (
|
||||||
<RecipesTab recipes={state.plan.recipes} />
|
// Keyed by week too, so switching weeks collapses whichever
|
||||||
|
// recipe was unfolded rather than leaving a now-unrelated detail
|
||||||
|
// panel open.
|
||||||
|
<RecipesTab key={weekKey} recipes={state.plan.recipes} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The "Recettes" tab — the week's planned recipes as a flat list, each linking to its own page. */
|
/**
|
||||||
|
* The "Recettes" tab — the week's planned recipes as a flat list. Clicking
|
||||||
|
* one no longer navigates to `/recettes/:id`: it unfolds that recipe's full
|
||||||
|
* detail inline (fetched on demand, same {@link RecipeDetailPanel} as the
|
||||||
|
* catalog's own master-detail view, `showActions` off since managing the
|
||||||
|
* recipe isn't the point of a quick glance mid-cooking) — same
|
||||||
|
* one-open-at-a-time behavior as the sidebar's own collapsible sections
|
||||||
|
* (`AppLayout`'s `SettingsMenu`).
|
||||||
|
*/
|
||||||
function RecipesTab({ recipes }: { recipes: readonly CookingSessionRecipeRef[] }) {
|
function RecipesTab({ recipes }: { recipes: readonly CookingSessionRecipeRef[] }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
// Which recipe (by id) is currently unfolded — `null` when none is.
|
||||||
|
// Single value rather than a Set: only one recipe's detail shows at a
|
||||||
|
// time, collapsing whichever was open before.
|
||||||
|
const [expandedId, setExpandedId] = useState<number | null>(null);
|
||||||
|
const [detailState, setDetailState] = useState<RecipeDetailState>({ status: "empty" });
|
||||||
|
|
||||||
|
// Mirrors `RecipesPage`'s own fetch-on-select effect: fires on every
|
||||||
|
// `expandedId` change (including re-expanding a recipe already seen this
|
||||||
|
// visit — no cache, same recompute-over-cache stance as the rest of this
|
||||||
|
// page) and ignores a response that arrives after the viewer has already
|
||||||
|
// moved on (`cancelled`).
|
||||||
|
useEffect(() => {
|
||||||
|
if (expandedId === null) {
|
||||||
|
setDetailState({ status: "empty" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let cancelled = false;
|
||||||
|
setDetailState({ status: "loading" });
|
||||||
|
|
||||||
|
apiClient
|
||||||
|
.getRecipe(expandedId)
|
||||||
|
.then((recipe) => {
|
||||||
|
if (!cancelled) setDetailState({ status: "loaded", recipe });
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (cancelled) return;
|
||||||
|
if (err instanceof ApiError && err.code === ErrorCode.RECIPE_NOT_FOUND) {
|
||||||
|
setDetailState({ status: "not-found" });
|
||||||
|
} else {
|
||||||
|
setDetailState({ status: "error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [expandedId]);
|
||||||
|
|
||||||
if (recipes.length === 0) {
|
if (recipes.length === 0) {
|
||||||
return <p className="cooking-session-page__status">{t("cookingSession.empty")}</p>;
|
return <p className="cooking-session-page__status">{t("cookingSession.empty")}</p>;
|
||||||
|
|
@ -141,17 +195,33 @@ function RecipesTab({ recipes }: { recipes: readonly CookingSessionRecipeRef[] }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="cooking-recipes" aria-label={t("cookingSession.recipesLegend")}>
|
<ul className="cooking-recipes" aria-label={t("cookingSession.recipesLegend")}>
|
||||||
{recipes.map((recipe) => (
|
{recipes.map((recipe) => {
|
||||||
|
const isExpanded = expandedId === recipe.recipeId;
|
||||||
|
return (
|
||||||
<li key={`${recipe.recipeId}-${recipe.portions}`} className="cooking-recipes__item">
|
<li key={`${recipe.recipeId}-${recipe.portions}`} className="cooking-recipes__item">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="cooking-recipes__row"
|
||||||
|
aria-expanded={isExpanded}
|
||||||
|
onClick={() => setExpandedId(isExpanded ? null : recipe.recipeId)}
|
||||||
|
>
|
||||||
<span className="cooking-recipes__name">{recipe.name}</span>
|
<span className="cooking-recipes__name">{recipe.name}</span>
|
||||||
<span className="cooking-recipes__portions">
|
<span className="cooking-recipes__portions">
|
||||||
{t("cookingSession.recipesTab.portions", { n: recipe.portions })}
|
{t("cookingSession.recipesTab.portions", { n: recipe.portions })}
|
||||||
</span>
|
</span>
|
||||||
<Link className="cooking-recipes__link" to={`/recettes/${recipe.recipeId}`}>
|
<span className="cooking-recipes__chevron" aria-hidden="true">
|
||||||
{t("cookingSession.recipesTab.viewRecipe")}
|
{isExpanded ? "▾" : "▸"}
|
||||||
</Link>
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{isExpanded && (
|
||||||
|
<div className="cooking-recipes__detail">
|
||||||
|
<RecipeDetailPanel state={detailState} showActions={false} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- "Recettes" tab ---------------------------------------------------------
|
// --- "Recettes" tab ---------------------------------------------------------
|
||||||
// The week's planned recipes as a flat list — name, its own portion count,
|
// The week's planned recipes as a flat list — name and its own portion
|
||||||
// and a jump to the recipe's page. Deliberately thin: `plan.recipes` only
|
// count on an always-visible summary row; clicking one unfolds its full
|
||||||
// carries `{recipeId, name, portions}`.
|
// detail inline underneath (fetched on demand, see `RecipesTab`/`&__detail`
|
||||||
|
// below) instead of navigating to the recipe's own page.
|
||||||
.cooking-recipes {
|
.cooking-recipes {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|
@ -100,14 +101,37 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-xs);
|
gap: var(--space-xs);
|
||||||
|
|
||||||
|
// The card: background/radius/shadow live here (not on `&__row` below) so
|
||||||
|
// the summary row and, once unfolded, the detail panel read as one block
|
||||||
|
// instead of two stacked cards.
|
||||||
&__item {
|
&__item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
// Keeps the detail panel's own edges (see `&__detail` below) inside the
|
||||||
|
// card's rounded corners instead of poking past them.
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The always-visible clickable summary — a real `<button>` (not a `<div
|
||||||
|
// onClick>`) so the whole row is a proper keyboard/click target for the
|
||||||
|
// expand/collapse toggle (`RecipesTab`), styled to read exactly like the
|
||||||
|
// plain flex row it replaces.
|
||||||
|
&__row {
|
||||||
|
appearance: none;
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
padding: var(--space-sm) var(--space-md);
|
padding: var(--space-sm) var(--space-md);
|
||||||
background: var(--color-surface);
|
border: none;
|
||||||
border-radius: var(--radius-md);
|
background: none;
|
||||||
box-shadow: var(--shadow-sm);
|
font: inherit;
|
||||||
|
text-align: start;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
|
|
@ -124,14 +148,34 @@
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pushed to the trailing edge — a quiet secondary action, not the point
|
// Pushed to the trailing edge — a quiet affordance hinting the row
|
||||||
// of the row.
|
// unfolds, not an action of its own (the whole row is already clickable).
|
||||||
&__link {
|
&__chevron {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: var(--font-size-sm);
|
color: var(--color-text-muted);
|
||||||
font-weight: 600;
|
}
|
||||||
color: var(--color-primary);
|
|
||||||
|
// The unfolded recipe — reuses `.recipe-detail-panel` wholesale (see
|
||||||
|
// recipes.scss) rather than re-implementing a second "read a recipe"
|
||||||
|
// layout, just inset under the summary row with a separating rule.
|
||||||
|
&__detail {
|
||||||
|
padding: 0 var(--space-md) var(--space-md);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
|
||||||
|
.recipe-detail-panel {
|
||||||
|
// The shared panel is styled for a standalone side panel (its own
|
||||||
|
// card background/shadow, fills 100% of a sized flex parent, scrolls
|
||||||
|
// internally) — already sitting inside this card and in normal
|
||||||
|
// document flow here, so neutralize all of that rather than double
|
||||||
|
// up on card chrome or clip content to a height nothing constrains.
|
||||||
|
height: auto;
|
||||||
|
overflow-y: visible;
|
||||||
|
padding-top: var(--space-md);
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue