citrica-ui-toolkit
v0.0.25
Published
Citrica UI Toolkit is a React component library built on top of HeroUI components, providing a custom design system wrapper. The library is built with TypeScript and bundled using tsup for distribution.
Readme
CITRICA UI TOOLKIT
Citrica UI Toolkit is a React component library built on top of HeroUI components, providing a custom design system wrapper. The library is built with TypeScript and bundled using tsup for distribution.
Installation
npm install citrica-ui-toolkitArchitecture
Components follow Atomic Design principles:
- Atoms: Basic building blocks (Button, Input, Select, Autocomplete, Textarea, Icon, Card, Grid, Text)
- Molecules: Compositions of atoms (Modal, Carousel, Accordion)
- Organisms: Complex UI sections (Footer, Header, Sidebar, Login)
Components Documentation
ATOMS
Button
Interactive button component with multiple variants and admin mode support.
Import:
import { Button } from 'citrica-ui-toolkit';Props:
label?: string- Button text labelchildren?: React.ReactNode- Custom content (overrides label)variant?: "primary" | "secondary" | "flat" | "link" | "success" | "warning" | "danger"- Visual style (default: "primary")textVariant?: "label" | "body" | "title" | "display" | "headline" | "subtitle"- Text typography variant (default: "label")size?: "xs" | "sm" | "md" | "lg"- Button size (default: "md")type?: "button" | "submit" | "reset"- HTML button type (default: "button")isDisabled?: boolean- Disable button (default: false)isIconOnly?: boolean- Button with only icon, no textisAdmin?: boolean- Use admin theme colors (default: false)isLoading?: boolean- Show loading statestartIcon?: IconName- Lucide icon at start (via Icon component)endIcon?: IconName- Lucide icon at endiconSize?: number- Size in px forstartIcon/endIconstartContent?: React.ReactNode- Content before labelendContent?: React.ReactNode- Content after labelfullWidth?: boolean- Expand to full widthcolor?: string- Free CSS text color (e.g."#FFF"or"var(--color-text-white)"). Takes priority over the variant colortextColor?: string- Text color token (e.g."color-on-primary"). Used whencoloris not providedhref?: string- If set, the button renders as an anchor (<a>) — ideal fortel:,mailto:or navigation linksas?: React.ElementType- Element/component to render (e.g."a"or Next's<Link>)target?: string- Anchor targetrel?: string- Anchor relisExternal?: boolean- Shortcut: opens in a new tab with a saferel(default: false)hoverColor?: string- Text color on hover (mainly for thelinkvariant)backgroundHoverColor?: string- Background color on hover (mainly for thelinkvariant)unstyled?: boolean- Removes the design-system styles (thebtn-citrica-uibox, variant fill and forced text color). The consumer controls everything viaclassNameand the color is inherited (currentColor), so the icon follows the text. Keeps semantics/behavior (as/href, loading, focus, icons). Ideal for nav rows or custom-shaped CTAs (default: false)className?: string- Additional CSS classesonPress?: () => void- Click handler
Usage:
// Basic button
<Button label="Click me" variant="primary" />
// Admin mode button
<Button label="Admin Action" variant="primary" isAdmin={true} />
// Button with Lucide icons (via startIcon/endIcon)
<Button label="Save" startIcon="Save" />
<Button label="Next" endIcon="ArrowRight" iconSize={18} />
// Custom content
<Button label="Save" startContent={<Icon name="Save" />} />
// Loading button
<Button label="Loading..." isLoading={true} />
// Link variant with custom hover colors
<Button
label="Learn more"
variant="link"
hoverColor="#265197"
backgroundHoverColor="#EAF0FA"
/>
// Render as a real anchor (tel/mailto/navigation)
<Button label="Call us" href="tel:+5112345678" />
<Button label="Docs" href="https://example.com" isExternal />
// Render as Next.js Link
<Button label="Go home" as={Link} href="/" />
// Custom text color (free CSS value or token)
<Button label="White text" color="#FFFFFF" />
<Button label="Primary text" textColor="color-on-primary" />
// Extra-small icon-only button
<Button isIconOnly size="xs" startIcon="Plus" />
// Unstyled button (consumer fully controls styling via className)
<Button unstyled label="Custom" className="flex items-center gap-2 text-blue-600" />CSS Classes Applied:
btn-citrica-uiorbtn-citrica-ui-adminbtn-{variant}orbtn-{variant}-admin(includesbtn-link/btn-link-adminandbtn-flat/btn-flat-admin)btn-icon-only-citrica-ui(whenisIconOnly)btn-xs-citrica-uiorbtn-xs-citrica-ui-admin(whensize="xs")btn-hover-text(whenhoverColoris set),btn-hover-bg(whenbackgroundHoverColoris set)
Notes:
- Uses
forwardReffor ref forwarding. - Text/icon color priority:
color(free CSS) →textColor(token) → the variant's color. - Custom hover colors are passed as CSS custom properties (
--btn-hover-text,--btn-hover-bg) and consumed by the SCSS on:hover(a modifier class disables the default opacity hover). - With
unstyled, none of the system styles/hover vars apply; the label inheritscurrentColor.
Input
Text input field with icon support and validation capabilities.
Import:
import { Input } from 'citrica-ui-toolkit';Props:
label?: string- Input labelplaceholder?: string- Placeholder textvalue?: string- Controlled valuedefaultValue?: string- Uncontrolled default valueonChange?: (e: React.ChangeEvent<HTMLInputElement>) => void- Change handleronValueChange?: (value: string) => void- Value change handler (string only)name?: string- Input name for formstype?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search' | 'date' | 'datetime-local' | 'time'- Input type (default: 'text')variant?: 'primary' | 'secondary' | 'flat' | 'bordered' | 'faded' | 'underlined'- Visual style (default: 'primary')size?: 'sm' | 'md' | 'lg'- Input size (default: 'md')radius?: 'none' | 'sm' | 'md' | 'lg' | 'full'- Border radius (default: 'md')required?: boolean- Mark as requireddisabled?: boolean- Disable inputreadOnly?: boolean- Make read-onlyisInvalid?: boolean- Show invalid stateerrorMessage?: string- Error message to displaydescription?: string- Helper textstartIcon?: IconName- Lucide icon at start (e.g., "Mail", "Lock")endIcon?: IconName- Lucide icon at endstartContent?: React.ReactNode- Custom content at startendContent?: React.ReactNode- Custom content at endiconSize?: number- Icon size in pixels (default: 20)iconColor?: string- Icon colorfullWidth?: boolean- Full width (default: true)clearable?: boolean- Show clear buttonautoFocus?: boolean- Auto focus on mountmaxLength?: number- Maximum charactersclassName?: string- Additional CSS classesisAdmin?: boolean- Use admin theme colors (default: false)
Usage:
// Basic input
<Input label="Email" type="email" placeholder="Enter your email" />
// Input with icon
<Input
label="Password"
type="password"
startIcon="Lock"
variant="primary"
/>
// Input with validation
<Input
label="Username"
isInvalid={true}
errorMessage="Username is required"
/>
// Controlled input
<Input
value={value}
onValueChange={(val) => setValue(val)}
/>
// Admin mode input
<Input label="Usuario" variant="primary" isAdmin={true} />CSS Classes Applied:
input-citrica-uiorinput-citrica-ui-admininput-primary/input-secondary(or-adminvariants)
Notes:
- Uses
forwardReffor ref forwarding (compatible with react-hook-form) - Custom variants (primary/secondary) internally map to HeroUI's "bordered" variant
Select
Dropdown select component with option items.
Import:
import { Select, type SelectOption } from 'citrica-ui-toolkit';Props:
label?: string- Select labelplaceholder?: string- Placeholder textselectedKeys?: string[]- Controlled selected keysdefaultSelectedKeys?: string[]- Default selected keysonSelectionChange?: (keys: any) => void- Selection change handlername?: string- Input name for formsvariant?: 'primary' | 'secondary' | 'flat' | 'bordered' | 'faded' | 'underlined'- Visual style (default: 'primary')color?: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger'- HeroUI color scheme (default: 'default')size?: 'sm' | 'md' | 'lg'- Size (default: 'md')radius?: 'none' | 'sm' | 'md' | 'lg' | 'full'- Border radius (default: 'md')required?: boolean- Mark as requireddisabled?: boolean- Disable selectisInvalid?: boolean- Show invalid stateerrorMessage?: string- Error messagedescription?: string- Helper textstartIcon?: IconName- Icon at startendIcon?: IconName- Icon at endstartContent?: React.ReactNode- Custom content at startendContent?: React.ReactNode- Custom content at endiconSize?: number- Icon size (default: 20)iconColor?: string- Icon colorfullWidth?: boolean- Full width (default: true)options: SelectOption[]- Array of options (required)renderValue?: (items: SelectedItem[]) => React.ReactNode- Custom render for selected valueclassName?: string- Additional CSS classesclassNames?: object- Custom classes for the select's internal slots:base,trigger,label,value,selectorIcon,description,errorMessageisAdmin?: boolean- Use admin theme colors (default: false)shouldBlockScroll?: boolean- Whether the popover blocks page scroll when opened (default: false). An inline select doesn't need to lock the scroll; doing so makes react-aria setoverflow:hiddenon<html>, which breaksposition: stickyelements (e.g. a fixed topbar disappears). Set totrueonly if you really need it
SelectOption Interface:
interface SelectOption {
value: string; // Option value
label: string; // Display text
description?: string; // Optional description
startContent?: React.ReactNode; // Content at start
endContent?: React.ReactNode; // Content at end
}SelectedItem Interface (passed to renderValue):
interface SelectedItem extends SelectOption {
key: string; // Item key (equals value)
textValue: string; // Item text (equals label)
data?: SelectOption; // Original option data
}Usage:
const options = [
{ value: "1", label: "Option 1" },
{ value: "2", label: "Option 2", description: "Description for option 2" },
{ value: "3", label: "Option 3" }
];
// Basic select
<Select
label="Choose option"
placeholder="Select an option"
options={options}
/>
// Controlled select
<Select
selectedKeys={[selectedValue]}
onSelectionChange={(keys) => setSelectedValue(Array.from(keys)[0])}
options={options}
/>
// Select with custom render
<Select
options={options}
renderValue={(items) => (
<div>
{items.map(item => item.label).join(", ")}
</div>
)}
/>CSS Classes Applied:
select-citrica-uiorselect-citrica-ui-adminselect-primary/select-secondary(or-adminvariants)select-item-citrica-uiorselect-item-citrica-ui-admin(on option items)
Notes:
- Custom variants (primary/secondary) internally map to HeroUI's "bordered" variant.
- When a listbox opens, react-aria focuses the active option and calls
scrollIntoView(), which scrolls the page and breaksposition: stickyelements (a fixed topbar disappears) on desktop/Android, and on iOS inside a modal prevents selecting. The component installs a one-time, idempotent patch that neutralizesscrollIntoViewonly for elements inside a listbox, keeping keyboard navigation and anchor scrolling intact. - Combined with
shouldBlockScroll={false}(the default), this keeps a fixed topbar visible while the select is open.
Autocomplete
Searchable autocomplete/combobox component with filtering.
Import:
import { Autocomplete, type AutocompleteOption } from 'citrica-ui-toolkit';Props:
label?: string- Field labelplaceholder?: string- Placeholder textselectedKey?: string | null- Controlled selected keydefaultSelectedKey?: string- Default selected keyinputValue?: string- Controlled input valuedefaultInputValue?: string- Default input valueonSelectionChange?: (key: string | null) => void- Selection handleronInputChange?: (value: string) => void- Input change handleronClear?: () => void- Clear handlername?: string- Input namevariant?: 'primary' | 'secondary' | 'flat' | 'bordered' | 'faded' | 'underlined'- Style (default: 'primary')size?: 'sm' | 'md' | 'lg'- Size (default: 'md')radius?: 'none' | 'sm' | 'md' | 'lg' | 'full'- Border radius (default: 'md')required?: boolean- Required fielddisabled?: boolean- Disable componentisInvalid?: boolean- Invalid stateisReadOnly?: boolean- Read-only modeerrorMessage?: string- Error messagedescription?: string- Helper textstartIcon?: IconName- Icon at startendIcon?: IconName- Icon at endiconSize?: number- Icon size (default: 20)iconColor?: string- Icon colorfullWidth?: boolean- Full width (default: true)options: AutocompleteOption[]- Options array (required)allowsCustomValue?: boolean- Allow custom values (default: false)isClearable?: boolean- Show clear button (default: true)menuTrigger?: 'focus' | 'input' | 'manual'- When to open menu (default: 'focus')labelPlacement?: 'inside' | 'outside' | 'outside-left'- Label position (default: 'inside')disabledKeys?: string[]- Array of disabled keysmaxListboxHeight?: number- Max height of dropdownisVirtualized?: boolean- Enable virtualization for large listsclassName?: string- Additional CSS classes
AutocompleteOption Interface:
interface AutocompleteOption {
value: string;
label: string;
description?: string;
startContent?: React.ReactNode;
endContent?: React.ReactNode;
}Usage:
const options = [
{ value: "apple", label: "Apple" },
{ value: "banana", label: "Banana" },
{ value: "cherry", label: "Cherry" }
];
// Basic autocomplete
<Autocomplete
label="Choose fruit"
placeholder="Search fruits..."
options={options}
/>
// Controlled autocomplete
<Autocomplete
selectedKey={selected}
onSelectionChange={setSelected}
inputValue={input}
onInputChange={setInput}
options={options}
/>
// With custom values
<Autocomplete
options={options}
allowsCustomValue={true}
placeholder="Type or select..."
/>CSS Classes Applied:
autocomplete-citrica-uiautocomplete-primaryorautocomplete-secondaryautocomplete-item-citrica-ui(on items)
Text
Typography component with multiple variants and admin mode.
Import:
import { Text } from 'citrica-ui-toolkit';Props:
children: React.ReactNode- Text content (required)variant?: 'display' | 'headline' | 'title' | 'subtitle' | 'body' | 'label' | 'headlinecustom'- Typography style (default: 'body')weight?: 'light' | 'normal' | 'bold'- Font weight (default: 'normal')color?: string- Custom CSS color value (e.g."#FF5733"). Highest prioritytextColor?: string- Color token / CSS variable name (resolved asvar(--{textColor}))isAdmin?: boolean- Use admin theme (default: false)as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'div' | 'a'- HTML element (default: 'span')href?: string- Destination URL. If set (oras="a"), the text becomes a clickable linktarget?: string- Link target (e.g."_blank")rel?: string- Link relationship. If it opens in"_blank"andrelis omitted,"noopener noreferrer"is applied automaticallytextDecoration?: 'underline' | 'line-through'- Text decoration; works on any variantclassName?: string- Additional CSS classes- Plus all standard HTML attributes (
style,onClick, etc.) — a customstyletakes priority over the resolved color/decoration
Color priority: color (explicit CSS) → textColor (token) → link color (--color-text-link, when it's a link) → default (--color-black).
Usage:
// Basic text
<Text variant="body">This is body text</Text>
// Heading
<Text variant="headline" as="h1" weight="bold">
Main Heading
</Text>
// Custom color
<Text color="#FF5733">Custom colored text</Text>
// With CSS variable / token
<Text textColor="color-primary">Primary color text</Text>
// As a link (href makes it clickable; uses --color-text-link by default)
<Text href="/about">About us</Text>
// External link (auto rel="noopener noreferrer")
<Text href="https://example.com" target="_blank">External link</Text>
// Text decoration
<Text textDecoration="underline">Underlined text</Text>
<Text textDecoration="line-through">Old price</Text>
// Admin mode
<Text variant="label" isAdmin={true}>Admin text</Text>CSS Classes Applied:
text-{variant}ortext-{variant}-admintext-componenttext-{variant}-{weight}(if weight is not 'normal')
Notes:
- Color and decoration are applied via inline
style; passing your ownstyleoverrides them. - When rendered as a link (
hreforas="a"), opening in_blankwithout an explicitreldefaults to a safenoopener noreferrer.
Icon
Icon component using Lucide React icons library.
Import:
import { Icon, type IconName } from 'citrica-ui-toolkit';Props:
name: IconName- Lucide icon name (required, e.g., "Home", "User", "Settings")size?: number- Icon size in pixels (default: 20)strokeWidth?: number- Stroke width (default: 2)color?: string- Icon colorfallback?: IconName- Fallback icon if name not found (default: "AlertCircle")- Plus all other Lucide icon props (className, etc.)
Usage:
// Basic icon
<Icon name="Home" />
// Custom size and color
<Icon name="User" size={32} color="#FF5733" />
// With custom stroke
<Icon name="Settings" strokeWidth={3} />
// Common icon names
<Icon name="Mail" />
<Icon name="Lock" />
<Icon name="Search" />
<Icon name="ChevronDown" />
<Icon name="AlertCircle" />
<Icon name="Check" />
<Icon name="X" />
<Icon name="Menu" />Notes:
- Uses all icons from
lucide-reactlibrary - Automatically falls back to AlertCircle if icon not found
- Fully typed with TypeScript autocomplete for icon names
Card
Container component with optional header and footer sections.
Import:
import { Card } from 'citrica-ui-toolkit';Props:
children: ReactNode- Card body content (required)header?: ReactNode- Optional header contentfooter?: ReactNode- Optional footer contentshadow?: 'none' | 'sm' | 'md' | 'lg'- Shadow depth (default: 'sm')radius?: 'none' | 'sm' | 'md' | 'lg'- Border radius (default: 'md')variant?: 'shadow' | 'bordered' | 'light' | 'flat'- Visual variantisPressable?: boolean- Make card clickable (default: false)onPress?: () => void- Click handlerclassName?: string- Additional CSS classes
Usage:
// Basic card
<Card>
<p>Card content here</p>
</Card>
// Card with header and footer
<Card
header={<h3>Card Title</h3>}
footer={<Button label="Action" />}
shadow="md"
>
<p>Card body content</p>
</Card>
// Clickable card
<Card isPressable onPress={() => console.log('clicked')}>
<p>Click me</p>
</Card>Textarea
Multi-line text input component.
Import:
import { Textarea } from 'citrica-ui-toolkit';Props:
label?: string- Textarea labelplaceholder?: string- Placeholder textvalue?: string- Controlled valuedefaultValue?: string- Default valueonChange?: (e: React.ChangeEvent<HTMLInputElement>) => void- Change handleronValueChange?: (value: string) => void- Value change handlername?: string- Input namevariant?: 'primary' | 'secondary' | 'flat' | 'bordered' | 'faded' | 'underlined'- Style (default: 'primary')size?: 'sm' | 'md' | 'lg'- Size (default: 'md')radius?: 'none' | 'sm' | 'md' | 'lg' | 'full'- Border radius (default: 'md')required?: boolean- Required fielddisabled?: boolean- Disable textareareadOnly?: boolean- Read-only modeisInvalid?: boolean- Invalid stateerrorMessage?: string- Error messagedescription?: string- Helper textfullWidth?: boolean- Full width (default: true)autoFocus?: boolean- Auto focusmaxLength?: number- Max charactersminLength?: number- Min charactersrows?: number- Initial rows (default: 4)minRows?: number- Minimum rowsmaxRows?: number- Maximum rows (auto-grows)className?: string- Additional CSS classes
Usage:
// Basic textarea
<Textarea
label="Description"
placeholder="Enter description..."
rows={6}
/>
// With validation
<Textarea
label="Comments"
isInvalid={true}
errorMessage="Comments are required"
maxLength={500}
/>
// Auto-growing textarea
<Textarea
label="Message"
minRows={3}
maxRows={10}
/>CSS Classes Applied:
textarea-citrica-uitextarea-primaryortextarea-secondary
Grid Components (Container & Col)
Responsive grid layout system.
Import:
import { Container, Col } from 'citrica-ui-toolkit';Container
Props:
children?: React.ReactNode- Container contentclassName?: string- Additional CSS classesnoPadding?: boolean- Remove default padding (default: false)noLimit?: boolean- Remove max-width limit (default: false)
Usage:
<Container>
{/* Content here */}
</Container>
<Container noPadding noLimit>
{/* Full width, no padding */}
</Container>CSS Classes Applied:
o-containerno-padding(if noPadding)no-width-limit(if noLimit)
Col
Props:
cols: ColSizes- Column sizes by breakpoint (required)children?: React.ReactNode- Column contentclassName?: string- Additional CSS classesnoPadding?: boolean- Remove default padding
ColSizes Interface:
interface ColSizes {
lg?: number; // Large screens (12-column grid)
md?: number; // Medium screens (6-column grid)
sm?: number; // Small screens (4-column grid)
lgPush?: number; // Offset for large screens
mdPush?: number; // Offset for medium screens
smPush?: number; // Offset for small screens
}Usage:
// Responsive columns
<Container>
<Col cols={{ lg: 6, md: 3, sm: 2 }}>
Column 1
</Col>
<Col cols={{ lg: 6, md: 3, sm: 2 }}>
Column 2
</Col>
</Container>
// With offset
<Container>
<Col cols={{ lg: 8, lgPush: 2 }}>
Centered column with offset
</Col>
</Container>CSS Classes Applied:
o-col-{size}@lg,o-col-{size}@md,o-col-{size}@smo-col-push-{size}@lg,o-col-push-{size}@md,o-col-push-{size}@smno-padding(if noPadding)
MOLECULES
Modal
Modal dialog component with customizable content areas.
Import:
import { Modal } from 'citrica-ui-toolkit';Props:
isOpen: boolean- Modal open state (required)onClose: () => void- Close handler (required)title?: string- Modal titlechildren?: React.ReactNode- Modal body contentheader?: React.ReactNode- Custom header (overrides title)footer?: React.ReactNode- Footer contentsize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | 'full'- Modal size (default: 'md')placement?: 'center' | 'top' | 'bottom' | 'top-center' | 'bottom-center'- Position (default: 'center')backdrop?: 'transparent' | 'opaque' | 'blur'- Backdrop style (default: 'opaque')scrollBehavior?: 'normal' | 'inside' | 'outside'- Scroll behavior (default: 'normal')hideCloseButton?: boolean- Hide close button (default: false)isDismissable?: boolean- Allow dismiss by clicking outside (default: true)className?: string- Additional CSS classesclassNames?: object- Custom classes for modal parts
Usage:
const [isOpen, setIsOpen] = useState(false);
// Basic modal
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Modal Title"
>
<p>Modal content here</p>
</Modal>
// Modal with footer
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Confirm Action"
footer={
<>
<Button label="Cancel" variant="flat" onPress={() => setIsOpen(false)} />
<Button label="Confirm" variant="primary" />
</>
}
>
<p>Are you sure you want to proceed?</p>
</Modal>
// Large modal with blur backdrop
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
size="2xl"
backdrop="blur"
scrollBehavior="inside"
>
<div>Large modal content...</div>
</Modal>Notes:
- Automatically handles ESC key for closing
- Locks body scroll when open
- Accessible with keyboard navigation
Carousel
Image carousel/slider component using Swiper.
Import:
import { Carousel, type CarouselItem } from 'citrica-ui-toolkit';Props:
items: CarouselItem[]- Array of carousel items (required)autoplay?: boolean- Enable autoplay (default: false)autoplayDelay?: number- Autoplay delay in ms (default: 3000)showPagination?: boolean- Show pagination dots (default: true)showNavigation?: boolean- Show prev/next arrows (default: false)loop?: boolean- Enable infinite loop (default: false)className?: string- Additional CSS classesslideClassName?: string- CSS classes for slidesbreakpoints?: object- Custom responsive breakpoints
CarouselItem Interface:
interface CarouselItem {
id: string; // Unique identifier
src: string; // Image source URL
alt: string; // Image alt text
title?: string; // Optional title overlay
description?: string; // Optional description overlay
}Usage:
const items = [
{ id: "1", src: "/img/slide1.jpg", alt: "Slide 1", title: "First Slide" },
{ id: "2", src: "/img/slide2.jpg", alt: "Slide 2" },
{ id: "3", src: "/img/slide3.jpg", alt: "Slide 3", description: "Description" }
];
// Basic carousel
<Carousel items={items} />
// Autoplay carousel
<Carousel
items={items}
autoplay={true}
autoplayDelay={5000}
loop={true}
/>
// Carousel with navigation
<Carousel
items={items}
showNavigation={true}
showPagination={true}
/>
// Custom breakpoints
<Carousel
items={items}
breakpoints={{
640: { slidesPerView: 1, spaceBetween: 10 },
768: { slidesPerView: 2, spaceBetween: 20 },
1024: { slidesPerView: 3, spaceBetween: 30 }
}}
/>Default Breakpoints:
- 640px: 1 slide, centered
- 768px: 1 slide, centered
- 1024px: 3 slides, not centered
Notes:
- Requires Swiper CSS imports (included in component)
- Supports title/description overlays on images
Accordion
Collapsible content panels with admin mode support.
Import:
import { Accordion, type AccordionItemData } from 'citrica-ui-toolkit';Props:
items: AccordionItemData[]- Array of accordion items (required)variant?: 'light' | 'shadow' | 'bordered' | 'splitted'- Visual style (default: 'splitted')selectionMode?: 'single' | 'multiple' | 'none'- Expansion mode (default: 'multiple')defaultExpandedKeys?: Iterable<string | number> | 'all'- Initial expanded itemsexpandedKeys?: Iterable<string | number> | 'all'- Controlled expanded itemsdisabledKeys?: Iterable<string | number>- Disabled item keysonSelectionChange?: (keys: any) => void- Selection change handlerisCompact?: boolean- Compact sizing (default: false)isDisabled?: boolean- Disable whole accordion (default: false)hideIndicator?: boolean- Hide open/close indicator (default: false)showDivider?: boolean- Show divider between items (default: true)fullWidth?: boolean- Full width (default: true)className?: string- Additional CSS classesisAdmin?: boolean- Use admin theme colors (default: false)
AccordionItemData Interface:
interface AccordionItemData {
key: string | number; // Unique key
title: React.ReactNode; // Header (string auto-wrapped as subtitle bold)
content: React.ReactNode; // Panel content (string auto-wrapped as body)
subtitle?: React.ReactNode;
startContent?: React.ReactNode;
isDisabled?: boolean;
}Usage:
const items = [
{ key: "1", title: "¿Qué es Citrica UI?", content: "Una librería de componentes React." },
{ key: "2", title: "¿Cómo se instala?", content: "npm install citrica-ui-toolkit" },
];
// Basic accordion
<Accordion items={items} />
// Single selection with initial open
<Accordion
items={items}
selectionMode="single"
defaultExpandedKeys={["1"]}
variant="bordered"
/>
// Admin mode
<Accordion items={items} isAdmin={true} />CSS Classes Applied:
accordion-citrica-uioraccordion-citrica-ui-adminaccordion-item-citrica-ui,accordion-item-{variant}(or-admin)
ORGANISMS
Header
Responsive navigation header with multiple layout variants.
Import:
import { Header, type NavItem } from 'citrica-ui-toolkit';Props:
logo?: React.ReactNode- Custom logo componentvariant?: 'floating' | 'split' | 'basic'- Layout variant (default: 'basic')className?: string- Additional CSS classesshowButton?: boolean- Show CTA button (default: false)buttonText?: string- Button text (default: 'GET STARTED')onButtonClick?: () => void- Button click handlernavItems?: NavItem[]- Navigation items (default: [])
NavItem Interface:
interface NavItem {
title: string; // Display text
href: string; // Link or anchor (#section-id)
}Variants:
- basic: Traditional header with left logo, right navigation
- floating: Floating centered navigation pill with transparent background
- split: Logo centered, navigation split left/right
Usage:
const navItems = [
{ title: "Home", href: "#home" },
{ title: "About", href: "#about" },
{ title: "Services", href: "#services" },
{ title: "Contact", href: "#contact" }
];
// Basic header
<Header
variant="basic"
navItems={navItems}
showButton={true}
buttonText="Get Started"
/>
// Floating header (Matour style)
<Header
variant="floating"
logo={<img src="/logo.png" alt="Logo" />}
navItems={navItems}
showButton={true}
/>
// Split header (Flowblox style)
<Header
variant="split"
navItems={navItems}
showButton={true}
buttonText="Contact Us"
onButtonClick={() => alert('CTA clicked')}
/>Features:
- Responsive mobile menu with hamburger icon
- Smooth scroll to anchor links
- Background changes on scroll
- Fixed positioning with z-index 50
Footer
Footer component with logo, social links, and company info.
Import:
import { Footer } from 'citrica-ui-toolkit';Props:
logoSrc?: string- Company logo URL (default: '/img/home/Logo-galiz.png')logoAlt?: string- Logo alt text (default: 'Logo')companyName?: string- Company name (default: 'Gáliz Perú')year?: number- Copyright year (default: current year)developedByText?: string- "Developed by" text (default: 'Desarrollado por')devLogoSrc?: string- Developer logo URLdevLogoAlt?: string- Developer logo altsocialLinks?: object- Social media linksinstagram?: stringfacebook?: stringlinkedin?: string
instagramIconSrc?: string- Instagram icon URLfacebookIconSrc?: string- Facebook icon URLlinkedinIconSrc?: string- LinkedIn icon URLheight?: string- Footer height (default: '204px')backgroundColor?: string- Background color (overrides gradient)gradient?: string- Background gradient (default: linear gradient)className?: string- Additional CSS classes
Usage:
// Basic footer
<Footer
companyName="My Company"
socialLinks={{
instagram: "https://instagram.com/mycompany",
facebook: "https://facebook.com/mycompany",
linkedin: "https://linkedin.com/company/mycompany"
}}
/>
// Custom footer
<Footer
logoSrc="/my-logo.png"
logoAlt="My Company"
companyName="My Company Ltd"
year={2024}
developedByText="Built by"
devLogoSrc="/dev-logo.png"
backgroundColor="#0D1321"
height="250px"
socialLinks={{
instagram: "https://instagram.com/...",
linkedin: "https://linkedin.com/..."
}}
/>Layout:
- Uses Grid Container and Col for responsive layout
- 3 columns on large screens: logo, company info, social icons
- Responsive adjustments for mobile
Sidebar
Collapsible navigation sidebar with accordion support.
Import:
import { Sidebar, type MenuItem } from 'citrica-ui-toolkit';Props:
items: MenuItem[]- Navigation items (required)activeHref?: string- Currently active main itemactiveSubHref?: string- Currently active sub-itemonItemClick?: (href: string) => void- Item click handlerheader?: React.ReactNode- Optional content at the top of the panel (e.g. a logo)ariaLabel?: string- Accessible label for the nav (default: "Navegación principal")className?: string- Additional CSS classes
MenuItem Interface:
interface MenuItem {
title: string; // Item title
href?: string; // Link URL
icon?: string; // Lucide icon name (e.g. "Home", "Settings")
subItems?: SubMenuItem[]; // Optional sub-items (creates accordion)
}
interface SubMenuItem {
title: string; // Sub-item title
href: string; // Link URL
}Usage:
const menuItems = [
{ title: "Dashboard", href: "/dashboard", icon: "LayoutDashboard" },
{
title: "Products",
icon: "Package",
subItems: [
{ title: "All Products", href: "/products" },
{ title: "Add Product", href: "/products/add" },
{ title: "Categories", href: "/products/categories" }
]
},
{ title: "Settings", href: "/settings", icon: "Settings" }
];
// Basic sidebar
<Sidebar
items={menuItems}
activeHref="/dashboard"
onItemClick={(href) => router.push(href)}
/>
// Sidebar with active sub-item, header and aria label
<Sidebar
items={menuItems}
activeHref="/products"
activeSubHref="/products/add"
header={<img src="/logo.png" alt="Logo" />}
ariaLabel="Main navigation"
onItemClick={(href) => router.push(href)}
/>Features:
- Responsive: drawer on mobile (with floating menu trigger, overlay backdrop, Escape to close, body-scroll lock and focus management), fixed sticky sidebar on desktop
- Per-item Lucide icons via
MenuItem.icon - Optional
headerslot (e.g. logo) at the top of the panel - Accordion support for nested items: a group auto-opens when one of its sub-items is active, and a single click always toggles what's visible
- Active state highlighting for both main items and sub-items
- Built with
Button unstyledrows, so it carries no HeroUI button visuals
Theming (CSS variables):
The sidebar is themed entirely through CSS variables with sensible fallbacks to the admin design tokens (avoids hardcoded grays and the "white text on white background" bug). Override them via the style prop on the consuming wrapper if needed:
| Variable | Fallback token | Purpose |
| --- | --- | --- |
| --sb-bg | --color-admin-surface-container-lowest | Panel background |
| --sb-fg | --color-admin-on-surface | Default text |
| --sb-hover-bg | --color-admin-surface-container | Row hover background |
| --sb-active-bg | --color-admin-primary-container | Active row background |
| --sb-active-fg | --color-admin-on-primary-container | Active row text |
| --sb-accent | --color-admin-primary | Accent (dots, mobile trigger) |
| --sb-border | --color-admin-outline | Borders/dividers |
Styling
All components use custom CSS classes that should be defined in your application:
Component Base Classes
btn-citrica-ui,btn-citrica-ui-admininput-citrica-uiselect-citrica-ui,select-item-citrica-uiautocomplete-citrica-ui,autocomplete-item-citrica-uitextarea-citrica-ui
Variant Classes
- Button:
btn-primary,btn-secondary,btn-success,btn-warning,btn-danger,btn-flat,btn-link - Button extras:
btn-icon-only-citrica-ui(icon-only),btn-xs-citrica-ui(xs size),btn-hover-text/btn-hover-bg(custom hover colors) - Admin variants: Add
-adminsuffix (e.g.,btn-primary-admin) - Input/Select/Textarea:
input-primary,input-secondary, etc.
Text Classes
text-display,text-headline,text-title,text-subtitle,text-body,text-label- Weight modifiers:
text-{variant}-light,text-{variant}-bold - Admin variants: Add
-adminsuffix
Color Classes
color-black,color-whitecolor-on-primary,color-on-secondary,color-on-success, etc.- Admin colors:
color-admin-on-primary, etc.
Grid Classes
o-container,no-padding,no-width-limito-col-{1-12}@lg,o-col-{1-6}@md,o-col-{1-4}@smo-col-push-{n}@{breakpoint}
TypeScript Support
All components are fully typed with TypeScript. Import types as needed:
import type {
ButtonProps,
InputProps,
SelectOption,
AutocompleteOption,
CarouselItem,
NavItem,
MenuItem
} from 'citrica-ui-toolkit';Important Notes
- Custom Variants: Components with
primaryandsecondaryvariants internally map to HeroUI'sborderedvariant for consistent custom styling - Admin Mode: Button and Text components support
isAdminprop for alternate color schemes - Icon Names: Icon component uses Lucide React icon names (e.g., "Home", "User", "Settings")
- Refs: Input component uses
forwardReffor compatibility with form libraries like react-hook-form - CSS Requirements: You must define custom CSS classes in your application for proper styling
Build & Development
# Development mode (watch)
npm run dev
# Production build
npm run buildBuild outputs to dist/ with:
dist/index.js(CommonJS)dist/index.mjs(ES Module)dist/index.d.ts(TypeScript definitions)
Login
Multi-mode authentication component with login, forgot password, and new password flows.
Import:
import { Login, type LoginMode } from 'citrica-ui-toolkit';Props:
mode?: LoginMode- Display mode: 'login' | 'forgot-password' | 'new-password' (default: 'login')- Styling
bgImage?: string- Background image URL for right section (default: '/img/login-form-image-lg.jpg')logo?: string- Logo image URL (default: '/img/citrica-logo.png')logoAlt?: string- Logo alt text (default: 'Logo')logoClassName?: string- Logo CSS classes (default: 'w-[80px] pb-3 items-center')className?: string- Additional CSS classes
- Content - Login mode
title?: string- Main title (default: '¡Bienvenido!')subtitle?: string- Subtitle text (default: 'Ingresa tu correo electrónico y contraseña')emailLabel?: string- Email input label (default: 'Email')passwordLabel?: string- Password input label (default: 'Clave')emailPlaceholder?: string- Email placeholder (default: 'Correo electrónico')passwordPlaceholder?: string- Password placeholder (default: 'Contraseña')loginButtonText?: string- Login button text (default: 'Iniciar sesión')loadingButtonText?: string- Loading state text (default: 'Accediendo...')
- Content - Forgot Password mode
forgotPasswordTitle?: string- Title (default: '¿No puedes iniciar sesión?')forgotPasswordSubtitle?: string- SubtitleforgotPasswordDescription?: string- Description textsendLinkButtonText?: string- Send link button text (default: 'Enviar enlace')sendingLinkButtonText?: string- Sending state text (default: 'Enviando...')backToLoginText?: string- Back to login link text (default: '¿Olvidaste tu contraseña?')
- Content - New Password mode
newPasswordTitle?: string- Title (default: 'Crea una nueva contraseña')newPasswordSubtitle?: string- SubtitlenewPasswordLabel?: string- Password input label (default: 'Nueva contraseña')newPasswordPlaceholder?: string- Password placeholdersavePasswordButtonText?: string- Save button text (default: 'Guardar contraseña')savingPasswordButtonText?: string- Saving state text (default: 'Guardando...')cancelText?: string- Cancel link text (default: 'Cancelar')
- Actions - Login mode
onLogin?: (email: string, password: string) => Promise<{error?: boolean; message?: string}>- Login handleronForgotPasswordClick?: () => void- Forgot password click handler
- Actions - Forgot Password mode
onSendResetLink?: (email: string) => Promise<{error?: boolean; message?: string}>- Send reset link handleronBackToLogin?: () => void- Back to login handler
- Actions - New Password mode
onSaveNewPassword?: (password: string) => Promise<{error?: boolean; message?: string}>- Save password handleronCancel?: () => void- Cancel handler
- Toggles
showForgotPassword?: boolean- Show forgot password link (default: true)
- Error messages
emptyFieldsError?: string- Empty fields error (default: 'Por favor ingresa tu correo y contraseña')loginError?: string- Login error (default: 'Correo o contraseña incorrectos')emptyEmailError?: string- Empty email errorresetLinkError?: string- Reset link erroremptyPasswordError?: string- Empty password errorsavePasswordError?: string- Save password error
Usage:
"use client";
import { useState } from "react";
import { Login, LoginMode } from "citrica-ui-toolkit";
export default function LoginPage() {
const [mode, setMode] = useState<LoginMode>("login");
const handleLogin = async (email: string, password: string) => {
// Your login logic
const response = await yourAuthService.login(email, password);
if (response.success) {
return { error: false, message: "Login exitoso" };
} else {
return { error: true, message: "Email o contraseña incorrectos" };
}
};
const handleForgotPasswordClick = () => {
setMode("forgot-password");
};
const handleSendResetLink = async (email: string) => {
// Your reset link logic
await yourAuthService.sendResetLink(email);
return { error: false, message: "Enlace enviado exitosamente" };
};
const handleBackToLogin = () => {
setMode("login");
};
const handleSaveNewPassword = async (password: string) => {
// Your save password logic
await yourAuthService.savePassword(password);
setMode("login");
return { error: false, message: "Contraseña guardada" };
};
return (
<div className="w-full min-h-screen flex items-center justify-center bg-gray-100">
<Login
mode={mode}
bgImage="https://your-image-url.com/background.jpg"
logo="/your-logo.png"
// Actions - Login mode
onLogin={handleLogin}
onForgotPasswordClick={handleForgotPasswordClick}
// Actions - Forgot Password mode
onSendResetLink={handleSendResetLink}
onBackToLogin={handleBackToLogin}
// Actions - New Password mode
onSaveNewPassword={handleSaveNewPassword}
onCancel={handleBackToLogin}
/>
</div>
);
}Features:
- Three modes: login, forgot password, and new password
- Two-column layout: form on left, background image on right
- Responsive design (image hidden on mobile with
md:block) - Built-in password visibility toggle
- Input validation and error messages
- Loading states for async operations
- Inline CSS styling for consistent design
- Uses Input variant="faded" with custom classNames
Layout:
- Fixed width container (968px)
- Left section: White form container (484px max) with border and shadow
- Right section: Background image (484px max) with rounded corners
- Form uses gap-4 spacing between inputs and button
Styling Notes:
- Component has inline styles that don't require external CSS
- Form inputs use HeroUI's "faded" variant with custom colors:
- Border:
#D4DEED, hover:#265197 - Labels:
#FF5B00(orange) - Input text:
#265197(blue) - Placeholder:
#A7BDE2(light blue)
- Border:
License
ISC
