Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions _bmad-output/planning-artifacts/bmm-workflow-status.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Workflow Status - Quick Flow Mode

# This project uses Quick Flow methodology - minimal planning with direct implementation
# For Quick Flow work, use the quick-flow-solo-dev agent
# This file documents completed work for reference only

# STATUS DEFINITIONS:
# ==================
# Completion Status:
# - {file-path}: File created/found
# - completed: Work completed without artifact
# - skipped: Not applicable

generated: "2026-01-09"
project: "website"
project_type: "web-application"
selected_track: "quick-flow"
field_type: "brownfield"
workflow_path: "quick-flow-brownfield"

# User Description
user_description: |
Clean pass avant refonte UI conséquente:
- Mise à jour des dépendances
- Nettoyage des fichiers obsolètes
- Challenge de l'architecture
- Migration Radix UI → Ark UI
- Optimisation des structures et patterns
Objectif: Remise à niveau et préparation pour refonte graphique

# Quick Flow Work Completed
workflow_status:
cleanup-and-migration: "_bmad-output/implementation-artifacts/tech-spec-ocobo-cleanup-migration-ui.md"
dependencies-cleanup: "_bmad-output/implementation-artifacts/tech-spec-dependencies-cleanup.md"

# Next Steps (Flexible - Quick Flow Mode)
# ========================================
# Use quick-flow-solo-dev agent for:
# - Direct implementation from user requests
# - Optional tech-specs when helpful
# - Flexible, rapid development
#
# No formal workflow tracking needed in Quick Flow mode.
# This file serves as historical reference only.
25 changes: 21 additions & 4 deletions app/components/IntContactForm.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createListCollection } from '@ark-ui/react/select';
import { useTranslation } from 'react-i18next';
import { Form } from 'react-router';

Expand All @@ -8,6 +9,15 @@ import { Input } from './ui/Input';
import { Label } from './ui/Label';
import { Select } from './ui/Select';

// Create collection for team size select
const teamSizeCollection = createListCollection({
items: [
{ value: '1', label: '1-10' },
{ value: '11', label: '11-50' },
{ value: '50', label: '50+' },
],
});

const ContactForm: React.FunctionComponent<React.PropsWithChildren> = (
props,
) => {
Expand Down Expand Up @@ -59,15 +69,22 @@ const ContactForm: React.FunctionComponent<React.PropsWithChildren> = (
})}
>
<Label htmlFor="team">{t('form.team', { ns: 'contact' })}*</Label>
<Select.Root name="team" defaultValue="1" required>
<Select.Root
name="team"
collection={teamSizeCollection}
defaultValue={['1']}
required
>
<Select.Trigger id="team">
<Select.Value />
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.Item value="1">1-10</Select.Item>
<Select.Item value="11">11-50</Select.Item>
<Select.Item value="50">50+</Select.Item>
{teamSizeCollection.items.map((item) => (
<Select.Item key={item.value} item={item}>
{item.label}
</Select.Item>
))}
</Select.Group>
</Select.Content>
</Select.Root>
Expand Down
20 changes: 14 additions & 6 deletions app/components/LanguageSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createListCollection } from '@ark-ui/react/select';
import { useTranslation } from 'react-i18next';
import { useLocation, useNavigate, useParams } from 'react-router';

Expand Down Expand Up @@ -69,10 +70,17 @@ const LanguageSwitcher = () => {

if (!params.lang || supportedLngs.length < 2) return null;

// Create collection for Ark UI Select
const languageCollection = createListCollection({
items: supportedLngs.map((lng) => ({ value: lng, label: lng })),
});

return (
<Select.Root
defaultValue={lang}
onValueChange={(value) => {
collection={languageCollection}
defaultValue={[lang]}
onValueChange={(details: any) => {
const value = details.value[0];
const convertPathname = (lng: string) => {
return pathname.replace(`/${lang}`, `/${lng}`);
};
Expand All @@ -94,11 +102,11 @@ const LanguageSwitcher = () => {
</Select.Trigger>

<Select.Content>
{supportedLngs.map((lng) => (
<Select.Item key={lng} value={lng}>
{languageCollection.items.map((item) => (
<Select.Item key={item.value} item={item}>
<span className={flex({ gap: '2', alignItems: 'center' })}>
<Flag lang={lng} />
{t(`common:language.${lng}`)}
<Flag lang={item.value} />
{t(`common:language.${item.value}`)}
</span>
</Select.Item>
))}
Expand Down
49 changes: 21 additions & 28 deletions app/components/MainMobileMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import * as React from 'react';
import { ChevronRight } from 'lucide-react';
import { NavLink } from 'react-router';

import { cva } from '@ocobo/styled-system/css';
import { styled } from '@ocobo/styled-system/jsx';
import { css } from '@ocobo/styled-system/css';
import { flex } from '@ocobo/styled-system/patterns';

import { useMenuItems } from '~/hooks/useMenuItems';
Expand All @@ -15,32 +14,25 @@ import { SubMenu } from './SubMenu';
import { Accordion } from './ui/Accordion';
import { ScrollArea } from './ui/ScrollArea';

const triggerStyles = cva({
base: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
transition: 'all',
cursor: 'pointer',
textStyle: 'nav',
fontWeight: 'bold',
},
const triggerStyles = css({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
transition: 'all',
cursor: 'pointer',
textStyle: 'nav',
fontWeight: 'bold',
});

const itemStyles = cva({
base: {
p: '8',
const itemStyles = css({
p: '8',

_last: {
mt: 'auto',
textAlign: 'center',
},
_last: {
mt: 'auto',
textAlign: 'center',
},
});

const AccordionTrigger = styled(Accordion.Trigger, triggerStyles);
const AccordionItem = styled(Accordion.Item, itemStyles);

const MainMobileMenu = () => {
const items = useMenuItems();

Expand All @@ -49,7 +41,6 @@ const MainMobileMenu = () => {
<Header />
<ScrollArea>
<Accordion.Root
type="single"
collapsible
className={flex({
direction: 'column',
Expand All @@ -60,15 +51,17 @@ const MainMobileMenu = () => {
{items.map(({ key, title, url, subItems, className }) => (
<React.Fragment key={key}>
{url ? (
<div className={itemStyles()}>
<NavLink to={url} className={className ?? triggerStyles()}>
<div className={itemStyles}>
<NavLink to={url} className={className ?? triggerStyles}>
{title}
{subItems && subItems.length > 0 && <ChevronRight />}
</NavLink>
</div>
) : (
<AccordionItem value={key}>
<AccordionTrigger>{title}</AccordionTrigger>
<Accordion.Item value={key} className={itemStyles}>
<Accordion.Trigger className={triggerStyles}>
{title}
</Accordion.Trigger>
{subItems && subItems.length > 0 && (
<Accordion.Content>
<SubMenu.Root>
Expand All @@ -82,7 +75,7 @@ const MainMobileMenu = () => {
</SubMenu.Root>
</Accordion.Content>
)}
</AccordionItem>
</Accordion.Item>
)}
</React.Fragment>
))}
Expand Down
4 changes: 2 additions & 2 deletions app/components/MobileMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from 'react';

import { ark } from '@ark-ui/react/factory';
import { createContext } from '@radix-ui/react-context';
import * as Portal from '@radix-ui/react-portal';
import { Slot } from '@radix-ui/react-slot';
import { RemoveScroll } from 'react-remove-scroll';
import { useNavigation } from 'react-router';

Expand Down Expand Up @@ -61,7 +61,7 @@ const MobileMenu: React.FunctionComponent<React.PropsWithChildren> = ({

return (
<Portal.Root>
<RemoveScroll as={Slot} allowPinchZoom enabled>
<RemoveScroll as={ark.div} allowPinchZoom enabled>
<div
className={css({
position: 'fixed',
Expand Down
92 changes: 61 additions & 31 deletions app/components/ui/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,72 @@
import * as React from 'react';

import * as AccordionPrimitive from '@radix-ui/react-accordion';
import { createStyleContext } from '@shadow-panda/style-context';
import { Accordion as ArkAccordion } from '@ark-ui/react/accordion';
import { ChevronRight } from 'lucide-react';

import { styled } from '@ocobo/styled-system/jsx';
import { cx } from '@ocobo/styled-system/css';
import { accordion } from '@ocobo/styled-system/recipes';

const { withProvider, withContext } = createStyleContext(accordion);

const Header = withContext(styled(AccordionPrimitive.Header), 'header');

const TriggerBase = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ children, ...props }, ref) => (
<Header>
<AccordionPrimitive.Trigger ref={ref} {...props}>
{children}
<ChevronRight />
</AccordionPrimitive.Trigger>
</Header>
));
TriggerBase.displayName = AccordionPrimitive.Trigger.displayName;
HTMLButtonElement,
React.ComponentPropsWithoutRef<typeof ArkAccordion.ItemTrigger>
>(({ children, className, ...props }, ref) => {
const styles = accordion();
return (
<div className={styles.header}>
<ArkAccordion.ItemTrigger
ref={ref}
className={cx(styles.trigger, className)}
{...props}
>
{children}
<ChevronRight />
</ArkAccordion.ItemTrigger>
</div>
);
});
TriggerBase.displayName = 'AccordionTrigger';

// Wrapper for content with inner div for animation
const ContentBase = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ children, ...props }, ref) => (
<AccordionPrimitive.Content ref={ref} {...props}>
<div>{children}</div>
</AccordionPrimitive.Content>
));
ContentBase.displayName = AccordionPrimitive.Content.displayName;
HTMLDivElement,
React.ComponentPropsWithoutRef<typeof ArkAccordion.ItemContent>
>(({ children, ...props }, ref) => {
const styles = accordion();
return (
<ArkAccordion.ItemContent ref={ref} className={styles.content} {...props}>
<div>{children}</div>
</ArkAccordion.ItemContent>
);
});
ContentBase.displayName = 'AccordionContent';

const RootBase = React.forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<typeof ArkAccordion.Root>
>((props, ref) => {
const styles = accordion();
return <ArkAccordion.Root ref={ref} className={styles.root} {...props} />;
});
RootBase.displayName = 'AccordionRoot';

const Root = withProvider(styled(AccordionPrimitive.Root), 'root');
const Item = withContext(styled(AccordionPrimitive.Item), 'item');
const Trigger = withContext(styled(TriggerBase), 'trigger');
const Content = withContext(styled(ContentBase), 'content');
const ItemBase = React.forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<typeof ArkAccordion.Item>
>(({ className, ...props }, ref) => {
const styles = accordion();
return (
<ArkAccordion.Item
ref={ref}
className={cx(styles.item, className)}
{...props}
/>
);
});
ItemBase.displayName = 'AccordionItem';

export const Accordion = { Root, Item, Trigger, Content };
export const Accordion = {
Root: RootBase,
Item: ItemBase,
Trigger: TriggerBase,
Content: ContentBase,
};
23 changes: 8 additions & 15 deletions app/components/ui/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
import * as React from 'react';

import { Slot } from '@radix-ui/react-slot';
import { ark } from '@ark-ui/react/factory';

import { HTMLStyledProps, styled } from '@ocobo/styled-system/jsx';
import { button } from '@ocobo/styled-system/recipes';

// BaseButton: unstyled Ark UI button for composition (used by IconButton)
const BaseButton = React.forwardRef<
HTMLButtonElement,
React.ButtonHTMLAttributes<HTMLButtonElement> & {
asChild?: boolean;
children?: React.ReactNode;
}
>(({ asChild = false, children, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp ref={ref} {...props}>
{children}
</Comp>
);
React.ComponentPropsWithoutRef<typeof ark.button>
>((props, ref) => {
return <ark.button ref={ref} {...props} />;
});
BaseButton.displayName = 'BaseButton';

BaseButton.displayName = 'Button';

const Button = styled(BaseButton, button);
// Button: styled with button recipe
const Button = styled(ark.button, button);
type ButtonProps = HTMLStyledProps<typeof Button>;

export { Button, BaseButton };
Expand Down
Loading