Skip to content
Merged
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
257 changes: 257 additions & 0 deletions src/Toolkit/kits/shadcn/input-group/EXAMPLES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
# Examples

## Icon

Use `InputGroup:Icon` to add a leading or trailing icon to an input. Padding is applied automatically.

```twig {"preview":true}
<div class="max-w-md">
<twig:InputGroup>
<twig:InputGroup:Icon>
{{ ux_icon('lucide:search') }}
</twig:InputGroup:Icon>
<twig:InputGroup:Input
id="search"
name="query"
type="search"
placeholder="Search..."
/>
</twig:InputGroup>
</div>
```

## Trailing icon

Position the icon at the end using `position="end"`.

```twig {"preview":true}
<div class="max-w-md">
<twig:InputGroup>
<twig:InputGroup:Input
id="email"
name="email"
type="email"
placeholder="Enter your email"
/>
<twig:InputGroup:Icon position="end">
{{ ux_icon('lucide:mail') }}
</twig:InputGroup:Icon>
</twig:InputGroup>
</div>
```

## Both icons

Combine leading and trailing icons.

```twig {"preview":true}
<div class="max-w-md">
<twig:InputGroup>
<twig:InputGroup:Icon>
{{ ux_icon('lucide:user') }}
</twig:InputGroup:Icon>
<twig:InputGroup:Input
id="username"
name="username"
placeholder="Username"
/>
<twig:InputGroup:Icon position="end">
{{ ux_icon('lucide:check') }}
</twig:InputGroup:Icon>
</twig:InputGroup>
</div>
```

## Two trailing icons

Group multiple icons together in a single `InputGroup:Icon`. Add extra padding to accommodate the additional icons.

```twig {"preview":true}
<div class="max-w-md">
<twig:InputGroup>
<twig:InputGroup:Input
id="password"
name="password"
type="password"
placeholder="Enter password"
class="pr-16"
/>
<twig:InputGroup:Icon position="end" class="gap-1">
{{ ux_icon('lucide:eye', { class: 'cursor-pointer pointer-events-auto hover:text-foreground' }) }}
{{ ux_icon('lucide:copy', { class: 'cursor-pointer pointer-events-auto hover:text-foreground' }) }}
</twig:InputGroup:Icon>
</twig:InputGroup>
</div>
```

## Search with icon and shortcut

Combine icons with other elements like keyboard hints.

```twig {"preview":true}
<div class="max-w-md">
<twig:InputGroup>
<twig:InputGroup:Icon>
{{ ux_icon('lucide:search') }}
</twig:InputGroup:Icon>
<twig:InputGroup:Input
id="search"
name="query"
type="search"
placeholder="Search documentation..."
class="pr-14"
/>
<twig:Kbd class="hidden lg:inline-flex pointer-events-none absolute right-[9px] top-1/2 h-5 -translate-y-1/2">
⌘K
</twig:Kbd>
</twig:InputGroup>
</div>
```

## Textarea

Use `InputGroup:Textarea` for multi-line input. Use `align="start"` on icons to position them at the top.

```twig {"preview":true}
<div class="max-w-md">
<twig:InputGroup>
<twig:InputGroup:Icon align="start">
{{ ux_icon('lucide:message-square') }}
</twig:InputGroup:Icon>
<twig:InputGroup:Textarea
id="message"
name="message"
placeholder="Type your message..."
rows="4"
/>
</twig:InputGroup>
</div>
```

## Textarea with trailing icon

Position an icon at the end of a textarea.

```twig {"preview":true}
<div class="max-w-md">
<twig:InputGroup>
<twig:InputGroup:Textarea
id="notes"
name="notes"
placeholder="Add notes..."
rows="3"
/>
<twig:InputGroup:Icon position="end" align="start">
{{ ux_icon('lucide:pencil') }}
</twig:InputGroup:Icon>
</twig:InputGroup>
</div>
```

## Textarea with character count

Use `InputGroup:Addon` with `align="block-end"` to add content below the textarea. Add `class="border-t"` for a separator line.

```twig {"preview":true, "height":"300px"}
<div class="max-w-md">
<twig:InputGroup>
<twig:InputGroup:Textarea
id="message"
name="message"
placeholder="Enter your message"
rows="4"
/>
<twig:InputGroup:Addon align="block-end" class="border-t">
<twig:InputGroup:Text class="text-xs">
120 characters left
</twig:InputGroup:Text>
</twig:InputGroup:Addon>
</twig:InputGroup>
</div>
```

## Addon with icon

Use `InputGroup:Addon` for flexible positioning of icons and other elements.

```twig {"preview":true}
<div class="max-w-md">
<twig:InputGroup>
<twig:InputGroup:Addon>
{{ ux_icon('lucide:dollar-sign') }}
</twig:InputGroup:Addon>
<twig:InputGroup:Input
id="price"
name="price"
type="number"
placeholder="0.00"
/>
<twig:InputGroup:Addon align="inline-end">
<twig:InputGroup:Text>USD</twig:InputGroup:Text>
</twig:InputGroup:Addon>
</twig:InputGroup>
</div>
```

## Block start addon

Position content above the input using `align="block-start"`. Use `border-b` for a separator below and `border-t` for a separator above.

```twig {"preview":true, "height":"300px"}
<div class="max-w-md">
<twig:InputGroup>
<twig:InputGroup:Addon align="block-start" class="border-b">
<twig:InputGroup:Text class="text-xs font-semibold">Bio</twig:InputGroup:Text>
</twig:InputGroup:Addon>
<twig:InputGroup:Textarea
id="bio"
name="bio"
placeholder="Tell us about yourself..."
rows="3"
/>
<twig:InputGroup:Addon align="block-end" class="justify-end border-t">
<twig:InputGroup:Text class="text-xs">Max 500 characters</twig:InputGroup:Text>
</twig:InputGroup:Addon>
</twig:InputGroup>
</div>
```

## Spinner

Show loading indicators while processing input.

```twig {"preview":true, "height":"300px"}
<div class="max-w-md flex flex-col gap-4">
<twig:InputGroup data-disabled="true">
<twig:InputGroup:Input placeholder="Searching..." disabled />
<twig:InputGroup:Addon align="inline-end">
<twig:Spinner />
</twig:InputGroup:Addon>
</twig:InputGroup>

<twig:InputGroup data-disabled="true">
<twig:InputGroup:Input placeholder="Processing..." disabled />
<twig:InputGroup:Addon>
<twig:Spinner />
</twig:InputGroup:Addon>
</twig:InputGroup>

<twig:InputGroup data-disabled="true">
<twig:InputGroup:Input placeholder="Saving changes..." disabled />
<twig:InputGroup:Addon align="inline-end">
<twig:InputGroup:Text>Saving...</twig:InputGroup:Text>
<twig:Spinner />
</twig:InputGroup:Addon>
</twig:InputGroup>

<twig:InputGroup data-disabled="true">
<twig:InputGroup:Input placeholder="Refreshing data..." disabled />
<twig:InputGroup:Addon>
{{ ux_icon('lucide:loader', { class: 'animate-spin' }) }}
</twig:InputGroup:Addon>
<twig:InputGroup:Addon align="inline-end">
<twig:InputGroup:Text class="text-muted-foreground">Please wait...</twig:InputGroup:Text>
</twig:InputGroup:Addon>
</twig:InputGroup>
</div>
```
12 changes: 12 additions & 0 deletions src/Toolkit/kits/shadcn/input-group/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "../../../schema-kit-recipe-v1.json",
"type": "component",
"name": "Input Group",
"description": "Wraps inputs with optional leading or trailing elements like icons or keyboard hints.",
"copy-files": {
"templates/": "templates/"
},
"dependencies": {
"recipe": ["spinner"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{# @block content The default block #}
{# Icon padding - simpler selectors #}
{%- set iconStartPadding = 'has-[[data-position=start]]:[&_[data-slot=input-group-control]]:pl-9' -%}
{%- set iconEndPadding = 'has-[[data-position=end]]:[&_[data-slot=input-group-control]]:pr-9' -%}
{# Inline addon padding #}
{%- set addonStartPadding = 'has-[[data-align=inline-start]]:[&_[data-slot=input-group-control]]:pl-2' -%}
{%- set addonEndPadding = 'has-[[data-align=inline-end]]:[&_[data-slot=input-group-control]]:pr-2' -%}
{# Block addon layout - auto height and flex-col #}
{%- set blockLayout = 'has-[[data-align=block-start]]:h-auto has-[[data-align=block-start]]:flex-col has-[[data-align=block-end]]:h-auto has-[[data-align=block-end]]:flex-col' -%}
{# Focus state on parent when child control is focused #}
{%- set focusState = 'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]' -%}
<div
data-slot="input-group"
class="{{ ('group/input-group relative flex h-10 w-full items-center rounded-md border border-input bg-background shadow-xs transition-[color,box-shadow] has-[>textarea]:h-auto ' ~ iconStartPadding ~ ' ' ~ iconEndPadding ~ ' ' ~ addonStartPadding ~ ' ' ~ addonEndPadding ~ ' ' ~ blockLayout ~ ' ' ~ focusState ~ ' ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{# @prop align 'inline-start'|'inline-end'|'block-start'|'block-end' The addon alignment, default to `inline-start` #}
{# @block content The default block #}
{%- props align = 'inline-start' -%}
{%- set style = html_cva(
base: "text-muted-foreground flex h-auto items-center justify-center gap-2 text-sm font-medium select-none [&_svg:not([class*='size-'])]:size-4 group-data-[disabled=true]/input-group:opacity-50",
variants: {
align: {
'inline-start': 'order-first pl-3',
'inline-end': 'order-last pr-3',
'block-start': 'order-first w-full justify-start px-3 pt-3 [&.border-b]:pb-3',
'block-end': 'order-last w-full justify-start px-3 pb-3 [&.border-t]:pt-3',
},
},
) -%}
<div
data-slot="input-group-addon"
data-align="{{ align }}"
role="group"
class="{{ style.apply({align: align}, attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{# @prop position 'start'|'end' The icon position, default to `start` #}
{# @prop align 'center'|'start' The vertical alignment, default to `center` #}
{# @block content The default block #}
{%- props position = 'start', align = 'center' -%}
{%- set style = html_cva(
base: "pointer-events-none absolute flex text-muted-foreground [&_svg:not([class*='size-'])]:size-4",
variants: {
position: {
start: 'left-0 pl-3',
end: 'right-0 pr-3',
},
align: {
center: 'inset-y-0 items-center',
start: 'top-0 pt-3',
},
},
) -%}
<span
data-slot="input-group-icon"
data-position="{{ position }}"
class="{{ style.apply({position: position, align: align}, attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</span>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<input
data-slot="input-group-control"
class="{{ ('flex-1 h-full w-full rounded-none border-0 bg-transparent px-3 py-2 text-sm shadow-none file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50 ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{# @block content The default block #}
<span
data-slot="input-group-text"
class="{{ ('text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*=size-])]:size-4 ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</span>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{# @block content The default block #}
<textarea
data-slot="input-group-control"
class="{{ ('flex-1 min-h-[80px] w-full resize-none rounded-none border-0 bg-transparent px-3 py-3 text-base shadow-none placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</textarea>
Loading
Loading