@stackloop/ui 4.1.1 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -14
- package/dist/ButtonGroup.d.ts +9 -0
- package/dist/DropdownMenu.d.ts +50 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2514 -2204
- package/dist/index.js.map +1 -1
- package/dist/stackloop-ui.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ import '@stackloop/ui/theme.css'
|
|
|
73
73
|
Import components from the package root:
|
|
74
74
|
|
|
75
75
|
```tsx
|
|
76
|
-
import { Button, Modal, Input } from '@stackloop/ui'
|
|
76
|
+
import { Button, ButtonGroup, Modal, Input } from '@stackloop/ui'
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
All components are **client-side** components with `'use client'` directive, making them compatible with Next.js App Router.
|
|
@@ -160,7 +160,7 @@ import { Button, Modal } from '@stackloop/ui'
|
|
|
160
160
|
|
|
161
161
|
Components with the `animate` prop:
|
|
162
162
|
|
|
163
|
-
- `Accordion`, `AudioRecorder`, `Badge`, `BottomSheet`, `Button`, `Card`, `CameraCapture`, `Checkbox`, `CountrySelect`, `DatePicker`, `Drawer`, `Dropdown`, `DualSlider`, `FileUploader`, `FloatingActionButton`, `Input`, `Modal`, `MultiSelect`, `Pagination`, `PhoneInput`, `RadioPills`, `Select`, `Slider`, `Spinner`, `StepProgress`, `Table`, `Textarea`, `ThumbnailGrid`, `Toggle`, `ToastProvider`
|
|
163
|
+
- `Accordion`, `AudioRecorder`, `Badge`, `BottomSheet`, `Button`, `ButtonGroup`, `Card`, `CameraCapture`, `Checkbox`, `CountrySelect`, `DatePicker`, `Drawer`, `Dropdown`, `DualSlider`, `FileUploader`, `FloatingActionButton`, `Input`, `Modal`, `MultiSelect`, `Pagination`, `PhoneInput`, `RadioPills`, `Select`, `Slider`, `Spinner`, `StepProgress`, `Table`, `Textarea`, `ThumbnailGrid`, `Toggle`, `ToastProvider`
|
|
164
164
|
|
|
165
165
|
## Ripple Behavior
|
|
166
166
|
|
|
@@ -222,6 +222,69 @@ Call `setupRippleEffects()` only once per app (for example in `main.tsx`) to avo
|
|
|
222
222
|
<Button variant="outline" size="lg" onClick={() => {}}>Save</Button>
|
|
223
223
|
```
|
|
224
224
|
|
|
225
|
+
**ButtonGroup**:
|
|
226
|
+
- **Description:** Segmented button group with a rounded outer container and separate buttons divided by borders.
|
|
227
|
+
- **Props:**
|
|
228
|
+
- **`options`**: `ButtonGroupOption[]` — required.
|
|
229
|
+
- `value`: `string` — required unique option value.
|
|
230
|
+
- `label`: `string` — required fallback text label.
|
|
231
|
+
- `icon`: `ReactNode` — optional leading icon.
|
|
232
|
+
- `disabled`: `boolean` — optional per-option disabled state.
|
|
233
|
+
- `className`: `string` — optional per-option class override.
|
|
234
|
+
- `ariaLabel`: `string` — optional aria-label for accessibility.
|
|
235
|
+
- `render`: `(option, state) => ReactNode` — optional custom content renderer. If omitted, the default icon + label layout is used.
|
|
236
|
+
- `onClick`: `(option, event) => void` — optional per-option click callback.
|
|
237
|
+
- **`value`**: `string` — optional selected value.
|
|
238
|
+
- **`onChange`**: `(value: string) => void` — optional change callback.
|
|
239
|
+
- **`onOptionClick`**: `(option, index) => void` — optional group-level callback for any option click.
|
|
240
|
+
- **`size`**: `'sm' | 'md' | 'lg'` — default: `'md'`.
|
|
241
|
+
- **`disabled`**: `boolean` — default: `false`.
|
|
242
|
+
- **`className`**: `string` — optional.
|
|
243
|
+
- **`animate`**: `boolean` — default: `true`.
|
|
244
|
+
- **Usage:**
|
|
245
|
+
|
|
246
|
+
```jsx
|
|
247
|
+
import { ButtonGroup } from '@stackloop/ui'
|
|
248
|
+
|
|
249
|
+
const options = [
|
|
250
|
+
{ label: 'Day', value: 'day' },
|
|
251
|
+
{ label: 'Week', value: 'week' },
|
|
252
|
+
{ label: 'Month', value: 'month' }
|
|
253
|
+
]
|
|
254
|
+
|
|
255
|
+
<ButtonGroup
|
|
256
|
+
options={options}
|
|
257
|
+
value={selectedRange}
|
|
258
|
+
onChange={setSelectedRange}
|
|
259
|
+
/>
|
|
260
|
+
|
|
261
|
+
// Custom render + per-option and group-level click hooks
|
|
262
|
+
const customOptions = [
|
|
263
|
+
{
|
|
264
|
+
label: 'Overview',
|
|
265
|
+
value: 'overview',
|
|
266
|
+
render: (option, state) => (
|
|
267
|
+
<span className="flex flex-col items-start leading-tight">
|
|
268
|
+
<span className="text-[10px] uppercase tracking-[0.2em] opacity-70">View</span>
|
|
269
|
+
<span className={state.selected ? 'font-semibold' : 'font-medium'}>{option.label}</span>
|
|
270
|
+
</span>
|
|
271
|
+
),
|
|
272
|
+
onClick: (option) => console.log('Option clicked:', option.value)
|
|
273
|
+
},
|
|
274
|
+
{ label: 'Details', value: 'details' },
|
|
275
|
+
{ label: 'Activity', value: 'activity' }
|
|
276
|
+
]
|
|
277
|
+
|
|
278
|
+
<ButtonGroup
|
|
279
|
+
options={customOptions}
|
|
280
|
+
value={selectedView}
|
|
281
|
+
onChange={setSelectedView}
|
|
282
|
+
onOptionClick={(option, index) => {
|
|
283
|
+
console.log('Group handler:', option.value, index)
|
|
284
|
+
}}
|
|
285
|
+
/>
|
|
286
|
+
```
|
|
287
|
+
|
|
225
288
|
**Input**:
|
|
226
289
|
- **Description:** Unified input API with smart type routing. Supports native text/password/email/etc plus `phone`, `country`, and `date` while keeping a consistent `value` + `onChange` pattern.
|
|
227
290
|
- **Props:**
|
|
@@ -624,20 +687,72 @@ Call `setupRippleEffects()` only once per app (for example in `main.tsx`) to avo
|
|
|
624
687
|
/>
|
|
625
688
|
```
|
|
626
689
|
|
|
627
|
-
**
|
|
628
|
-
- **Description:**
|
|
629
|
-
- **
|
|
630
|
-
-
|
|
631
|
-
-
|
|
632
|
-
|
|
633
|
-
-
|
|
634
|
-
-
|
|
690
|
+
**DropdownMenu**:
|
|
691
|
+
- **Description:** Headless-style dropdown menu primitive with custom trigger support, outside-click close behavior, and nested submenus.
|
|
692
|
+
- **Placement behavior:**
|
|
693
|
+
- Root menu opens **down or up** based on viewport space (or can be forced).
|
|
694
|
+
- Nested submenus open **right or left** based on side space (or can be forced).
|
|
695
|
+
- **Use case:**
|
|
696
|
+
- Nested navigation or action menus where each menu item can itself open another submenu.
|
|
697
|
+
- Context menus launched from custom triggers like icon buttons, cards, or toolbar actions.
|
|
698
|
+
- **Core Components:**
|
|
699
|
+
- **`DropdownMenu`**: Root wrapper with controlled/uncontrolled open state.
|
|
700
|
+
- **`DropdownMenuTrigger`**: Trigger button or custom trigger with `asChild`.
|
|
701
|
+
- **`DropdownMenuContent`**: Root menu panel.
|
|
702
|
+
- **`DropdownMenuItem`**: Clickable item (button or custom child with `asChild`).
|
|
703
|
+
- **`DropdownMenuSub`**: Nested menu wrapper.
|
|
704
|
+
- **`DropdownMenuSubTrigger`**: Trigger for nested menu.
|
|
705
|
+
- **`DropdownMenuSubContent`**: Nested panel.
|
|
706
|
+
- **Root Props (`DropdownMenu`):**
|
|
707
|
+
- **`open`**: `boolean` — optional controlled state.
|
|
708
|
+
- **`defaultOpen`**: `boolean` — default: `false`.
|
|
709
|
+
- **`onOpenChange`**: `(open: boolean) => void` — optional callback.
|
|
710
|
+
- **`placement`**: `'auto' | 'top' | 'bottom'` — default: `'auto'`.
|
|
711
|
+
- **`className`**: `string` — optional.
|
|
635
712
|
- **Usage:**
|
|
636
713
|
|
|
637
714
|
```jsx
|
|
638
|
-
import {
|
|
639
|
-
|
|
640
|
-
|
|
715
|
+
import {
|
|
716
|
+
DropdownMenu,
|
|
717
|
+
DropdownMenuTrigger,
|
|
718
|
+
DropdownMenuContent,
|
|
719
|
+
DropdownMenuItem,
|
|
720
|
+
DropdownMenuSub,
|
|
721
|
+
DropdownMenuSubTrigger,
|
|
722
|
+
DropdownMenuSubContent
|
|
723
|
+
} from '@stackloop/ui'
|
|
724
|
+
|
|
725
|
+
<DropdownMenu>
|
|
726
|
+
<DropdownMenuTrigger>Open menu</DropdownMenuTrigger>
|
|
727
|
+
<DropdownMenuContent>
|
|
728
|
+
<DropdownMenuItem asChild>
|
|
729
|
+
<a href="/dashboard">Dashboard</a>
|
|
730
|
+
</DropdownMenuItem>
|
|
731
|
+
|
|
732
|
+
<DropdownMenuSub>
|
|
733
|
+
<DropdownMenuSubTrigger>Products</DropdownMenuSubTrigger>
|
|
734
|
+
<DropdownMenuSubContent>
|
|
735
|
+
<DropdownMenuItem asChild>
|
|
736
|
+
<a href="/products/new">New Arrivals</a>
|
|
737
|
+
</DropdownMenuItem>
|
|
738
|
+
|
|
739
|
+
<DropdownMenuSub>
|
|
740
|
+
<DropdownMenuSubTrigger>Categories</DropdownMenuSubTrigger>
|
|
741
|
+
<DropdownMenuSubContent>
|
|
742
|
+
<DropdownMenuItem asChild>
|
|
743
|
+
<a href="/products/categories/tools">Tools</a>
|
|
744
|
+
</DropdownMenuItem>
|
|
745
|
+
<DropdownMenuItem asChild>
|
|
746
|
+
<a href="/products/categories/home">Home</a>
|
|
747
|
+
</DropdownMenuItem>
|
|
748
|
+
</DropdownMenuSubContent>
|
|
749
|
+
</DropdownMenuSub>
|
|
750
|
+
</DropdownMenuSubContent>
|
|
751
|
+
</DropdownMenuSub>
|
|
752
|
+
|
|
753
|
+
<DropdownMenuItem onClick={() => console.log('Signed out')}>Sign out</DropdownMenuItem>
|
|
754
|
+
</DropdownMenuContent>
|
|
755
|
+
</DropdownMenu>
|
|
641
756
|
```
|
|
642
757
|
|
|
643
758
|
**Tooltip**:
|
|
@@ -667,7 +782,7 @@ Call `setupRippleEffects()` only once per app (for example in `main.tsx`) to avo
|
|
|
667
782
|
```
|
|
668
783
|
|
|
669
784
|
**Select**:
|
|
670
|
-
- **Description:** **Form-specific** select component with label, error, hint, and validation support. Specifically designed for use in forms with proper semantics, accessibility, and validation integration. Use this component when building forms.
|
|
785
|
+
- **Description:** **Form-specific** select component with label, error, hint, and validation support. Specifically designed for use in forms with proper semantics, accessibility, and validation integration. Use this component when building forms. It includes form-focused features like `required`, hint text, and better integration with form libraries (React Hook Form, Formik, etc.).
|
|
671
786
|
- **Props:**
|
|
672
787
|
- **`options`**: `{ value: string; label: string; icon?: ReactNode; disabled?: boolean }[]` — required. Array of selectable options with optional icons and disabled state.
|
|
673
788
|
- **`value`**: `string` — optional. Currently selected value.
|
package/dist/ButtonGroup.d.ts
CHANGED
|
@@ -4,11 +4,20 @@ export interface ButtonGroupOption {
|
|
|
4
4
|
label: string;
|
|
5
5
|
icon?: React.ReactNode;
|
|
6
6
|
disabled?: boolean;
|
|
7
|
+
className?: string;
|
|
8
|
+
ariaLabel?: string;
|
|
9
|
+
render?: (option: ButtonGroupOption, state: {
|
|
10
|
+
selected: boolean;
|
|
11
|
+
disabled: boolean;
|
|
12
|
+
index: number;
|
|
13
|
+
}) => React.ReactNode;
|
|
14
|
+
onClick?: (option: ButtonGroupOption, event: React.MouseEvent<HTMLButtonElement>) => void;
|
|
7
15
|
}
|
|
8
16
|
export interface ButtonGroupProps {
|
|
9
17
|
options: ButtonGroupOption[];
|
|
10
18
|
value?: string;
|
|
11
19
|
onChange?: (value: string) => void;
|
|
20
|
+
onOptionClick?: (option: ButtonGroupOption, index: number) => void;
|
|
12
21
|
size?: 'sm' | 'md' | 'lg';
|
|
13
22
|
disabled?: boolean;
|
|
14
23
|
className?: string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type HTMLMotionProps } from 'framer-motion';
|
|
3
|
+
type RootVerticalPlacement = 'auto' | 'top' | 'bottom';
|
|
4
|
+
type SubmenuHorizontalPlacement = 'auto' | 'left' | 'right';
|
|
5
|
+
export interface DropdownMenuProps {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
open?: boolean;
|
|
8
|
+
defaultOpen?: boolean;
|
|
9
|
+
onOpenChange?: (open: boolean) => void;
|
|
10
|
+
placement?: RootVerticalPlacement;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const DropdownMenu: React.FC<DropdownMenuProps>;
|
|
14
|
+
export interface DropdownMenuTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
15
|
+
asChild?: boolean;
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
}
|
|
18
|
+
export declare const DropdownMenuTrigger: React.ForwardRefExoticComponent<DropdownMenuTriggerProps & React.RefAttributes<HTMLElement>>;
|
|
19
|
+
export interface DropdownMenuContentProps extends Omit<HTMLMotionProps<'div'>, 'children'> {
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
align?: 'start' | 'end';
|
|
22
|
+
sideOffset?: number;
|
|
23
|
+
animate?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare const DropdownMenuContent: React.FC<DropdownMenuContentProps>;
|
|
26
|
+
export interface DropdownMenuItemProps extends React.HTMLAttributes<HTMLElement> {
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
asChild?: boolean;
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
closeOnSelect?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare const DropdownMenuItem: React.ForwardRefExoticComponent<DropdownMenuItemProps & React.RefAttributes<HTMLElement>>;
|
|
33
|
+
export interface DropdownMenuSubProps {
|
|
34
|
+
children: React.ReactNode;
|
|
35
|
+
defaultOpen?: boolean;
|
|
36
|
+
placement?: SubmenuHorizontalPlacement;
|
|
37
|
+
}
|
|
38
|
+
export declare const DropdownMenuSub: React.FC<DropdownMenuSubProps>;
|
|
39
|
+
export interface DropdownMenuSubTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
40
|
+
asChild?: boolean;
|
|
41
|
+
children: React.ReactNode;
|
|
42
|
+
}
|
|
43
|
+
export declare const DropdownMenuSubTrigger: React.ForwardRefExoticComponent<DropdownMenuSubTriggerProps & React.RefAttributes<HTMLElement>>;
|
|
44
|
+
export interface DropdownMenuSubContentProps extends Omit<HTMLMotionProps<'div'>, 'children'> {
|
|
45
|
+
children: React.ReactNode;
|
|
46
|
+
sideOffset?: number;
|
|
47
|
+
animate?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare const DropdownMenuSubContent: React.FC<DropdownMenuSubContentProps>;
|
|
50
|
+
export {};
|