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
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Label } from '@/components/ui/label';
import { FlowCheckbox } from './FlowCheckbox';

// ============================================================================
// Meta
// ============================================================================

const meta = {
title: 'Components/UiPath/Flow Checkbox',
component: FlowCheckbox,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof FlowCheckbox>;

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

// ============================================================================
// Stories
// ============================================================================

export const Default: Story = {};

export const Checked: Story = {
args: {
defaultChecked: true,
},
};

export const WithLabel: Story = {
render: () => (
<div className="flex items-center gap-2">
<FlowCheckbox id="terms" />
<Label htmlFor="terms">Accept terms and conditions</Label>
</div>
),
};

export const Disabled: Story = {
render: () => (
<div className="flex items-center gap-2">
<FlowCheckbox id="disabled" disabled />
<Label htmlFor="disabled">Disabled</Label>
</div>
),
};

export const DisabledChecked: Story = {
render: () => (
<div className="flex items-center gap-2">
<FlowCheckbox id="disabled-checked" disabled defaultChecked />
<Label htmlFor="disabled-checked">Disabled and checked</Label>
</div>
),
};

export const WithDescription: Story = {
render: () => (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<FlowCheckbox id="marketing" />
<Label htmlFor="marketing">Marketing emails</Label>
</div>
<p className="pl-6 text-sm text-foreground-subtle">
Receive emails about new products, features, and more.
</p>
</div>
),
};

export const Group: Story = {
render: () => (
<div className="flex flex-col gap-3">
<span className="text-sm font-medium text-foreground">Notification preferences</span>
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<FlowCheckbox id="all" defaultChecked />
<Label htmlFor="all">All notifications</Label>
</div>
<div className="flex items-center gap-2">
<FlowCheckbox id="email" defaultChecked />
<Label htmlFor="email">Email notifications</Label>
</div>
<div className="flex items-center gap-2">
<FlowCheckbox id="push" />
<Label htmlFor="push">Push notifications</Label>
</div>
<div className="flex items-center gap-2">
<FlowCheckbox id="sms" />
<Label htmlFor="sms">SMS notifications</Label>
</div>
</div>
</div>
),
};
33 changes: 33 additions & 0 deletions packages/apollo-wind/src/components/UiPath/FlowCheckbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { Check } from 'lucide-react';
import * as React from 'react';
import { cn } from '@/lib';

export interface FlowCheckboxProps
extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> {}

const FlowCheckbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
FlowCheckboxProps
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
'peer h-4 w-4 shrink-0 cursor-pointer rounded-sm border border-border',
'ring-offset-background',
'hover:border-border-hover',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
'data-[state=checked]:border-foreground-accent data-[state=checked]:bg-foreground-accent',
'disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...props}
>
<CheckboxPrimitive.Indicator className="flex items-center justify-center text-foreground-inverse">
<Check className="h-3.5 w-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
FlowCheckbox.displayName = CheckboxPrimitive.Root.displayName;

export { FlowCheckbox };
100 changes: 100 additions & 0 deletions packages/apollo-wind/src/components/UiPath/FlowInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Check, CreditCard, Info, Loader2, Mail, Search } from 'lucide-react';
import { FlowInput, FlowInputAddon, FlowInputGroup } from './FlowInput';

// Classes to reset FlowInput container styles when used inside a FlowInputGroup
const groupInputCn = 'flex-1 border-0 bg-transparent p-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0';

const meta = {
title: 'Components/UiPath/Flow Input',
component: FlowInput,
parameters: { layout: 'centered' },
tags: ['autodocs'],
} satisfies Meta<typeof FlowInput>;

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

export const Default: Story = {
args: { placeholder: 'Enter text...' },
};

export const Disabled: Story = {
args: { placeholder: 'Disabled input', disabled: true },
};

export const WithValue: Story = {
args: { defaultValue: 'https://x.com/shadcn' },
};

export const WithLeadingIcon: StoryObj = {
render: () => (
<FlowInputGroup className="w-[280px]">
<FlowInputAddon><Search /></FlowInputAddon>
<FlowInput className={groupInputCn} placeholder="Search..." />
<FlowInputAddon>12 results</FlowInputAddon>
</FlowInputGroup>
),
};

export const WithLeadingText: StoryObj = {
render: () => (
<FlowInputGroup className="w-[280px]">
<FlowInputAddon>https://</FlowInputAddon>
<FlowInput className={groupInputCn} placeholder="example.com" />
<FlowInputAddon><Info /></FlowInputAddon>
</FlowInputGroup>
),
};

export const WithTrailingStatus: StoryObj = {
render: () => (
<FlowInputGroup className="w-[280px]">
<FlowInputAddon>https://</FlowInputAddon>
<FlowInput className={groupInputCn} defaultValue="@shadcn" />
<FlowInputAddon>
<span className="flex size-4 items-center justify-center rounded-full bg-surface-hover">
<Check className="size-3 text-foreground" />
</span>
</FlowInputAddon>
</FlowInputGroup>
),
};

export const WithLeadingIconOnly: StoryObj = {
render: () => (
<FlowInputGroup className="w-[280px]">
<FlowInputAddon><Mail /></FlowInputAddon>
<FlowInput className={groupInputCn} placeholder="Enter your email..." />
</FlowInputGroup>
),
};

export const WithCurrencyAddons: StoryObj = {
render: () => (
<FlowInputGroup className="w-[280px]">
<FlowInputAddon>$</FlowInputAddon>
<FlowInput className={groupInputCn} placeholder="0.00" />
<FlowInputAddon>USD</FlowInputAddon>
</FlowInputGroup>
),
};

export const WithCreditCard: StoryObj = {
render: () => (
<FlowInputGroup className="w-[280px]">
<FlowInputAddon><CreditCard /></FlowInputAddon>
<FlowInput className={groupInputCn} placeholder="Card number" />
<FlowInputAddon><Check /></FlowInputAddon>
</FlowInputGroup>
),
};

export const Loading: StoryObj = {
render: () => (
<FlowInputGroup className="w-[280px]">
<FlowInput className={groupInputCn} placeholder="Searching..." disabled />
<FlowInputAddon><Loader2 className="animate-spin" /></FlowInputAddon>
</FlowInputGroup>
),
};
61 changes: 61 additions & 0 deletions packages/apollo-wind/src/components/UiPath/FlowInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import * as React from 'react';
import { cn } from '@/lib';

export interface FlowInputProps extends React.InputHTMLAttributes<HTMLInputElement> {}

const FlowInput = React.forwardRef<HTMLInputElement, FlowInputProps>(
({ className, type, ...props }, ref) => (
<input
type={type}
className={cn(
'flex h-10 w-full rounded-xl bg-surface-overlay px-3 py-2',
'text-sm text-foreground placeholder:text-foreground-muted placeholder:font-normal',
'transition-colors',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
'disabled:cursor-not-allowed disabled:opacity-50',
'file:border-0 file:bg-transparent file:text-sm file:font-medium',
className
)}
ref={ref}
{...props}
/>
)
);
FlowInput.displayName = 'FlowInput';

// Wraps FlowInput with inline addons (icons, text). Takes ownership of the
// container styles — use FlowInput with className overrides inside.
const FlowInputGroup = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
'flex h-10 w-full items-center gap-2 overflow-hidden rounded-xl bg-surface-overlay px-3 py-2',
'transition-colors',
'focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 focus-within:ring-offset-background',
className
)}
{...props}
/>
)
);
FlowInputGroup.displayName = 'FlowInputGroup';

// Inline slot for icons or text on either side of the input inside a group.
const FlowInputAddon = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
'flex shrink-0 items-center justify-center',
'text-sm font-medium text-foreground-muted whitespace-nowrap',
'[&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-foreground-muted',
className
)}
{...props}
/>
)
);
FlowInputAddon.displayName = 'FlowInputAddon';

export { FlowInput, FlowInputGroup, FlowInputAddon };
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Label } from '@/components/ui/label';
import { FlowRadioGroup, FlowRadioGroupItem } from './FlowRadioGroup';

// ============================================================================
// Meta
// ============================================================================

const meta = {
title: 'Components/UiPath/Flow Radio Group',
component: FlowRadioGroup,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof FlowRadioGroup>;

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

// ============================================================================
// Stories
// ============================================================================

export const Default: Story = {
render: () => (
<FlowRadioGroup defaultValue="comfortable">
<div className="flex items-center gap-2">
<FlowRadioGroupItem value="default" id="r1" />
<Label htmlFor="r1">Default</Label>
</div>
<div className="flex items-center gap-2">
<FlowRadioGroupItem value="comfortable" id="r2" />
<Label htmlFor="r2">Comfortable</Label>
</div>
<div className="flex items-center gap-2">
<FlowRadioGroupItem value="compact" id="r3" />
<Label htmlFor="r3">Compact</Label>
</div>
</FlowRadioGroup>
),
};

export const Disabled: Story = {
render: () => (
<FlowRadioGroup defaultValue="option-one">
<div className="flex items-center gap-2">
<FlowRadioGroupItem value="option-one" id="d1" />
<Label htmlFor="d1">Option one</Label>
</div>
<div className="flex items-center gap-2">
<FlowRadioGroupItem value="option-two" id="d2" disabled />
<Label htmlFor="d2" className="opacity-50">
Option two (disabled)
</Label>
</div>
</FlowRadioGroup>
),
};

export const WithDescription: Story = {
render: () => (
<FlowRadioGroup defaultValue="card">
<div className="flex items-start gap-3">
<FlowRadioGroupItem value="card" id="p1" className="mt-0.5" />
<div className="flex flex-col gap-0.5">
<Label htmlFor="p1">Card</Label>
<span className="text-xs text-foreground-subtle">Pay with credit or debit card.</span>
</div>
</div>
<div className="flex items-start gap-3">
<FlowRadioGroupItem value="paypal" id="p2" className="mt-0.5" />
<div className="flex flex-col gap-0.5">
<Label htmlFor="p2">Paypal</Label>
<span className="text-xs text-foreground-subtle">Pay with your Paypal account.</span>
</div>
</div>
</FlowRadioGroup>
),
};
Loading
Loading