Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
b64c663
feat(headless/components): implement TooltipMultiple for synchronized…
BlackPoretsky Dec 13, 2025
d48d31b
feat(headless/components): enhance TooltipMultiple with additional pr…
BlackPoretsky Dec 14, 2025
56d0c9e
feat(headless/components): enhance Tooltip and TooltipMultiple with c…
BlackPoretsky Dec 21, 2025
217822e
feat(headless/components): add NoSsr component for server-side render…
BlackPoretsky Dec 21, 2025
251b0a4
feat(headless/components): add Marquee component with root and track …
BlackPoretsky Dec 21, 2025
964bb8e
feat(headless/hooks): add useEyeDropper hook for color picking functi…
BlackPoretsky Dec 22, 2025
76d52dc
feat(headless/hooks): introduce useEventListener hook for managing ev…
BlackPoretsky Dec 22, 2025
3848b56
feat(headless/hooks): add useColorScheme hook for responsive color sc…
BlackPoretsky Dec 22, 2025
6e39f5c
feat(headless/hooks): add useDocumentTitle hook for managing document…
BlackPoretsky Dec 22, 2025
9f87316
feat(headless/hooks): add useFavicon hook for dynamic favicon management
BlackPoretsky Dec 22, 2025
1b9f36a
feat(headless/hooks): add multiple utility hooks for enhanced functio…
BlackPoretsky Dec 23, 2025
f7e6674
feat(headless/hooks): add new utility hooks including useFileDialog, …
BlackPoretsky Feb 7, 2026
1ec0900
feat(headless/hooks): improve tabbable search for useFocusTrap
BlackPoretsky Feb 8, 2026
6b22e93
feat(headless/components): enhance List component with virtualization…
BlackPoretsky Feb 8, 2026
c89b3ff
feat(headless/components): add CSPProvider component for Content Secu…
BlackPoretsky Feb 15, 2026
6221cb8
feat(flippo/components): introduce new Box, Card, Center, Flex, Grid,…
BlackPoretsky Apr 30, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ui/Box';
39 changes: 39 additions & 0 deletions packages/ui/uikit/flippo/components/src/components/Box/ui/Box.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type React from 'react';

import { useRender } from '@flippo-ui/headless-components';
import { extractBoxProps } from '~@lib/layouts';

import type { BoxProps as BoxLayoutProps } from '~@lib/types';

/**
* Box - A fundamental layout building block.
* Supports all layout props: margin, padding, sizing, position, overflow,
* and can act as a flex/grid child.
*/
export function Box<ElementType extends keyof React.JSX.IntrinsicElements = 'div'>(
props: Box.Props<ElementType>
) {
const { as: Tag = 'div', ref, ...restProps } = props;

const { style, otherProps } = extractBoxProps(restProps);

const element = useRender({
defaultTagName: Tag,
ref: ref as React.Ref<Element>,
props: [{ style }, otherProps]
});

return element;
}

export type BoxComponentProps<ElementType extends keyof React.JSX.IntrinsicElements = 'div'>
= React.PropsWithChildren<React.ComponentPropsWithRef<ElementType>>
& BoxLayoutProps
& {
/** HTML element to render */
as?: ElementType;
};

export namespace Box {
export type Props<ElementType extends keyof React.JSX.IntrinsicElements = 'div'> = BoxComponentProps<ElementType>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Context
export { useCardContext, useOptionalCardContext } from './ui/root/CardContext';
export type { CardContextValue } from './ui/root/CardContext';
export { CardContent as Content } from './ui/content/CardContent';
export { CardDescription as Description } from './ui/description/CardDescription';
export { CardFooter as Footer } from './ui/footer/CardFooter';

export { CardRoot as Root } from './ui/root/CardRoot';
export { CardTitle as Title } from './ui/title/CardTitle';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as Card from './index.parts';
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from 'react';

import type { Meta, StoryObj } from '@storybook/react';

import * as Button from '../../Button';
import * as Card from '../index.parts';

const meta: Meta<typeof Card.Root> = {
title: 'Components/Card',
component: Card.Root,
parameters: {
layout: 'centered'
},
tags: ['autodocs']
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
render: () => (
<Card.Root style={{ maxWidth: 400 }}>
<Card.Content>
<Card.Title>{'Card Title'}</Card.Title>
<Card.Description>
{'Card automatically connects Title and Description to Root via aria-labelledby and aria-describedby.'}
</Card.Description>
<p>{'Main content goes here. You can add any content you want inside the card.'}</p>
</Card.Content>
<Card.Footer>
<Button.Button variant={'primary'}>{'Action'}</Button.Button>
<Button.Button variant={'secondary'}>{'Cancel'}</Button.Button>
</Card.Footer>
</Card.Root>
)
};

export const WithoutFooter: Story = {
render: () => (
<Card.Root style={{ maxWidth: 400 }}>
<Card.Content>
<Card.Title>{'Simple Card'}</Card.Title>
<Card.Description>
{'A card without a footer section.'}
</Card.Description>
<p>{'This card only has content, no footer actions.'}</p>
</Card.Content>
</Card.Root>
)
};

export const WithLayoutProps: Story = {
render: () => (
<Card.Root p={24} m={16} maxWidth={500}>
<Card.Content>
<Card.Title>{'Card with Layout Props'}</Card.Title>
<Card.Description>
{'This card uses layout props (padding, margin, maxWidth) directly on the Root component.'}
</Card.Description>
</Card.Content>
</Card.Root>
)
};

export const MultipleCards: Story = {
render: () => (
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap' }}>
<Card.Root style={{ maxWidth: 300 }}>
<Card.Content>
<Card.Title>{'Card 1'}</Card.Title>
<Card.Description>{'First card description'}</Card.Description>
<p>{'Content for the first card.'}</p>
</Card.Content>
</Card.Root>
<Card.Root style={{ maxWidth: 300 }}>
<Card.Content>
<Card.Title>{'Card 2'}</Card.Title>
<Card.Description>{'Second card description'}</Card.Description>
<p>{'Content for the second card.'}</p>
</Card.Content>
</Card.Root>
<Card.Root style={{ maxWidth: 300 }}>
<Card.Content>
<Card.Title>{'Card 3'}</Card.Title>
<Card.Description>{'Third card description'}</Card.Description>
<p>{'Content for the third card.'}</p>
</Card.Content>
</Card.Root>
</div>
)
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.CardContent {
padding: var(--f-spacing-6);
display: flex;
flex-direction: column;
gap: var(--f-spacing-4);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type React from 'react';

import { useRender } from '@flippo-ui/headless-components';
import { cx } from 'class-variance-authority';

import type { PolymorphicComponentPropsWithRef } from '~@lib/types';

import styles from './CardContent.module.scss';

/**
* CardContent - Main content area of the card.
*/
export function CardContent(props: CardContent.Props) {
const {
as: Tag = 'div',
ref,
className,
...otherProps
} = props;

const cardContentClasses = cx(styles.CardContent, className);

const element = useRender({ defaultTagName: Tag, ref, props: [{ className: cardContentClasses }, otherProps] });

return element;
}

export namespace CardContent {
export type Props = PolymorphicComponentPropsWithRef<'div'>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@use 'mixins/_font.scss' as font;

.CardDescription {
@include font.body('default');
color: var(--f-color-text-3);
margin: 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type React from 'react';

import { useRender } from '@flippo-ui/headless-components';
import { cx } from 'class-variance-authority';

import type { PolymorphicComponentPropsWithRef } from '~@lib/types';

import { useCardContext } from '../root/CardContext';

import styles from './CardDescription.module.scss';

/**
* CardDescription - Card subtitle or description text.
* Automatically connected to Card.Root via context for accessibility.
*/
export function CardDescription(props: CardDescription.Props) {
const {
as: Tag = 'p',
ref,
className,
id: providedId,
...otherProps
} = props;

const context = useCardContext();

const descriptionId = providedId ?? context.descriptionId;
const cardDescriptionClasses = cx(styles.CardDescription, className);

const element = useRender({ defaultTagName: Tag, ref, props: [{ className: cardDescriptionClasses, id: descriptionId }, otherProps] });

return element;
}

export namespace CardDescription {
export type Props = PolymorphicComponentPropsWithRef<'p'>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.CardFooter {
padding: var(--f-spacing-6);
padding-top: 0;
display: flex;
align-items: center;
gap: var(--f-spacing-3);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type React from 'react';

import { useRender } from '@flippo-ui/headless-components';
import { cx } from 'class-variance-authority';

import type { PolymorphicComponentPropsWithRef } from '~@lib/types';

import styles from './CardFooter.module.scss';

/**
* CardFooter - Footer area for card actions or additional content.
*/
export function CardFooter(props: CardFooter.Props) {
const {
as: Tag = 'div',
ref,
className,
...otherProps
} = props;

const cardFooterClasses = cx(styles.CardFooter, className);

const element = useRender({ defaultTagName: Tag, ref, props: [{ className: cardFooterClasses }, otherProps] });

return element;
}

export namespace CardFooter {
export type Props = PolymorphicComponentPropsWithRef<'div'>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';

export type CardContextValue = {
titleId: string;
descriptionId: string;
};

export const CardContext = React.createContext<CardContextValue | null>(null);

export function useCardContext() {
const context = React.use(CardContext);
if (!context) {
throw new Error('Card components must be used within Card.Root');
}
return context;
}

export function useOptionalCardContext() {
return React.use(CardContext);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.CardRoot {
background-color: var(--f-color-bg-1);
border-radius: var(--f-border-radius-card);
border: 1px solid var(--f-color-stroke);
overflow: hidden;
transition: all 0.2s ease;

&:hover {
border-color: var(--f-color-stroke-hover);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';
import type * as ReactTypes from 'react';

import { useRender } from '@flippo-ui/headless-components';
import { cx } from 'class-variance-authority';

import { extractBoxProps } from '~@lib/layouts';

import type { BoxProps } from '~@lib/types';

import { CardContext } from './CardContext';
import styles from './CardRoot.module.scss';

/**
* CardRoot - Root container for Card component.
* Provides context for Title and Description to connect via aria-labelledby and aria-describedby.
*/
export function CardRoot<ElementType extends keyof ReactTypes.JSX.IntrinsicElements = 'div'>(
props: CardRoot.Props<ElementType>
) {
const {
as: Tag = 'div',
ref,
className,
...restProps
} = props;

const titleId = React.useId();
const descriptionId = React.useId();

const { style, otherProps } = extractBoxProps(restProps);

const contextValue = React.useMemo(
() => ({ titleId, descriptionId }),
[titleId, descriptionId]
);

const element = useRender({
defaultTagName: Tag,
ref: ref as React.Ref<Element>,
props: [{
style,
'className': cx(styles.CardRoot, className),
'aria-labelledby': titleId,
'aria-describedby': descriptionId
}, otherProps]
});

return <CardContext.Provider value={contextValue}>{element}</CardContext.Provider>;
}

export type CardRootProps<ElementType extends keyof React.JSX.IntrinsicElements = 'div'>
= React.PropsWithChildren<React.ComponentPropsWithRef<ElementType>>
& BoxProps
& {
/** HTML element to render */
as?: ElementType;
/** Additional CSS class */
className?: string;
};

export namespace CardRoot {
export type Props<ElementType extends keyof React.JSX.IntrinsicElements = 'div'> = CardRootProps<ElementType>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@use 'mixins/_font.scss' as font;

.CardTitle {
@include font.heading3('default');
color: var(--f-color-text-primary);
margin: 0;
}
Loading
Loading