Web: sidebar avec icônes + repli/déploiement
Design validé via une maquette HTML itérée avec l'utilisateur (voir historique de conversation) avant implémentation. - nav-icons.tsx: jeu d'icônes trait fin (24x24, inline SVG) pour les 3 items de nav principaux, les 4 pages de Paramètres et le chevron de repli — pas de librairie d'icônes pour une poignée de glyphes - AppLayout: bouton replier/déplier à côté du logo — la sidebar passe de 15rem à un rail 4.25rem icônes-seules ; un seul toggle de classe CSS sur le <aside> pilote tout (aucun sous-composant n'a besoin de savoir que la sidebar est repliée), état persisté en localStorage - Logo : monogramme "bC" en repli plutôt qu'une icône arbitraire à la place du nom complet - Avatar rond (initiale) dans le menu compte, popover recalée en largeur fixe quand la sidebar est repliée (sinon écrasée à 4.25rem) - title="" sur chaque item pour l'infobulle native une fois replié
This commit is contained in:
parent
a8d897dfab
commit
73bfcd8fac
4 changed files with 350 additions and 25 deletions
|
|
@ -15,7 +15,9 @@
|
|||
|
||||
// Fixed-width nav rail. Its own surface (not the page background), same
|
||||
// elevation language as a card, so it reads as a distinct, permanent piece
|
||||
// of chrome rather than part of the scrolling content.
|
||||
// of chrome rather than part of the scrolling content. `width` transitions
|
||||
// so collapsing/expanding (see `.collapsed` below) reads as a deliberate
|
||||
// motion rather than an abrupt layout jump.
|
||||
.app-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -24,13 +26,55 @@
|
|||
padding: var(--space-lg) var(--space-md);
|
||||
background: var(--color-surface);
|
||||
border-right: 1px solid var(--color-border);
|
||||
transition: width 0.15s ease;
|
||||
|
||||
&__top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--space-sm) var(--space-xl);
|
||||
}
|
||||
|
||||
&__brand {
|
||||
padding: 0 var(--space-sm) var(--space-xl);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
font-size: var(--font-size-lg);
|
||||
color: var(--color-primary);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// Full wordmark by default; swapped for a short monogram when collapsed
|
||||
// (see `.collapsed &__brand-full`/`&__brand-mark` below) — clearer than
|
||||
// an arbitrary icon standing in for the brand itself.
|
||||
&__brand-mark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__collapse-toggle {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
flex-shrink: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-base);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text-muted);
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-surface-alt);
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
|
||||
&__nav {
|
||||
|
|
@ -40,12 +84,23 @@
|
|||
gap: var(--space-xs);
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-sm);
|
||||
border-radius: var(--radius-base);
|
||||
color: var(--color-text);
|
||||
font-weight: 600;
|
||||
font-size: var(--font-size-sm);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-surface-alt);
|
||||
|
|
@ -87,6 +142,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
&__settings-toggle-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
overflow: hidden;
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__settings-nav {
|
||||
margin-top: var(--space-xs);
|
||||
}
|
||||
|
|
@ -99,8 +167,11 @@
|
|||
}
|
||||
|
||||
&__account-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
width: 100%;
|
||||
padding: 0.5rem var(--space-sm);
|
||||
padding: 0.4rem var(--space-sm);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
|
|
@ -110,12 +181,27 @@
|
|||
border: 1px solid var(--color-border);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-surface-alt);
|
||||
}
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
flex-shrink: 0;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
// Anchored just above the toggle rather than inline in the flow — it's a
|
||||
// transient overlay, not part of the sidebar's permanent layout.
|
||||
&__account-menu {
|
||||
|
|
@ -143,12 +229,68 @@
|
|||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-surface-alt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Collapsed (icon-only rail) state -----------------------------------
|
||||
// A single class toggle on the root element — every nested rule below
|
||||
// just hides labels/chevrons and re-centers icons via CSS, no child
|
||||
// component needs to know the sidebar is collapsed.
|
||||
&.collapsed {
|
||||
width: 4.25rem;
|
||||
padding-left: var(--space-sm);
|
||||
padding-right: var(--space-sm);
|
||||
|
||||
.app-sidebar__top {
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.app-sidebar__brand-full {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-sidebar__brand-mark {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.app-sidebar__collapse-toggle svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.app-sidebar__nav a,
|
||||
.app-sidebar__settings-toggle,
|
||||
.app-sidebar__account-toggle {
|
||||
justify-content: center;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.app-sidebar__settings-toggle-left {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.label,
|
||||
.chevron {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// The popover would otherwise shrink to the icon rail's own width,
|
||||
// squashing "Mon compte"/"Se déconnecter" — give it a normal,
|
||||
// comfortable width instead, still anchored to the rail's left edge.
|
||||
.app-sidebar__account-menu {
|
||||
left: 0;
|
||||
right: auto;
|
||||
width: 12rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.app-content {
|
||||
|
|
@ -176,10 +318,14 @@
|
|||
border-right: none;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
|
||||
&__brand {
|
||||
&__top {
|
||||
padding: 0 var(--space-sm) 0 0;
|
||||
}
|
||||
|
||||
&__brand {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&__nav {
|
||||
flex-direction: row;
|
||||
overflow-x: auto;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,20 @@ import { useTranslation } from "react-i18next";
|
|||
import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
import { useAuth } from "../features/auth/AuthContext";
|
||||
import "./AppLayout.scss";
|
||||
import {
|
||||
AccountIcon,
|
||||
ChevronLeftIcon,
|
||||
DietPreferencesIcon,
|
||||
HouseholdIcon,
|
||||
PlanningIcon,
|
||||
RecipesIcon,
|
||||
SettingsIcon,
|
||||
ShoppingListIcon,
|
||||
UserPreferencesIcon,
|
||||
} from "./nav-icons";
|
||||
|
||||
/** `localStorage` key persisting {@link AppLayout}'s collapsed/expanded sidebar state across reloads. */
|
||||
const SIDEBAR_COLLAPSED_KEY = "batchcooking:sidebarCollapsed";
|
||||
|
||||
/**
|
||||
* One entry in the sidebar's main nav. `key` maps to `layout.nav.<key>` in
|
||||
|
|
@ -10,9 +24,9 @@ import "./AppLayout.scss";
|
|||
* one locale key, no other file to touch.
|
||||
*/
|
||||
const NAV_ITEMS = [
|
||||
{ to: "/", key: "planning" },
|
||||
{ to: "/recettes", key: "recipes" },
|
||||
{ to: "/liste-de-courses", key: "shoppingList" },
|
||||
{ to: "/", key: "planning", Icon: PlanningIcon },
|
||||
{ to: "/recettes", key: "recipes", Icon: RecipesIcon },
|
||||
{ to: "/liste-de-courses", key: "shoppingList", Icon: ShoppingListIcon },
|
||||
] as const;
|
||||
|
||||
/**
|
||||
|
|
@ -20,10 +34,10 @@ const NAV_ITEMS = [
|
|||
* maps to `layout.settings.nav.<key>`.
|
||||
*/
|
||||
const SETTINGS_ITEMS = [
|
||||
{ to: "/parametres/compte", key: "account" },
|
||||
{ to: "/parametres/preferences", key: "preferences" },
|
||||
{ to: "/parametres/foyer", key: "household" },
|
||||
{ to: "/parametres/preferences-utilisateur", key: "userPreferences" },
|
||||
{ to: "/parametres/compte", key: "account", Icon: AccountIcon },
|
||||
{ to: "/parametres/preferences", key: "preferences", Icon: DietPreferencesIcon },
|
||||
{ to: "/parametres/foyer", key: "household", Icon: HouseholdIcon },
|
||||
{ to: "/parametres/preferences-utilisateur", key: "userPreferences", Icon: UserPreferencesIcon },
|
||||
] as const;
|
||||
|
||||
/**
|
||||
|
|
@ -31,20 +45,52 @@ const SETTINGS_ITEMS = [
|
|||
* collapsible "Paramètres" nav, and the account menu at the bottom) plus a
|
||||
* main content area rendering the matched child route via `<Outlet />`.
|
||||
*
|
||||
* The sidebar itself can be collapsed to an icon-only rail (see
|
||||
* `isCollapsed`/`SIDEBAR_COLLAPSED_KEY`) — every nav item keeps its icon
|
||||
* and gains a `title` tooltip, its label just hides via CSS
|
||||
* (`.app-sidebar.collapsed .label`, see AppLayout.scss), so collapsing is a
|
||||
* single class toggle on the `<aside>`, not something each child component
|
||||
* needs to know about.
|
||||
*
|
||||
* Mounted once as the parent element of the whole authenticated route
|
||||
* group, itself wrapped in {@link RequireAuth} (see `App.tsx`) — `user` is
|
||||
* therefore guaranteed non-null by the time this renders.
|
||||
*/
|
||||
export function AppLayout() {
|
||||
const { t } = useTranslation();
|
||||
const [isCollapsed, setIsCollapsed] = useState(
|
||||
() => localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === "true",
|
||||
);
|
||||
|
||||
function toggleCollapsed() {
|
||||
setIsCollapsed((collapsed) => {
|
||||
const next = !collapsed;
|
||||
localStorage.setItem(SIDEBAR_COLLAPSED_KEY, String(next));
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app-layout">
|
||||
<aside className="app-sidebar">
|
||||
<div className="app-sidebar__brand">batchCooking</div>
|
||||
<aside className={isCollapsed ? "app-sidebar collapsed" : "app-sidebar"}>
|
||||
<div className="app-sidebar__top">
|
||||
<div className="app-sidebar__brand">
|
||||
{/* Collapsed: a short monogram instead of the full wordmark — clearer than an arbitrary icon standing in for the brand itself. */}
|
||||
<span className="app-sidebar__brand-full">batchCooking</span>
|
||||
<span className="app-sidebar__brand-mark">bC</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="app-sidebar__collapse-toggle"
|
||||
onClick={toggleCollapsed}
|
||||
title={t(isCollapsed ? "layout.sidebar.expand" : "layout.sidebar.collapse")}
|
||||
>
|
||||
<ChevronLeftIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav className="app-sidebar__nav">
|
||||
{NAV_ITEMS.map(({ to, key }) => (
|
||||
{NAV_ITEMS.map(({ to, key, Icon }) => (
|
||||
<NavLink
|
||||
key={to}
|
||||
to={to}
|
||||
|
|
@ -54,8 +100,10 @@ export function AppLayout() {
|
|||
// same way `end` would.
|
||||
end={to === "/"}
|
||||
className={({ isActive }) => (isActive ? "active" : undefined)}
|
||||
title={t(`layout.nav.${key}`)}
|
||||
>
|
||||
{t(`layout.nav.${key}`)}
|
||||
<Icon />
|
||||
<span className="label">{t(`layout.nav.${key}`)}</span>
|
||||
</NavLink>
|
||||
))}
|
||||
</nav>
|
||||
|
|
@ -74,11 +122,14 @@ export function AppLayout() {
|
|||
/**
|
||||
* Collapsible "Paramètres" section revealing the settings pages (Compte /
|
||||
* Préférences alimentaires / Foyer / Préférences utilisateur — see
|
||||
* `pages/settings/`). Starts open whenever
|
||||
* the current route is already under `/parametres`, so following a link
|
||||
* there (e.g. from {@link AccountMenu}) doesn't land on a collapsed menu;
|
||||
* otherwise starts closed to keep the sidebar's main focus on the primary
|
||||
* nav above it.
|
||||
* `pages/settings/`). Starts open whenever the current route is already
|
||||
* under `/parametres`, so following a link there (e.g. from
|
||||
* {@link AccountMenu}) doesn't land on a collapsed menu; otherwise starts
|
||||
* closed to keep the sidebar's main focus on the primary nav above it.
|
||||
*
|
||||
* Independent of the whole-sidebar collapse toggle in {@link AppLayout} —
|
||||
* this menu's own open/closed state persists across that (a rail-collapsed
|
||||
* sidebar can still have "Paramètres" expanded, just showing icons).
|
||||
*/
|
||||
function SettingsMenu() {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -92,20 +143,28 @@ function SettingsMenu() {
|
|||
className="app-sidebar__settings-toggle"
|
||||
aria-expanded={isOpen}
|
||||
onClick={() => setIsOpen((open) => !open)}
|
||||
title={t("layout.settings.toggle")}
|
||||
>
|
||||
{t("layout.settings.toggle")}
|
||||
<span aria-hidden="true">{isOpen ? "▾" : "▸"}</span>
|
||||
<span className="app-sidebar__settings-toggle-left">
|
||||
<SettingsIcon />
|
||||
<span className="label">{t("layout.settings.toggle")}</span>
|
||||
</span>
|
||||
<span className="chevron" aria-hidden="true">
|
||||
{isOpen ? "▾" : "▸"}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<nav className="app-sidebar__nav app-sidebar__settings-nav">
|
||||
{SETTINGS_ITEMS.map(({ to, key }) => (
|
||||
{SETTINGS_ITEMS.map(({ to, key, Icon }) => (
|
||||
<NavLink
|
||||
key={to}
|
||||
to={to}
|
||||
className={({ isActive }) => (isActive ? "active" : undefined)}
|
||||
title={t(`layout.settings.nav.${key}`)}
|
||||
>
|
||||
{t(`layout.settings.nav.${key}`)}
|
||||
<Icon />
|
||||
<span className="label">{t(`layout.settings.nav.${key}`)}</span>
|
||||
</NavLink>
|
||||
))}
|
||||
</nav>
|
||||
|
|
@ -134,6 +193,8 @@ function AccountMenu() {
|
|||
navigate("/login");
|
||||
}
|
||||
|
||||
const initial = user?.firstName?.charAt(0).toUpperCase() ?? "";
|
||||
|
||||
return (
|
||||
<div className="app-sidebar__footer">
|
||||
<button
|
||||
|
|
@ -141,8 +202,12 @@ function AccountMenu() {
|
|||
className="app-sidebar__account-toggle"
|
||||
aria-expanded={isOpen}
|
||||
onClick={() => setIsOpen((open) => !open)}
|
||||
title={t("layout.greeting", { firstName: user?.firstName })}
|
||||
>
|
||||
{t("layout.greeting", { firstName: user?.firstName })}
|
||||
<span className="app-sidebar__avatar" aria-hidden="true">
|
||||
{initial}
|
||||
</span>
|
||||
<span className="label">{t("layout.greeting", { firstName: user?.firstName })}</span>
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
|
|
|
|||
110
apps/web/src/layouts/nav-icons.tsx
Normal file
110
apps/web/src/layouts/nav-icons.tsx
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
import type { ReactNode } from "react";
|
||||
|
||||
// Small, hand-drawn line-icon set for the sidebar nav (24×24 viewBox,
|
||||
// matches the reviewed mockup — see the plan/PR description) rather than
|
||||
// pulling in an icon library for a handful of glyphs. Sized entirely via
|
||||
// CSS (`.app-sidebar__nav svg` etc., see AppLayout.scss) — no width/height
|
||||
// attribute here, so the same markup works at any size the caller picks.
|
||||
// `aria-hidden` on every icon: each one is always paired with visible text
|
||||
// (the nav label, or a `title` tooltip when collapsed) that already
|
||||
// conveys the meaning — the icon itself is decorative.
|
||||
|
||||
function Icon({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{children}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlanningIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<rect x="3" y="4" width="18" height="18" rx="2" />
|
||||
<path d="M16 2v4M8 2v4M3 10h18" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function RecipesIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
|
||||
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function ShoppingListIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<circle cx="9" cy="21" r="1" />
|
||||
<circle cx="20" cy="21" r="1" />
|
||||
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function SettingsIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccountIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="12" cy="7" r="4" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function DietPreferencesIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10z" />
|
||||
<path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function HouseholdIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
||||
<path d="M9 22V12h6v10" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function UserPreferencesIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<circle cx="13.5" cy="6.5" r=".5" />
|
||||
<circle cx="17.5" cy="10.5" r=".5" />
|
||||
<circle cx="8.5" cy="7.5" r=".5" />
|
||||
<circle cx="6.5" cy="12.5" r=".5" />
|
||||
<path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.9 0 1.5-.7 1.5-1.5 0-.4-.2-.8-.4-1.1-.2-.3-.4-.6-.4-1 0-.8.7-1.5 1.5-1.5H16c3.3 0 6-2.7 6-6 0-4.4-4-8-10-8z" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChevronLeftIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M15 18l-6-6 6-6" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
|
@ -59,6 +59,10 @@
|
|||
}
|
||||
},
|
||||
"layout": {
|
||||
"sidebar": {
|
||||
"collapse": "Replier le menu",
|
||||
"expand": "Déplier le menu"
|
||||
},
|
||||
"nav": {
|
||||
"planning": "Planning",
|
||||
"recipes": "Recettes",
|
||||
|
|
|
|||
Loading…
Reference in a new issue