alope-ui 1.1.4 → 1.1.5

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.
Files changed (2) hide show
  1. package/README.md +2526 -0
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -96,16 +96,23 @@ export default App;
96
96
 
97
97
  - [🪗 Accordion](#accordion) - Collapsible content panels
98
98
  - [🍞 Breadcrumb](#breadcrumb) - Navigation trail
99
+ - [📑 Tabs](#tabs) - Tabbed navigation interface
100
+ - [📄 Pagination](#pagination) - Page navigation controls
99
101
 
100
102
  ### Feedback Components
101
103
 
102
104
  - [⚠️ Alert](#alert) - Status messages and notifications
103
105
  - [🍞 Toast](#toast) - Temporary notification messages
106
+ - [💡 Tooltip](#tooltip) - Contextual information on hover
107
+ - [⏳ Spinner](#spinner) - Loading indicators
108
+ - [💀 Skeleton](#skeleton) - Content loading placeholders
104
109
 
105
110
  ### Data Display
106
111
 
107
112
  - [🏷️ Badge](#badge) - Status indicators and labels
108
113
  - [🃏 Card](#card) - Content containers
114
+ - [📊 Table](#table) - Tabular data display
115
+ - [👤 Avatar](#avatar) - User profile images
109
116
 
110
117
  ### Form Controls
111
118
 
@@ -113,6 +120,9 @@ export default App;
113
120
  - [🔘 Radio Input](#radio-input) - Single choice selections
114
121
  - [📝 Select Input](#select-input) - Dropdown selections
115
122
  - [⌨️ Text Input](#text-input) - Text input fields
123
+ - [📝 Textarea](#textarea) - Multi-line text input
124
+ - [🔄 Toggle](#toggle) - Switch controls
125
+ - [📁 File Upload](#file-upload) - File selection and upload
116
126
 
117
127
  ### Interactive Elements
118
128
 
@@ -1703,6 +1713,2522 @@ const BasicExample = () => {
1703
1713
  />
1704
1714
  ```
1705
1715
 
1716
+ ---
1717
+
1718
+ ## Pagination
1719
+
1720
+ The Pagination component provides navigation controls for paginated content, supporting both simplified and full pagination modes with customizable appearance.
1721
+
1722
+ ### Import
1723
+
1724
+ ```jsx
1725
+ import { Pagination } from "alope-ui";
1726
+ ```
1727
+
1728
+ ### Props
1729
+
1730
+ | Prop | Type | Default | Description |
1731
+ | -------------- | -------------------------- | ----------- | -------------------------------------------------------- |
1732
+ | `currentPage` | `number` | `required` | Current active page number |
1733
+ | `onPageChange` | `(page: number) => void` | `required` | Callback function when page changes |
1734
+ | `count` | `number` | `undefined` | Total number of items |
1735
+ | `pageSize` | `number` | `undefined` | Number of items per page |
1736
+ | `totalPages` | `number` | `undefined` | Total number of pages (deprecated, use count + pageSize) |
1737
+ | `format` | `'none' \| 'long'` | `'none'` | Format for displaying page info |
1738
+ | `simplified` | `boolean` | `false` | Show simplified pagination (prev/next only) |
1739
+ | `asLink` | `boolean` | `false` | Render pagination buttons as links |
1740
+ | `getPageHref` | `(page: number) => string` | `undefined` | Custom href generator for pagination links |
1741
+ | `siblingCount` | `number` | `1` | Number of page buttons to show on each side |
1742
+ | `prevIcon` | `React.ReactNode` | `'<'` | Custom icon for previous button |
1743
+ | `nextIcon` | `React.ReactNode` | `'>'` | Custom icon for next button |
1744
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'sm'` | Size variant of pagination buttons |
1745
+
1746
+ ### Basic Pagination
1747
+
1748
+ ```jsx
1749
+ import { useState } from "react";
1750
+ import { Pagination } from "alope-ui";
1751
+
1752
+ const BasicExample = () => {
1753
+ const [currentPage, setCurrentPage] = useState(1);
1754
+
1755
+ return (
1756
+ <Pagination
1757
+ currentPage={currentPage}
1758
+ count={100}
1759
+ pageSize={10}
1760
+ onPageChange={setCurrentPage}
1761
+ />
1762
+ );
1763
+ };
1764
+ ```
1765
+
1766
+ ### Simplified Pagination
1767
+
1768
+ ```jsx
1769
+ const SimplifiedExample = () => {
1770
+ const [currentPage, setCurrentPage] = useState(1);
1771
+
1772
+ return (
1773
+ <Pagination
1774
+ currentPage={currentPage}
1775
+ count={100}
1776
+ pageSize={10}
1777
+ onPageChange={setCurrentPage}
1778
+ simplified
1779
+ />
1780
+ );
1781
+ };
1782
+ ```
1783
+
1784
+ ### Pagination with Format
1785
+
1786
+ ```jsx
1787
+ {
1788
+ /* None Format - Shows page numbers */
1789
+ }
1790
+ <Pagination
1791
+ currentPage={currentPage}
1792
+ count={100}
1793
+ pageSize={10}
1794
+ onPageChange={setCurrentPage}
1795
+ format="none"
1796
+ simplified
1797
+ />;
1798
+
1799
+ {
1800
+ /* Long Format - Shows item range */
1801
+ }
1802
+ <Pagination
1803
+ currentPage={currentPage}
1804
+ count={100}
1805
+ pageSize={10}
1806
+ onPageChange={setCurrentPage}
1807
+ format="long"
1808
+ simplified
1809
+ />;
1810
+ ```
1811
+
1812
+ ### Sizes
1813
+
1814
+ ```jsx
1815
+ {
1816
+ /* Small Size */
1817
+ }
1818
+ <Pagination
1819
+ currentPage={currentPage}
1820
+ count={100}
1821
+ pageSize={10}
1822
+ onPageChange={setCurrentPage}
1823
+ size="sm"
1824
+ />;
1825
+
1826
+ {
1827
+ /* Medium Size */
1828
+ }
1829
+ <Pagination
1830
+ currentPage={currentPage}
1831
+ count={100}
1832
+ pageSize={10}
1833
+ onPageChange={setCurrentPage}
1834
+ size="md"
1835
+ />;
1836
+
1837
+ {
1838
+ /* Large Size */
1839
+ }
1840
+ <Pagination
1841
+ currentPage={currentPage}
1842
+ count={100}
1843
+ pageSize={10}
1844
+ onPageChange={setCurrentPage}
1845
+ size="lg"
1846
+ />;
1847
+ ```
1848
+
1849
+ ### Custom Sibling Count
1850
+
1851
+ ```jsx
1852
+ {
1853
+ /* Show 2 page buttons on each side */
1854
+ }
1855
+ <Pagination
1856
+ currentPage={currentPage}
1857
+ count={100}
1858
+ pageSize={10}
1859
+ onPageChange={setCurrentPage}
1860
+ siblingCount={2}
1861
+ />;
1862
+ ```
1863
+
1864
+ ### Custom Icons
1865
+
1866
+ ```jsx
1867
+ import { ChevronLeft, ChevronRight } from "lucide-react";
1868
+
1869
+ <Pagination
1870
+ currentPage={currentPage}
1871
+ count={100}
1872
+ pageSize={10}
1873
+ onPageChange={setCurrentPage}
1874
+ prevIcon={<ChevronLeft size={16} />}
1875
+ nextIcon={<ChevronRight size={16} />}
1876
+ />;
1877
+ ```
1878
+
1879
+ ### As Links
1880
+
1881
+ ```jsx
1882
+ <Pagination
1883
+ currentPage={currentPage}
1884
+ count={100}
1885
+ pageSize={10}
1886
+ onPageChange={setCurrentPage}
1887
+ asLink
1888
+ getPageHref={(page) => `/products?page=${page}`}
1889
+ />
1890
+ ```
1891
+
1892
+ ### Using TotalPages (Legacy)
1893
+
1894
+ ```jsx
1895
+ {
1896
+ /* This approach is deprecated, prefer using count + pageSize */
1897
+ }
1898
+ <Pagination
1899
+ currentPage={currentPage}
1900
+ totalPages={10}
1901
+ onPageChange={setCurrentPage}
1902
+ />;
1903
+ ```
1904
+
1905
+ ---
1906
+
1907
+ ## Table
1908
+
1909
+ The Table component provides a flexible and feature-rich data table with support for custom rendering, row selection, striping, sticky headers, and responsive design.
1910
+
1911
+ ### Import
1912
+
1913
+ ```jsx
1914
+ import { Table } from "alope-ui";
1915
+ import type { Column } from "alope-ui";
1916
+ ```
1917
+
1918
+ ### Props
1919
+
1920
+ | Prop | Type | Default | Description |
1921
+ | --------------------- | ------------------------- | ----------- | ------------------------------------ |
1922
+ | `columns` | `Column<T>[]` | `required` | Array of column configurations |
1923
+ | `data` | `T[]` | `required` | Array of data objects to display |
1924
+ | `striped` | `boolean` | `false` | Enable alternating row colors |
1925
+ | `stripeColor` | `string` | `undefined` | Custom color for striped rows |
1926
+ | `headerClassName` | `string` | `""` | CSS class for table header |
1927
+ | `dataColumnClassName` | `string` | `""` | CSS class for data cells |
1928
+ | `dataRowClassName` | `string` | `""` | CSS class for data rows |
1929
+ | `tableClassName` | `string` | `""` | CSS class for table element |
1930
+ | `captionClassName` | `string` | `""` | CSS class for caption |
1931
+ | `containerClassName` | `string` | `""` | CSS class for container |
1932
+ | `customEmptyData` | `React.ReactNode` | `undefined` | Custom message when no data |
1933
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size variant of table |
1934
+ | `caption` | `string` | `undefined` | Table caption text |
1935
+ | `captionPosition` | `'top' \| 'bottom'` | `'bottom'` | Position of caption |
1936
+ | `stickyHeader` | `boolean` | `false` | Make header sticky on scroll |
1937
+ | `selectable` | `boolean` | `false` | Enable row selection with checkboxes |
1938
+ | `onSelectionChange` | `(selected: T[]) => void` | `undefined` | Callback when selection changes |
1939
+ | `selectedRows` | `T[]` | `[]` | Array of selected rows |
1940
+
1941
+ ### Column Type
1942
+
1943
+ ```typescript
1944
+ type Column<T> = {
1945
+ header: string;
1946
+ accessor: keyof T;
1947
+ render?: (value: T[keyof T], row: T) => React.ReactNode;
1948
+ };
1949
+ ```
1950
+
1951
+ ### Basic Table
1952
+
1953
+ ```jsx
1954
+ import { Table } from "alope-ui";
1955
+ import type { Column } from "alope-ui";
1956
+
1957
+ type User = {
1958
+ id: number,
1959
+ name: string,
1960
+ email: string,
1961
+ role: string,
1962
+ };
1963
+
1964
+ const columns: Column<User>[] = [
1965
+ { header: "Name", accessor: "name" },
1966
+ { header: "Email", accessor: "email" },
1967
+ { header: "Role", accessor: "role" },
1968
+ ];
1969
+
1970
+ const data: User[] = [
1971
+ { id: 1, name: "John Doe", email: "john@example.com", role: "Admin" },
1972
+ { id: 2, name: "Jane Smith", email: "jane@example.com", role: "User" },
1973
+ { id: 3, name: "Bob Johnson", email: "bob@example.com", role: "Manager" },
1974
+ ];
1975
+
1976
+ const BasicExample = () => {
1977
+ return <Table columns={columns} data={data} />;
1978
+ };
1979
+ ```
1980
+
1981
+ ### Striped Table
1982
+
1983
+ ```jsx
1984
+ <Table columns={columns} data={data} striped />;
1985
+
1986
+ {
1987
+ /* With custom stripe color */
1988
+ }
1989
+ <Table
1990
+ columns={columns}
1991
+ data={data}
1992
+ striped
1993
+ stripeColor="bg-blue-50 dark:bg-blue-900"
1994
+ />;
1995
+ ```
1996
+
1997
+ ### Sizes
1998
+
1999
+ ```jsx
2000
+ {
2001
+ /* Small Size */
2002
+ }
2003
+ <Table columns={columns} data={data} size="sm" />;
2004
+
2005
+ {
2006
+ /* Medium Size */
2007
+ }
2008
+ <Table columns={columns} data={data} size="md" />;
2009
+
2010
+ {
2011
+ /* Large Size */
2012
+ }
2013
+ <Table columns={columns} data={data} size="lg" />;
2014
+ ```
2015
+
2016
+ ### Custom Rendering
2017
+
2018
+ ```jsx
2019
+ const columns: Column<User>[] = [
2020
+ { header: "Name", accessor: "name" },
2021
+ { header: "Email", accessor: "email" },
2022
+ {
2023
+ header: "Role",
2024
+ accessor: "role",
2025
+ render: (value, row) => (
2026
+ <span
2027
+ className={
2028
+ value === "Admin"
2029
+ ? "bg-red-100 text-red-800 px-2 py-1 rounded"
2030
+ : "bg-blue-100 text-blue-800 px-2 py-1 rounded"
2031
+ }
2032
+ >
2033
+ {value}
2034
+ </span>
2035
+ ),
2036
+ },
2037
+ ];
2038
+
2039
+ <Table columns={columns} data={data} />;
2040
+ ```
2041
+
2042
+ ### Selectable Rows
2043
+
2044
+ ```jsx
2045
+ import { useState } from "react";
2046
+
2047
+ const SelectableExample = () => {
2048
+ const [selectedRows, setSelectedRows] = useState<User[]>([]);
2049
+
2050
+ return (
2051
+ <Table
2052
+ columns={columns}
2053
+ data={data}
2054
+ selectable
2055
+ selectedRows={selectedRows}
2056
+ onSelectionChange={setSelectedRows}
2057
+ />
2058
+ );
2059
+ };
2060
+ ```
2061
+
2062
+ ### Sticky Header
2063
+
2064
+ ```jsx
2065
+ <Table columns={columns} data={data} stickyHeader />
2066
+ ```
2067
+
2068
+ ### With Caption
2069
+
2070
+ ```jsx
2071
+ {
2072
+ /* Caption at bottom */
2073
+ }
2074
+ <Table
2075
+ columns={columns}
2076
+ data={data}
2077
+ caption="User Management Table"
2078
+ captionPosition="bottom"
2079
+ />;
2080
+
2081
+ {
2082
+ /* Caption at top */
2083
+ }
2084
+ <Table
2085
+ columns={columns}
2086
+ data={data}
2087
+ caption="User Management Table"
2088
+ captionPosition="top"
2089
+ />;
2090
+ ```
2091
+
2092
+ ### Custom Empty State
2093
+
2094
+ ```jsx
2095
+ <Table
2096
+ columns={columns}
2097
+ data={[]}
2098
+ customEmptyData={
2099
+ <div className="flex flex-col items-center gap-2">
2100
+ <span className="text-gray-500">No users found</span>
2101
+ <button className="px-4 py-2 bg-blue-500 text-white rounded">
2102
+ Add User
2103
+ </button>
2104
+ </div>
2105
+ }
2106
+ />
2107
+ ```
2108
+
2109
+ ### Custom Styling
2110
+
2111
+ ```jsx
2112
+ <Table
2113
+ columns={columns}
2114
+ data={data}
2115
+ containerClassName="shadow-lg rounded-lg"
2116
+ tableClassName="border-2 border-blue-500"
2117
+ headerClassName="bg-blue-600 text-white"
2118
+ dataRowClassName="hover:bg-blue-50"
2119
+ dataColumnClassName="text-gray-700"
2120
+ />
2121
+ ```
2122
+
2123
+ ### Complete Example
2124
+
2125
+ ```jsx
2126
+ import { useState } from "react";
2127
+ import { Table } from "alope-ui";
2128
+ import type { Column } from "alope-ui";
2129
+
2130
+ type Product = {
2131
+ id: number;
2132
+ name: string;
2133
+ price: number;
2134
+ stock: number;
2135
+ status: string;
2136
+ };
2137
+
2138
+ const ProductTable = () => {
2139
+ const [selectedRows, setSelectedRows] = useState<Product[]>([]);
2140
+
2141
+ const columns: Column<Product>[] = [
2142
+ { header: "Product Name", accessor: "name" },
2143
+ {
2144
+ header: "Price",
2145
+ accessor: "price",
2146
+ render: (value) => `$${value.toFixed(2)}`,
2147
+ },
2148
+ { header: "Stock", accessor: "stock" },
2149
+ {
2150
+ header: "Status",
2151
+ accessor: "status",
2152
+ render: (value) => (
2153
+ <span
2154
+ className={
2155
+ value === "Available"
2156
+ ? "bg-green-100 text-green-800 px-2 py-1 rounded"
2157
+ : "bg-gray-100 text-gray-800 px-2 py-1 rounded"
2158
+ }
2159
+ >
2160
+ {value}
2161
+ </span>
2162
+ ),
2163
+ },
2164
+ ];
2165
+
2166
+ const data: Product[] = [
2167
+ { id: 1, name: "Laptop", price: 999.99, stock: 15, status: "Available" },
2168
+ { id: 2, name: "Mouse", price: 29.99, stock: 0, status: "Out of Stock" },
2169
+ { id: 3, name: "Keyboard", price: 79.99, stock: 23, status: "Available" },
2170
+ ];
2171
+
2172
+ return (
2173
+ <Table
2174
+ columns={columns}
2175
+ data={data}
2176
+ striped
2177
+ selectable
2178
+ selectedRows={selectedRows}
2179
+ onSelectionChange={setSelectedRows}
2180
+ stickyHeader
2181
+ caption="Product Inventory"
2182
+ size="md"
2183
+ />
2184
+ );
2185
+ };
2186
+ ```
2187
+
2188
+ ---
2189
+
2190
+ ## Tooltip
2191
+
2192
+ The Tooltip component displays informative text when hovering over an element, with customizable placement, styling, and animation options.
2193
+
2194
+ ### Import
2195
+
2196
+ ```jsx
2197
+ import { Tooltip } from "alope-ui";
2198
+ ```
2199
+
2200
+ ### Props
2201
+
2202
+ | Prop | Type | Default | Description |
2203
+ | ------------------- | ---------------------------------------- | -------------------------------- | --------------------------------------- |
2204
+ | `children` | `React.ReactNode` | `required` | Element that triggers the tooltip |
2205
+ | `text` | `string` | `required` | Text content of the tooltip |
2206
+ | `showArrow` | `boolean` | `true` | Show arrow pointing to trigger element |
2207
+ | `placement` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'top'` | Position of tooltip relative to trigger |
2208
+ | `offset` | `number` | `8` | Distance from trigger element (in px) |
2209
+ | `bgColor` | `string` | `'bg-gray-800 dark:bg-gray-600'` | Background color class |
2210
+ | `isOpen` | `boolean` | `undefined` | Controlled visibility state |
2211
+ | `onOpenChange` | `(open: boolean) => void` | `undefined` | Callback when visibility changes |
2212
+ | `disabled` | `boolean` | `false` | Disable tooltip functionality |
2213
+ | `animationDuration` | `number` | `300` | Animation duration (in ms) |
2214
+ | `delay` | `number` | `700` | Delay before showing tooltip (in ms) |
2215
+
2216
+ ### Basic Tooltip
2217
+
2218
+ ```jsx
2219
+ import { Tooltip } from "alope-ui";
2220
+
2221
+ const BasicExample = () => {
2222
+ return (
2223
+ <Tooltip text="This is a helpful tooltip">
2224
+ <button className="px-4 py-2 bg-blue-500 text-white rounded">
2225
+ Hover me
2226
+ </button>
2227
+ </Tooltip>
2228
+ );
2229
+ };
2230
+ ```
2231
+
2232
+ ### Placement
2233
+
2234
+ ```jsx
2235
+ {
2236
+ /* Top Placement */
2237
+ }
2238
+ <Tooltip text="Tooltip on top" placement="top">
2239
+ <button>Top</button>
2240
+ </Tooltip>;
2241
+
2242
+ {
2243
+ /* Bottom Placement */
2244
+ }
2245
+ <Tooltip text="Tooltip on bottom" placement="bottom">
2246
+ <button>Bottom</button>
2247
+ </Tooltip>;
2248
+
2249
+ {
2250
+ /* Left Placement */
2251
+ }
2252
+ <Tooltip text="Tooltip on left" placement="left">
2253
+ <button>Left</button>
2254
+ </Tooltip>;
2255
+
2256
+ {
2257
+ /* Right Placement */
2258
+ }
2259
+ <Tooltip text="Tooltip on right" placement="right">
2260
+ <button>Right</button>
2261
+ </Tooltip>;
2262
+ ```
2263
+
2264
+ ### Without Arrow
2265
+
2266
+ ```jsx
2267
+ <Tooltip text="Tooltip without arrow" showArrow={false}>
2268
+ <button>No Arrow</button>
2269
+ </Tooltip>
2270
+ ```
2271
+
2272
+ ### Custom Offset
2273
+
2274
+ ```jsx
2275
+ {
2276
+ /* Small offset */
2277
+ }
2278
+ <Tooltip text="Close to element" offset={4}>
2279
+ <button>Small Offset</button>
2280
+ </Tooltip>;
2281
+
2282
+ {
2283
+ /* Large offset */
2284
+ }
2285
+ <Tooltip text="Far from element" offset={16}>
2286
+ <button>Large Offset</button>
2287
+ </Tooltip>;
2288
+ ```
2289
+
2290
+ ### Custom Background Color
2291
+
2292
+ ```jsx
2293
+ <Tooltip text="Blue tooltip" bgColor="bg-blue-600">
2294
+ <button>Blue Tooltip</button>
2295
+ </Tooltip>
2296
+
2297
+ <Tooltip text="Red tooltip" bgColor="bg-red-600">
2298
+ <button>Red Tooltip</button>
2299
+ </Tooltip>
2300
+
2301
+ <Tooltip text="Green tooltip" bgColor="bg-green-600">
2302
+ <button>Green Tooltip</button>
2303
+ </Tooltip>
2304
+ ```
2305
+
2306
+ ### Controlled Tooltip
2307
+
2308
+ ```jsx
2309
+ import { useState } from "react";
2310
+
2311
+ const ControlledExample = () => {
2312
+ const [isOpen, setIsOpen] = useState(false);
2313
+
2314
+ return (
2315
+ <div>
2316
+ <Tooltip
2317
+ text="Controlled tooltip"
2318
+ isOpen={isOpen}
2319
+ onOpenChange={setIsOpen}
2320
+ >
2321
+ <button onClick={() => setIsOpen(!isOpen)}>Click to toggle</button>
2322
+ </Tooltip>
2323
+ </div>
2324
+ );
2325
+ };
2326
+ ```
2327
+
2328
+ ### Custom Animation
2329
+
2330
+ ```jsx
2331
+ {
2332
+ /* Fast animation */
2333
+ }
2334
+ <Tooltip text="Fast animation" animationDuration={150}>
2335
+ <button>Fast</button>
2336
+ </Tooltip>;
2337
+
2338
+ {
2339
+ /* Slow animation */
2340
+ }
2341
+ <Tooltip text="Slow animation" animationDuration={600}>
2342
+ <button>Slow</button>
2343
+ </Tooltip>;
2344
+ ```
2345
+
2346
+ ### Custom Delay
2347
+
2348
+ ```jsx
2349
+ {
2350
+ /* No delay */
2351
+ }
2352
+ <Tooltip text="Instant tooltip" delay={0}>
2353
+ <button>No Delay</button>
2354
+ </Tooltip>;
2355
+
2356
+ {
2357
+ /* Long delay */
2358
+ }
2359
+ <Tooltip text="Patient tooltip" delay={1500}>
2360
+ <button>Long Delay</button>
2361
+ </Tooltip>;
2362
+ ```
2363
+
2364
+ ### Disabled Tooltip
2365
+
2366
+ ```jsx
2367
+ <Tooltip text="This won't show" disabled>
2368
+ <button>Disabled Tooltip</button>
2369
+ </Tooltip>
2370
+ ```
2371
+
2372
+ ### With Icons
2373
+
2374
+ ```jsx
2375
+ import { Info, HelpCircle } from "lucide-react";
2376
+
2377
+ <Tooltip text="Additional information about this feature">
2378
+ <Info className="w-4 h-4 text-gray-500 cursor-help" />
2379
+ </Tooltip>
2380
+
2381
+ <Tooltip text="Click for help documentation" placement="right">
2382
+ <HelpCircle className="w-5 h-5 text-blue-500 cursor-pointer" />
2383
+ </Tooltip>
2384
+ ```
2385
+
2386
+ ### Complete Example
2387
+
2388
+ ```jsx
2389
+ import { Tooltip } from "alope-ui";
2390
+ import { Info } from "lucide-react";
2391
+
2392
+ const UserProfile = () => {
2393
+ return (
2394
+ <div className="flex items-center gap-4">
2395
+ <div className="flex items-center gap-2">
2396
+ <span>Username</span>
2397
+ <Tooltip
2398
+ text="Your username is visible to all users"
2399
+ placement="top"
2400
+ bgColor="bg-blue-600"
2401
+ delay={500}
2402
+ >
2403
+ <Info className="w-4 h-4 text-gray-400 cursor-help" />
2404
+ </Tooltip>
2405
+ </div>
2406
+
2407
+ <div className="flex items-center gap-2">
2408
+ <span>Email</span>
2409
+ <Tooltip
2410
+ text="Your email is kept private and secure"
2411
+ placement="right"
2412
+ showArrow={false}
2413
+ offset={12}
2414
+ >
2415
+ <Info className="w-4 h-4 text-gray-400 cursor-help" />
2416
+ </Tooltip>
2417
+ </div>
2418
+
2419
+ <Tooltip text="Click to edit your profile" placement="bottom">
2420
+ <button className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
2421
+ Edit Profile
2422
+ </button>
2423
+ </Tooltip>
2424
+ </div>
2425
+ );
2426
+ };
2427
+ ```
2428
+
2429
+ ---
2430
+
2431
+ ## Spinner
2432
+
2433
+ The Spinner component displays an animated loading indicator with customizable size, color, duration, and thickness.
2434
+
2435
+ ### Import
2436
+
2437
+ ```jsx
2438
+ import { Spinner } from "alope-ui";
2439
+ ```
2440
+
2441
+ ### Props
2442
+
2443
+ | Prop | Type | Default | Description |
2444
+ | ----------- | ------------------------------------------------------------------------- | ----------- | ------------------------------- |
2445
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of the spinner |
2446
+ | `color` | `'primary' \| 'error' \| 'warning' \| 'success' \| 'info' \| 'secondary'` | `'primary'` | Color variant of the spinner |
2447
+ | `className` | `string` | `""` | Additional CSS classes |
2448
+ | `duration` | `number` | `1000` | Animation duration (in ms) |
2449
+ | `thickness` | `number` | `undefined` | Custom border thickness (in px) |
2450
+
2451
+ ### Basic Spinner
2452
+
2453
+ ```jsx
2454
+ import { Spinner } from "alope-ui";
2455
+
2456
+ const BasicExample = () => {
2457
+ return <Spinner />;
2458
+ };
2459
+ ```
2460
+
2461
+ ### Sizes
2462
+
2463
+ ```jsx
2464
+ {
2465
+ /* Small Size */
2466
+ }
2467
+ <Spinner size="sm" />;
2468
+
2469
+ {
2470
+ /* Medium Size */
2471
+ }
2472
+ <Spinner size="md" />;
2473
+
2474
+ {
2475
+ /* Large Size */
2476
+ }
2477
+ <Spinner size="lg" />;
2478
+ ```
2479
+
2480
+ ### Colors
2481
+
2482
+ ```jsx
2483
+ {
2484
+ /* Primary Color */
2485
+ }
2486
+ <Spinner color="primary" />;
2487
+
2488
+ {
2489
+ /* Error Color */
2490
+ }
2491
+ <Spinner color="error" />;
2492
+
2493
+ {
2494
+ /* Warning Color */
2495
+ }
2496
+ <Spinner color="warning" />;
2497
+
2498
+ {
2499
+ /* Success Color */
2500
+ }
2501
+ <Spinner color="success" />;
2502
+
2503
+ {
2504
+ /* Info Color */
2505
+ }
2506
+ <Spinner color="info" />;
2507
+
2508
+ {
2509
+ /* Secondary Color */
2510
+ }
2511
+ <Spinner color="secondary" />;
2512
+ ```
2513
+
2514
+ ### Custom Duration
2515
+
2516
+ ```jsx
2517
+ {
2518
+ /* Fast animation */
2519
+ }
2520
+ <Spinner duration={500} />;
2521
+
2522
+ {
2523
+ /* Slow animation */
2524
+ }
2525
+ <Spinner duration={2000} />;
2526
+ ```
2527
+
2528
+ ### Custom Thickness
2529
+
2530
+ ```jsx
2531
+ {
2532
+ /* Thin spinner */
2533
+ }
2534
+ <Spinner thickness={1} />;
2535
+
2536
+ {
2537
+ /* Thick spinner */
2538
+ }
2539
+ <Spinner thickness={6} />;
2540
+ ```
2541
+
2542
+ ### Combined Customization
2543
+
2544
+ ```jsx
2545
+ <Spinner size="lg" color="success" duration={1500} thickness={5} />
2546
+ ```
2547
+
2548
+ ### With Custom Styling
2549
+
2550
+ ```jsx
2551
+ <Spinner className="mx-auto my-4" />
2552
+
2553
+ <Spinner className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2" />
2554
+ ```
2555
+
2556
+ ### Loading States
2557
+
2558
+ ```jsx
2559
+ import { useState } from "react";
2560
+
2561
+ const LoadingButton = () => {
2562
+ const [isLoading, setIsLoading] = useState(false);
2563
+
2564
+ const handleClick = async () => {
2565
+ setIsLoading(true);
2566
+ // Simulate API call
2567
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2568
+ setIsLoading(false);
2569
+ };
2570
+
2571
+ return (
2572
+ <button
2573
+ onClick={handleClick}
2574
+ disabled={isLoading}
2575
+ className="px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50 flex items-center gap-2"
2576
+ >
2577
+ {isLoading && <Spinner size="sm" className="border-white" />}
2578
+ {isLoading ? "Loading..." : "Submit"}
2579
+ </button>
2580
+ );
2581
+ };
2582
+ ```
2583
+
2584
+ ### Centered Loading
2585
+
2586
+ ```jsx
2587
+ const CenteredLoading = () => {
2588
+ return (
2589
+ <div className="flex items-center justify-center h-64">
2590
+ <Spinner size="lg" color="primary" />
2591
+ </div>
2592
+ );
2593
+ };
2594
+ ```
2595
+
2596
+ ### With Text
2597
+
2598
+ ```jsx
2599
+ const LoadingWithText = () => {
2600
+ return (
2601
+ <div className="flex flex-col items-center gap-3">
2602
+ <Spinner size="lg" color="info" />
2603
+ <p className="text-gray-600 dark:text-gray-400">Loading data...</p>
2604
+ </div>
2605
+ );
2606
+ };
2607
+ ```
2608
+
2609
+ ### Complete Example
2610
+
2611
+ ```jsx
2612
+ import { useState, useEffect } from "react";
2613
+ import { Spinner } from "alope-ui";
2614
+
2615
+ const DataFetcher = () => {
2616
+ const [isLoading, setIsLoading] = useState(true);
2617
+ const [data, setData] = useState(null);
2618
+
2619
+ useEffect(() => {
2620
+ const fetchData = async () => {
2621
+ setIsLoading(true);
2622
+ try {
2623
+ // Simulate API call
2624
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2625
+ setData({ message: "Data loaded successfully!" });
2626
+ } catch (error) {
2627
+ console.error(error);
2628
+ } finally {
2629
+ setIsLoading(false);
2630
+ }
2631
+ };
2632
+
2633
+ fetchData();
2634
+ }, []);
2635
+
2636
+ if (isLoading) {
2637
+ return (
2638
+ <div className="flex flex-col items-center justify-center h-96 gap-4">
2639
+ <Spinner size="lg" color="primary" duration={800} />
2640
+ <p className="text-gray-600 dark:text-gray-400">
2641
+ Fetching your data...
2642
+ </p>
2643
+ </div>
2644
+ );
2645
+ }
2646
+
2647
+ return (
2648
+ <div className="p-4">
2649
+ <h2 className="text-xl font-bold">{data?.message}</h2>
2650
+ </div>
2651
+ );
2652
+ };
2653
+ ```
2654
+
2655
+ ---
2656
+
2657
+ ## Skeleton
2658
+
2659
+ The Skeleton component displays a placeholder preview of content before the actual data is loaded, improving perceived performance and user experience.
2660
+
2661
+ ### Import
2662
+
2663
+ ```jsx
2664
+ import { Skeleton } from "alope-ui";
2665
+ ```
2666
+
2667
+ ### Props
2668
+
2669
+ | Prop | Type | Default | Description |
2670
+ | ----------- | ---------------------- | ----------- | -------------------------------------- |
2671
+ | `width` | `string` | `undefined` | Width of the skeleton (CSS value) |
2672
+ | `height` | `string` | `undefined` | Height of the skeleton (CSS value) |
2673
+ | `rounded` | `boolean` | `false` | Apply rounded shape (full circle/pill) |
2674
+ | `isLoading` | `boolean` | `true` | Show skeleton or actual content |
2675
+ | `children` | `React.ReactNode` | `undefined` | Content to render when not loading |
2676
+ | `animode` | `'shimmer' \| 'flash'` | `'flash'` | Animation mode for the skeleton |
2677
+
2678
+ ### Basic Skeleton
2679
+
2680
+ ```jsx
2681
+ import { Skeleton } from "alope-ui";
2682
+
2683
+ const BasicExample = () => {
2684
+ return <Skeleton width="200px" height="20px" />;
2685
+ };
2686
+ ```
2687
+
2688
+ ### With Children
2689
+
2690
+ ```jsx
2691
+ import { useState, useEffect } from "react";
2692
+
2693
+ const SkeletonWithChildren = () => {
2694
+ const [isLoading, setIsLoading] = useState(true);
2695
+
2696
+ useEffect(() => {
2697
+ setTimeout(() => setIsLoading(false), 2000);
2698
+ }, []);
2699
+
2700
+ return (
2701
+ <Skeleton isLoading={isLoading}>
2702
+ <p>This content will show after loading</p>
2703
+ </Skeleton>
2704
+ );
2705
+ };
2706
+ ```
2707
+
2708
+ ### Animation Modes
2709
+
2710
+ ```jsx
2711
+ {
2712
+ /* Flash animation */
2713
+ }
2714
+ <Skeleton width="200px" height="20px" animode="flash" />;
2715
+
2716
+ {
2717
+ /* Shimmer animation */
2718
+ }
2719
+ <Skeleton width="200px" height="20px" animode="shimmer" />;
2720
+ ```
2721
+
2722
+ ### Rounded Skeleton
2723
+
2724
+ ```jsx
2725
+ {
2726
+ /* Rounded rectangle */
2727
+ }
2728
+ <Skeleton width="100px" height="100px" rounded />;
2729
+
2730
+ {
2731
+ /* Avatar skeleton */
2732
+ }
2733
+ <Skeleton width="48px" height="48px" rounded />;
2734
+ ```
2735
+
2736
+ ### Different Sizes
2737
+
2738
+ ```jsx
2739
+ {
2740
+ /* Small */
2741
+ }
2742
+ <Skeleton width="100px" height="16px" />;
2743
+
2744
+ {
2745
+ /* Medium */
2746
+ }
2747
+ <Skeleton width="200px" height="20px" />;
2748
+
2749
+ {
2750
+ /* Large */
2751
+ }
2752
+ <Skeleton width="300px" height="24px" />;
2753
+
2754
+ {
2755
+ /* Full width */
2756
+ }
2757
+ <Skeleton width="100%" height="20px" />;
2758
+ ```
2759
+
2760
+ ### Text Skeleton
2761
+
2762
+ ```jsx
2763
+ const TextSkeleton = () => {
2764
+ return (
2765
+ <div className="space-y-2">
2766
+ <Skeleton width="100%" height="20px" />
2767
+ <Skeleton width="90%" height="20px" />
2768
+ <Skeleton width="95%" height="20px" />
2769
+ <Skeleton width="85%" height="20px" />
2770
+ </div>
2771
+ );
2772
+ };
2773
+ ```
2774
+
2775
+ ### Card Skeleton
2776
+
2777
+ ```jsx
2778
+ const CardSkeleton = () => {
2779
+ return (
2780
+ <div className="border rounded-lg p-4 space-y-4">
2781
+ {/* Image skeleton */}
2782
+ <Skeleton width="100%" height="200px" />
2783
+
2784
+ {/* Title skeleton */}
2785
+ <Skeleton width="70%" height="24px" />
2786
+
2787
+ {/* Description skeleton */}
2788
+ <div className="space-y-2">
2789
+ <Skeleton width="100%" height="16px" />
2790
+ <Skeleton width="95%" height="16px" />
2791
+ <Skeleton width="90%" height="16px" />
2792
+ </div>
2793
+
2794
+ {/* Button skeleton */}
2795
+ <Skeleton width="120px" height="40px" />
2796
+ </div>
2797
+ );
2798
+ };
2799
+ ```
2800
+
2801
+ ### Avatar Skeleton
2802
+
2803
+ ```jsx
2804
+ const AvatarSkeleton = () => {
2805
+ return (
2806
+ <div className="flex items-center gap-3">
2807
+ <Skeleton width="48px" height="48px" rounded />
2808
+ <div className="space-y-2 flex-1">
2809
+ <Skeleton width="150px" height="16px" />
2810
+ <Skeleton width="100px" height="14px" />
2811
+ </div>
2812
+ </div>
2813
+ );
2814
+ };
2815
+ ```
2816
+
2817
+ ### List Skeleton
2818
+
2819
+ ```jsx
2820
+ const ListSkeleton = () => {
2821
+ return (
2822
+ <div className="space-y-4">
2823
+ {[...Array(5)].map((_, i) => (
2824
+ <div key={i} className="flex items-center gap-3">
2825
+ <Skeleton width="40px" height="40px" rounded />
2826
+ <div className="space-y-2 flex-1">
2827
+ <Skeleton width="60%" height="16px" />
2828
+ <Skeleton width="40%" height="14px" />
2829
+ </div>
2830
+ </div>
2831
+ ))}
2832
+ </div>
2833
+ );
2834
+ };
2835
+ ```
2836
+
2837
+ ### Table Skeleton
2838
+
2839
+ ```jsx
2840
+ const TableSkeleton = () => {
2841
+ return (
2842
+ <div className="space-y-3">
2843
+ {/* Header */}
2844
+ <div className="grid grid-cols-4 gap-4">
2845
+ <Skeleton width="100%" height="20px" />
2846
+ <Skeleton width="100%" height="20px" />
2847
+ <Skeleton width="100%" height="20px" />
2848
+ <Skeleton width="100%" height="20px" />
2849
+ </div>
2850
+
2851
+ {/* Rows */}
2852
+ {[...Array(5)].map((_, i) => (
2853
+ <div key={i} className="grid grid-cols-4 gap-4">
2854
+ <Skeleton width="100%" height="16px" />
2855
+ <Skeleton width="100%" height="16px" />
2856
+ <Skeleton width="100%" height="16px" />
2857
+ <Skeleton width="100%" height="16px" />
2858
+ </div>
2859
+ ))}
2860
+ </div>
2861
+ );
2862
+ };
2863
+ ```
2864
+
2865
+ ### Complete Example
2866
+
2867
+ ```jsx
2868
+ import { useState, useEffect } from "react";
2869
+ import { Skeleton } from "alope-ui";
2870
+
2871
+ type User = {
2872
+ id: number,
2873
+ name: string,
2874
+ email: string,
2875
+ avatar: string,
2876
+ };
2877
+
2878
+ const UserProfile = () => {
2879
+ const [isLoading, setIsLoading] = useState(true);
2880
+ const [user, setUser] = (useState < User) | (null > null);
2881
+
2882
+ useEffect(() => {
2883
+ const fetchUser = async () => {
2884
+ setIsLoading(true);
2885
+ // Simulate API call
2886
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2887
+ setUser({
2888
+ id: 1,
2889
+ name: "John Doe",
2890
+ email: "john@example.com",
2891
+ avatar: "/avatar.jpg",
2892
+ });
2893
+ setIsLoading(false);
2894
+ };
2895
+
2896
+ fetchUser();
2897
+ }, []);
2898
+
2899
+ if (isLoading) {
2900
+ return (
2901
+ <div className="max-w-md border rounded-lg p-6 space-y-4">
2902
+ {/* Avatar and name skeleton */}
2903
+ <div className="flex items-center gap-4">
2904
+ <Skeleton width="64px" height="64px" rounded />
2905
+ <div className="space-y-2 flex-1">
2906
+ <Skeleton width="150px" height="20px" />
2907
+ <Skeleton width="200px" height="16px" />
2908
+ </div>
2909
+ </div>
2910
+
2911
+ {/* Bio skeleton */}
2912
+ <div className="space-y-2">
2913
+ <Skeleton width="100%" height="16px" />
2914
+ <Skeleton width="95%" height="16px" />
2915
+ <Skeleton width="90%" height="16px" />
2916
+ </div>
2917
+
2918
+ {/* Stats skeleton */}
2919
+ <div className="flex gap-4">
2920
+ <Skeleton width="80px" height="40px" />
2921
+ <Skeleton width="80px" height="40px" />
2922
+ <Skeleton width="80px" height="40px" />
2923
+ </div>
2924
+
2925
+ {/* Button skeleton */}
2926
+ <Skeleton width="100%" height="44px" />
2927
+ </div>
2928
+ );
2929
+ }
2930
+
2931
+ return (
2932
+ <div className="max-w-md border rounded-lg p-6">
2933
+ <div className="flex items-center gap-4 mb-4">
2934
+ <img
2935
+ src={user?.avatar}
2936
+ alt={user?.name}
2937
+ className="w-16 h-16 rounded-full"
2938
+ />
2939
+ <div>
2940
+ <h2 className="text-xl font-bold">{user?.name}</h2>
2941
+ <p className="text-gray-600">{user?.email}</p>
2942
+ </div>
2943
+ </div>
2944
+ {/* Rest of the content */}
2945
+ </div>
2946
+ );
2947
+ };
2948
+ ```
2949
+
2950
+ ---
2951
+
2952
+ ## Toggle
2953
+
2954
+ The Toggle component provides an animated switch control for toggling between on/off states with various visual styles and sizes.
2955
+
2956
+ ### Import
2957
+
2958
+ ```jsx
2959
+ import { Toggle } from "alope-ui";
2960
+ ```
2961
+
2962
+ ### Props
2963
+
2964
+ | Prop | Type | Default | Description |
2965
+ | ------------------ | ------------------------------------------------------------------------- | ----------- | ----------------------------------- |
2966
+ | `variant` | `'primary' \| 'error' \| 'warning' \| 'success' \| 'info' \| 'secondary'` | `'primary'` | Color variant of the toggle |
2967
+ | `customSize` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of the toggle switch |
2968
+ | `thumbShape` | `'stadium' \| 'rounded'` | `'stadium'` | Shape of the toggle thumb |
2969
+ | `label` | `string` | `undefined` | Label text displayed next to toggle |
2970
+ | `wrapperClassName` | `string` | `""` | CSS class for wrapper element |
2971
+ | `labelClassName` | `string` | `""` | CSS class for label text |
2972
+ | `...props` | `InputHTMLAttributes<HTMLInputElement>` | - | All standard HTML input attributes |
2973
+
2974
+ ### Basic Toggle
2975
+
2976
+ ```jsx
2977
+ import { useState } from "react";
2978
+ import { Toggle } from "alope-ui";
2979
+
2980
+ const BasicExample = () => {
2981
+ const [isChecked, setIsChecked] = useState(false);
2982
+
2983
+ return (
2984
+ <Toggle
2985
+ checked={isChecked}
2986
+ onChange={(e) => setIsChecked(e.target.checked)}
2987
+ />
2988
+ );
2989
+ };
2990
+ ```
2991
+
2992
+ ### With Label
2993
+
2994
+ ```jsx
2995
+ <Toggle
2996
+ checked={isChecked}
2997
+ onChange={(e) => setIsChecked(e.target.checked)}
2998
+ label="Enable notifications"
2999
+ />
3000
+ ```
3001
+
3002
+ ### Variants
3003
+
3004
+ ```jsx
3005
+ {
3006
+ /* Primary Variant */
3007
+ }
3008
+ <Toggle
3009
+ checked={isChecked}
3010
+ onChange={(e) => setIsChecked(e.target.checked)}
3011
+ variant="primary"
3012
+ label="Primary"
3013
+ />;
3014
+
3015
+ {
3016
+ /* Error Variant */
3017
+ }
3018
+ <Toggle
3019
+ checked={isChecked}
3020
+ onChange={(e) => setIsChecked(e.target.checked)}
3021
+ variant="error"
3022
+ label="Error"
3023
+ />;
3024
+
3025
+ {
3026
+ /* Warning Variant */
3027
+ }
3028
+ <Toggle
3029
+ checked={isChecked}
3030
+ onChange={(e) => setIsChecked(e.target.checked)}
3031
+ variant="warning"
3032
+ label="Warning"
3033
+ />;
3034
+
3035
+ {
3036
+ /* Success Variant */
3037
+ }
3038
+ <Toggle
3039
+ checked={isChecked}
3040
+ onChange={(e) => setIsChecked(e.target.checked)}
3041
+ variant="success"
3042
+ label="Success"
3043
+ />;
3044
+
3045
+ {
3046
+ /* Info Variant */
3047
+ }
3048
+ <Toggle
3049
+ checked={isChecked}
3050
+ onChange={(e) => setIsChecked(e.target.checked)}
3051
+ variant="info"
3052
+ label="Info"
3053
+ />;
3054
+
3055
+ {
3056
+ /* Secondary Variant */
3057
+ }
3058
+ <Toggle
3059
+ checked={isChecked}
3060
+ onChange={(e) => setIsChecked(e.target.checked)}
3061
+ variant="secondary"
3062
+ label="Secondary"
3063
+ />;
3064
+ ```
3065
+
3066
+ ### Sizes
3067
+
3068
+ ```jsx
3069
+ {
3070
+ /* Small Size */
3071
+ }
3072
+ <Toggle
3073
+ checked={isChecked}
3074
+ onChange={(e) => setIsChecked(e.target.checked)}
3075
+ customSize="sm"
3076
+ label="Small"
3077
+ />;
3078
+
3079
+ {
3080
+ /* Medium Size */
3081
+ }
3082
+ <Toggle
3083
+ checked={isChecked}
3084
+ onChange={(e) => setIsChecked(e.target.checked)}
3085
+ customSize="md"
3086
+ label="Medium"
3087
+ />;
3088
+
3089
+ {
3090
+ /* Large Size */
3091
+ }
3092
+ <Toggle
3093
+ checked={isChecked}
3094
+ onChange={(e) => setIsChecked(e.target.checked)}
3095
+ customSize="lg"
3096
+ label="Large"
3097
+ />;
3098
+ ```
3099
+
3100
+ ### Thumb Shapes
3101
+
3102
+ ```jsx
3103
+ {
3104
+ /* Stadium Shape (Fully Rounded) */
3105
+ }
3106
+ <Toggle
3107
+ checked={isChecked}
3108
+ onChange={(e) => setIsChecked(e.target.checked)}
3109
+ thumbShape="stadium"
3110
+ label="Stadium"
3111
+ />;
3112
+
3113
+ {
3114
+ /* Rounded Shape (Slightly Rounded) */
3115
+ }
3116
+ <Toggle
3117
+ checked={isChecked}
3118
+ onChange={(e) => setIsChecked(e.target.checked)}
3119
+ thumbShape="rounded"
3120
+ label="Rounded"
3121
+ />;
3122
+ ```
3123
+
3124
+ ### Disabled State
3125
+
3126
+ ```jsx
3127
+ <Toggle
3128
+ checked={isChecked}
3129
+ onChange={(e) => setIsChecked(e.target.checked)}
3130
+ disabled
3131
+ label="Disabled toggle"
3132
+ />
3133
+ ```
3134
+
3135
+ ### Custom Styling
3136
+
3137
+ ```jsx
3138
+ <Toggle
3139
+ checked={isChecked}
3140
+ onChange={(e) => setIsChecked(e.target.checked)}
3141
+ label="Custom styled toggle"
3142
+ wrapperClassName="bg-gray-100 p-4 rounded-lg"
3143
+ labelClassName="text-lg font-bold text-blue-600"
3144
+ />
3145
+ ```
3146
+
3147
+ ### Complete Example
3148
+
3149
+ ```jsx
3150
+ import { useState } from "react";
3151
+ import { Toggle } from "alope-ui";
3152
+
3153
+ const CompleteExample = () => {
3154
+ const [settings, setSettings] = useState({
3155
+ notifications: false,
3156
+ darkMode: true,
3157
+ autoSave: false,
3158
+ });
3159
+
3160
+ const handleToggle =
3161
+ (key: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
3162
+ setSettings((prev) => ({
3163
+ ...prev,
3164
+ [key]: e.target.checked,
3165
+ }));
3166
+ };
3167
+
3168
+ return (
3169
+ <div className="space-y-4">
3170
+ <Toggle
3171
+ checked={settings.notifications}
3172
+ onChange={handleToggle("notifications")}
3173
+ variant="primary"
3174
+ customSize="md"
3175
+ label="Enable notifications"
3176
+ />
3177
+
3178
+ <Toggle
3179
+ checked={settings.darkMode}
3180
+ onChange={handleToggle("darkMode")}
3181
+ variant="secondary"
3182
+ customSize="md"
3183
+ label="Dark mode"
3184
+ />
3185
+
3186
+ <Toggle
3187
+ checked={settings.autoSave}
3188
+ onChange={handleToggle("autoSave")}
3189
+ variant="success"
3190
+ customSize="md"
3191
+ label="Auto-save documents"
3192
+ />
3193
+ </div>
3194
+ );
3195
+ };
3196
+ ```
3197
+
3198
+ ---
3199
+
3200
+ ## FileUpload
3201
+
3202
+ The FileUpload component provides a flexible file upload interface with support for drag-and-drop, file validation, preview, and two display modes (dropzone and text input).
3203
+
3204
+ ### Import
3205
+
3206
+ ```jsx
3207
+ import { FileUpload } from "alope-ui";
3208
+ ```
3209
+
3210
+ ### Props
3211
+
3212
+ | Prop | Type | Default | Description |
3213
+ | --------------- | --------------------------- | ------------ | ---------------------------------------------- |
3214
+ | `onFilesChange` | `(files: File[]) => void` | `required` | Callback fired when files change |
3215
+ | `acceptedFiles` | `string` | `'image/*'` | Accepted file types (MIME types or extensions) |
3216
+ | `maxFiles` | `number` | `1` | Maximum number of files allowed |
3217
+ | `maxKiloByte` | `number` | `2048` | Maximum file size in kilobytes |
3218
+ | `mode` | `'dropzone' \| 'textinput'` | `'dropzone'` | Display mode of the file upload |
3219
+ | `clearable` | `boolean` | `true` | Show clear button on file previews |
3220
+ | `inputLabel` | `string` | `undefined` | Label for text input mode button |
3221
+ | `helperText` | `string` | `undefined` | Helper text displayed below the input |
3222
+ | `mediaCapture` | `'user' \| 'environment'` | `undefined` | Camera capture mode (for mobile devices) |
3223
+
3224
+ ### Basic FileUpload (Dropzone Mode)
3225
+
3226
+ ```jsx
3227
+ import { useState } from "react";
3228
+ import { FileUpload } from "alope-ui";
3229
+
3230
+ const BasicExample = () => {
3231
+ const [files, setFiles] = useState<File[]>([]);
3232
+
3233
+ return (
3234
+ <FileUpload
3235
+ onFilesChange={setFiles}
3236
+ />
3237
+ );
3238
+ };
3239
+ ```
3240
+
3241
+ ### Text Input Mode
3242
+
3243
+ ```jsx
3244
+ <FileUpload
3245
+ mode="textinput"
3246
+ onFilesChange={setFiles}
3247
+ inputLabel="Upload File"
3248
+ />
3249
+ ```
3250
+
3251
+ ### Multiple Files
3252
+
3253
+ ```jsx
3254
+ <FileUpload
3255
+ maxFiles={5}
3256
+ onFilesChange={setFiles}
3257
+ helperText="You can upload up to 5 files"
3258
+ />
3259
+ ```
3260
+
3261
+ ### Custom File Types
3262
+
3263
+ ```jsx
3264
+ {
3265
+ /* Accept only images */
3266
+ }
3267
+ <FileUpload acceptedFiles="image/*" onFilesChange={setFiles} />;
3268
+
3269
+ {
3270
+ /* Accept specific image types */
3271
+ }
3272
+ <FileUpload
3273
+ acceptedFiles="image/png, image/jpeg, image/jpg"
3274
+ onFilesChange={setFiles}
3275
+ />;
3276
+
3277
+ {
3278
+ /* Accept PDFs */
3279
+ }
3280
+ <FileUpload acceptedFiles="application/pdf" onFilesChange={setFiles} />;
3281
+
3282
+ {
3283
+ /* Accept by file extension */
3284
+ }
3285
+ <FileUpload acceptedFiles=".pdf, .doc, .docx" onFilesChange={setFiles} />;
3286
+
3287
+ {
3288
+ /* Accept multiple types */
3289
+ }
3290
+ <FileUpload acceptedFiles="image/*, .pdf, .doc" onFilesChange={setFiles} />;
3291
+ ```
3292
+
3293
+ ### File Size Limit
3294
+
3295
+ ```jsx
3296
+ {
3297
+ /* 5MB limit */
3298
+ }
3299
+ <FileUpload
3300
+ maxKiloByte={5120}
3301
+ onFilesChange={setFiles}
3302
+ helperText="Maximum file size: 5MB"
3303
+ />;
3304
+
3305
+ {
3306
+ /* 500KB limit */
3307
+ }
3308
+ <FileUpload
3309
+ maxKiloByte={512}
3310
+ onFilesChange={setFiles}
3311
+ helperText="Maximum file size: 500KB"
3312
+ />;
3313
+ ```
3314
+
3315
+ ### With Helper Text
3316
+
3317
+ ```jsx
3318
+ <FileUpload
3319
+ onFilesChange={setFiles}
3320
+ helperText="Accepted formats: PNG, JPG, JPEG. Max size: 2MB"
3321
+ />
3322
+ ```
3323
+
3324
+ ### Camera Capture (Mobile)
3325
+
3326
+ ```jsx
3327
+ {
3328
+ /* Use front camera */
3329
+ }
3330
+ <FileUpload
3331
+ acceptedFiles="image/*"
3332
+ mediaCapture="user"
3333
+ onFilesChange={setFiles}
3334
+ />;
3335
+
3336
+ {
3337
+ /* Use back camera */
3338
+ }
3339
+ <FileUpload
3340
+ acceptedFiles="image/*"
3341
+ mediaCapture="environment"
3342
+ onFilesChange={setFiles}
3343
+ />;
3344
+ ```
3345
+
3346
+ ### Non-clearable Files
3347
+
3348
+ ```jsx
3349
+ <FileUpload onFilesChange={setFiles} clearable={false} />
3350
+ ```
3351
+
3352
+ ### Complete Example with Dropzone Mode
3353
+
3354
+ ```jsx
3355
+ import { useState } from "react";
3356
+ import { FileUpload } from "alope-ui";
3357
+
3358
+ const DropzoneExample = () => {
3359
+ const [uploadedFiles, setUploadedFiles] = useState<File[]>([]);
3360
+
3361
+ const handleFilesChange = (files: File[]) => {
3362
+ setUploadedFiles(files);
3363
+ console.log('Uploaded files:', files);
3364
+ };
3365
+
3366
+ return (
3367
+ <div className="max-w-2xl mx-auto p-6">
3368
+ <h2 className="text-xl font-bold mb-4">Upload Your Images</h2>
3369
+
3370
+ <FileUpload
3371
+ mode="dropzone"
3372
+ acceptedFiles="image/png, image/jpeg, image/jpg"
3373
+ maxFiles={3}
3374
+ maxKiloByte={5120}
3375
+ onFilesChange={handleFilesChange}
3376
+ helperText="Upload up to 3 images. Max 5MB per file."
3377
+ />
3378
+
3379
+ {uploadedFiles.length > 0 && (
3380
+ <div className="mt-4">
3381
+ <p className="font-medium">Selected files:</p>
3382
+ <ul className="list-disc list-inside">
3383
+ {uploadedFiles.map((file, index) => (
3384
+ <li key={index}>{file.name}</li>
3385
+ ))}
3386
+ </ul>
3387
+ </div>
3388
+ )}
3389
+ </div>
3390
+ );
3391
+ };
3392
+ ```
3393
+
3394
+ ### Complete Example with Text Input Mode
3395
+
3396
+ ```jsx
3397
+ import { useState } from "react";
3398
+ import { FileUpload } from "alope-ui";
3399
+
3400
+ const TextInputExample = () => {
3401
+ const [documents, setDocuments] = useState<File[]>([]);
3402
+
3403
+ return (
3404
+ <div className="max-w-2xl mx-auto p-6">
3405
+ <h2 className="text-xl font-bold mb-4">Upload Documents</h2>
3406
+
3407
+ <FileUpload
3408
+ mode="textinput"
3409
+ acceptedFiles=".pdf, .doc, .docx"
3410
+ maxFiles={5}
3411
+ maxKiloByte={10240}
3412
+ inputLabel="Choose Documents"
3413
+ onFilesChange={setDocuments}
3414
+ helperText="Accepted: PDF, DOC, DOCX. Max 10MB per file."
3415
+ />
3416
+ </div>
3417
+ );
3418
+ };
3419
+ ```
3420
+
3421
+ ---
3422
+
3423
+ ## Textarea
3424
+
3425
+ The Textarea component provides a multi-line text input with optional auto-resize functionality, label, error messages, and dark mode support.
3426
+
3427
+ ### Import
3428
+
3429
+ ```jsx
3430
+ import { Textarea } from "alope-ui";
3431
+ import type { TextareaType } from "alope-ui";
3432
+ ```
3433
+
3434
+ ### Props
3435
+
3436
+ | Prop | Type | Default | Description |
3437
+ | ----------------- | ------------------------------------------ | ----------- | -------------------------------------- |
3438
+ | `label` | `string` | `undefined` | Label text displayed above textarea |
3439
+ | `error` | `string` | `undefined` | Error message displayed below textarea |
3440
+ | `autoResize` | `boolean` | `false` | Enable auto-resize based on content |
3441
+ | `customClassName` | `string` | `""` | Custom CSS class for textarea element |
3442
+ | `...props` | `InputHTMLAttributes<HTMLTextAreaElement>` | - | All standard HTML textarea attributes |
3443
+
3444
+ ### Basic Textarea
3445
+
3446
+ ```jsx
3447
+ import { useState } from "react";
3448
+ import { Textarea } from "alope-ui";
3449
+
3450
+ const BasicExample = () => {
3451
+ const [value, setValue] = useState("");
3452
+
3453
+ return (
3454
+ <Textarea
3455
+ value={value}
3456
+ onChange={(e) => setValue(e.target.value)}
3457
+ placeholder="Enter your text here..."
3458
+ />
3459
+ );
3460
+ };
3461
+ ```
3462
+
3463
+ ### With Label
3464
+
3465
+ ```jsx
3466
+ <Textarea
3467
+ label="Description"
3468
+ name="description"
3469
+ value={value}
3470
+ onChange={(e) => setValue(e.target.value)}
3471
+ placeholder="Enter description..."
3472
+ />
3473
+ ```
3474
+
3475
+ ### With Error Message
3476
+
3477
+ ```jsx
3478
+ const [description, setDescription] = useState("");
3479
+ const [error, setError] = useState("");
3480
+
3481
+ const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
3482
+ setDescription(e.target.value);
3483
+ if (e.target.value.length < 10) {
3484
+ setError("Description must be at least 10 characters");
3485
+ } else {
3486
+ setError("");
3487
+ }
3488
+ };
3489
+
3490
+ <Textarea
3491
+ label="Description"
3492
+ value={description}
3493
+ onChange={handleChange}
3494
+ error={error}
3495
+ placeholder="Enter at least 10 characters..."
3496
+ />;
3497
+ ```
3498
+
3499
+ ### Auto-Resize Textarea
3500
+
3501
+ ```jsx
3502
+ <Textarea
3503
+ label="Auto-resizing textarea"
3504
+ autoResize
3505
+ value={value}
3506
+ onChange={(e) => setValue(e.target.value)}
3507
+ placeholder="This textarea grows as you type..."
3508
+ />
3509
+ ```
3510
+
3511
+ ### Disabled State
3512
+
3513
+ ```jsx
3514
+ <Textarea
3515
+ label="Disabled textarea"
3516
+ value="This textarea is disabled"
3517
+ disabled
3518
+ />
3519
+ ```
3520
+
3521
+ ### Custom Styling
3522
+
3523
+ ```jsx
3524
+ <Textarea
3525
+ label="Custom styled textarea"
3526
+ value={value}
3527
+ onChange={(e) => setValue(e.target.value)}
3528
+ customClassName="border-2 border-blue-500 focus:ring-blue-600 rounded-lg"
3529
+ placeholder="Custom styled..."
3530
+ />
3531
+ ```
3532
+
3533
+ ### With Rows
3534
+
3535
+ ```jsx
3536
+ <Textarea
3537
+ label="Fixed height textarea"
3538
+ rows={8}
3539
+ value={value}
3540
+ onChange={(e) => setValue(e.target.value)}
3541
+ placeholder="8 rows high..."
3542
+ />
3543
+ ```
3544
+
3545
+ ### Complete Example
3546
+
3547
+ ```jsx
3548
+ import { useState } from "react";
3549
+ import { Textarea } from "alope-ui";
3550
+
3551
+ const CompleteExample = () => {
3552
+ const [feedback, setFeedback] = useState("");
3553
+ const [error, setError] = useState("");
3554
+
3555
+ const handleSubmit = (e: React.FormEvent) => {
3556
+ e.preventDefault();
3557
+
3558
+ if (feedback.length < 20) {
3559
+ setError("Feedback must be at least 20 characters");
3560
+ return;
3561
+ }
3562
+
3563
+ console.log("Feedback submitted:", feedback);
3564
+ setFeedback("");
3565
+ setError("");
3566
+ };
3567
+
3568
+ const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
3569
+ setFeedback(e.target.value);
3570
+ if (error && e.target.value.length >= 20) {
3571
+ setError("");
3572
+ }
3573
+ };
3574
+
3575
+ return (
3576
+ <form onSubmit={handleSubmit} className="max-w-lg mx-auto p-6">
3577
+ <h2 className="text-xl font-bold mb-4">Share Your Feedback</h2>
3578
+
3579
+ <Textarea
3580
+ label="Feedback"
3581
+ name="feedback"
3582
+ value={feedback}
3583
+ onChange={handleChange}
3584
+ error={error}
3585
+ autoResize
3586
+ placeholder="Tell us what you think... (minimum 20 characters)"
3587
+ customClassName="mb-4"
3588
+ />
3589
+
3590
+ <p className="text-sm text-gray-500 mb-4">
3591
+ Character count: {feedback.length}
3592
+ </p>
3593
+
3594
+ <button
3595
+ type="submit"
3596
+ className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
3597
+ >
3598
+ Submit Feedback
3599
+ </button>
3600
+ </form>
3601
+ );
3602
+ };
3603
+ ```
3604
+
3605
+ ### Form Integration Example
3606
+
3607
+ ```jsx
3608
+ import { useState } from "react";
3609
+ import { Textarea } from "alope-ui";
3610
+
3611
+ const FormExample = () => {
3612
+ const [formData, setFormData] = useState({
3613
+ name: "",
3614
+ message: "",
3615
+ comments: "",
3616
+ });
3617
+
3618
+ const handleChange =
3619
+ (field: string) => (e: React.ChangeEvent<HTMLTextAreaElement>) => {
3620
+ setFormData((prev) => ({
3621
+ ...prev,
3622
+ [field]: e.target.value,
3623
+ }));
3624
+ };
3625
+
3626
+ return (
3627
+ <div className="space-y-4 max-w-lg mx-auto p-6">
3628
+ <Textarea
3629
+ label="Message"
3630
+ name="message"
3631
+ value={formData.message}
3632
+ onChange={handleChange("message")}
3633
+ placeholder="Your message..."
3634
+ rows={4}
3635
+ />
3636
+
3637
+ <Textarea
3638
+ label="Additional Comments"
3639
+ name="comments"
3640
+ value={formData.comments}
3641
+ onChange={handleChange("comments")}
3642
+ placeholder="Any additional comments..."
3643
+ autoResize
3644
+ />
3645
+ </div>
3646
+ );
3647
+ };
3648
+ ```
3649
+
3650
+ ---
3651
+
3652
+ ## Tabs
3653
+
3654
+ The Tabs component provides a tabbed navigation interface for organizing and displaying content in separate panels.
3655
+
3656
+ ### Import
3657
+
3658
+ ```jsx
3659
+ import { Tabs } from "alope-ui";
3660
+ import type { Tabs as TabType } from "alope-ui";
3661
+ ```
3662
+
3663
+ ### Props
3664
+
3665
+ | Prop | Type | Default | Description |
3666
+ | ------------------- | ------------------------------------------------------------------- | ----------- | ------------------------------ |
3667
+ | `tabs` | `TabType[]` | `required` | Array of tab objects |
3668
+ | `variant` | `'line' \| 'solid' \| 'subtle' \| 'inline' \| 'outline' \| 'plain'` | `'line'` | Visual variant style |
3669
+ | `fitted` | `boolean` | `false` | Make tabs fill available width |
3670
+ | `wrapperClassName` | `string` | `""` | Wrapper container CSS class |
3671
+ | `buttonClassName` | `string` | `""` | Tab button CSS class |
3672
+ | `activeClassName` | `string` | `""` | Active tab CSS class |
3673
+ | `inactiveClassName` | `string` | `""` | Inactive tab CSS class |
3674
+ | `activeTab` | `number` | `undefined` | Controlled active tab index |
3675
+ | `onTabChange` | `(index: number) => void` | `undefined` | Callback when tab changes |
3676
+
3677
+ ### Tab Object Type
3678
+
3679
+ ```typescript
3680
+ type Tabs = {
3681
+ label: string;
3682
+ content: React.ReactNode;
3683
+ disabled?: boolean;
3684
+ link?: string;
3685
+ };
3686
+ ```
3687
+
3688
+ ### Basic Tabs
3689
+
3690
+ ```jsx
3691
+ import { Tabs } from "alope-ui";
3692
+
3693
+ const BasicExample = () => {
3694
+ const tabs = [
3695
+ {
3696
+ label: "Profile",
3697
+ content: <div>Profile content here</div>,
3698
+ },
3699
+ {
3700
+ label: "Settings",
3701
+ content: <div>Settings content here</div>,
3702
+ },
3703
+ {
3704
+ label: "Notifications",
3705
+ content: <div>Notifications content here</div>,
3706
+ },
3707
+ ];
3708
+
3709
+ return <Tabs tabs={tabs} />;
3710
+ };
3711
+ ```
3712
+
3713
+ ### Variants
3714
+
3715
+ ```jsx
3716
+ {
3717
+ /* Line Variant */
3718
+ }
3719
+ <Tabs tabs={tabs} variant="line" />;
3720
+
3721
+ {
3722
+ /* Solid Variant */
3723
+ }
3724
+ <Tabs tabs={tabs} variant="solid" />;
3725
+
3726
+ {
3727
+ /* Subtle Variant */
3728
+ }
3729
+ <Tabs tabs={tabs} variant="subtle" />;
3730
+
3731
+ {
3732
+ /* Inline Variant */
3733
+ }
3734
+ <Tabs tabs={tabs} variant="inline" />;
3735
+
3736
+ {
3737
+ /* Outline Variant */
3738
+ }
3739
+ <Tabs tabs={tabs} variant="outline" />;
3740
+
3741
+ {
3742
+ /* Plain Variant */
3743
+ }
3744
+ <Tabs tabs={tabs} variant="plain" />;
3745
+ ```
3746
+
3747
+ ### Fitted Tabs
3748
+
3749
+ ```jsx
3750
+ {
3751
+ /* Tabs will fill the available width equally */
3752
+ }
3753
+ <Tabs tabs={tabs} fitted />;
3754
+ ```
3755
+
3756
+ ### Controlled Tabs
3757
+
3758
+ ```jsx
3759
+ import { useState } from "react";
3760
+ import { Tabs } from "alope-ui";
3761
+
3762
+ const ControlledExample = () => {
3763
+ const [activeTab, setActiveTab] = useState(0);
3764
+
3765
+ const tabs = [
3766
+ {
3767
+ label: "Tab 1",
3768
+ content: <div>Content 1</div>,
3769
+ },
3770
+ {
3771
+ label: "Tab 2",
3772
+ content: <div>Content 2</div>,
3773
+ },
3774
+ ];
3775
+
3776
+ return <Tabs tabs={tabs} activeTab={activeTab} onTabChange={setActiveTab} />;
3777
+ };
3778
+ ```
3779
+
3780
+ ### Disabled Tabs
3781
+
3782
+ ```jsx
3783
+ const tabs = [
3784
+ {
3785
+ label: "Active Tab",
3786
+ content: <div>Active content</div>,
3787
+ },
3788
+ {
3789
+ label: "Disabled Tab",
3790
+ content: <div>This content won't show</div>,
3791
+ disabled: true,
3792
+ },
3793
+ ];
3794
+
3795
+ <Tabs tabs={tabs} />;
3796
+ ```
3797
+
3798
+ ### Tabs with Links
3799
+
3800
+ ```jsx
3801
+ const tabs = [
3802
+ {
3803
+ label: "Dashboard",
3804
+ content: <div>Dashboard content</div>,
3805
+ link: "/dashboard",
3806
+ },
3807
+ {
3808
+ label: "Profile",
3809
+ content: <div>Profile content</div>,
3810
+ link: "/profile",
3811
+ },
3812
+ ];
3813
+
3814
+ <Tabs tabs={tabs} />;
3815
+ ```
3816
+
3817
+ ### Custom Styling
3818
+
3819
+ ```jsx
3820
+ <Tabs
3821
+ tabs={tabs}
3822
+ variant="solid"
3823
+ wrapperClassName="bg-gray-100 dark:bg-gray-800"
3824
+ buttonClassName="text-base"
3825
+ activeClassName="font-bold"
3826
+ inactiveClassName="opacity-70"
3827
+ />
3828
+ ```
3829
+
3830
+ ### Complete Example
3831
+
3832
+ ```jsx
3833
+ import { useState } from "react";
3834
+ import { Tabs } from "alope-ui";
3835
+
3836
+ const CompleteExample = () => {
3837
+ const [activeTab, setActiveTab] = useState(0);
3838
+
3839
+ const tabs = [
3840
+ {
3841
+ label: "Overview",
3842
+ content: (
3843
+ <div className="p-4">
3844
+ <h2 className="text-xl font-bold mb-2">Overview</h2>
3845
+ <p>Dashboard overview content goes here.</p>
3846
+ </div>
3847
+ ),
3848
+ },
3849
+ {
3850
+ label: "Analytics",
3851
+ content: (
3852
+ <div className="p-4">
3853
+ <h2 className="text-xl font-bold mb-2">Analytics</h2>
3854
+ <p>Analytics and statistics content.</p>
3855
+ </div>
3856
+ ),
3857
+ },
3858
+ {
3859
+ label: "Reports",
3860
+ content: (
3861
+ <div className="p-4">
3862
+ <h2 className="text-xl font-bold mb-2">Reports</h2>
3863
+ <p>Reports and data visualization.</p>
3864
+ </div>
3865
+ ),
3866
+ },
3867
+ {
3868
+ label: "Settings",
3869
+ content: (
3870
+ <div className="p-4">
3871
+ <h2 className="text-xl font-bold mb-2">Settings</h2>
3872
+ <p>Configuration and preferences.</p>
3873
+ </div>
3874
+ ),
3875
+ disabled: true,
3876
+ },
3877
+ ];
3878
+
3879
+ return (
3880
+ <Tabs
3881
+ tabs={tabs}
3882
+ variant="subtle"
3883
+ fitted
3884
+ activeTab={activeTab}
3885
+ onTabChange={setActiveTab}
3886
+ />
3887
+ );
3888
+ };
3889
+ ```
3890
+
3891
+ ---
3892
+
3893
+ ## Avatar
3894
+
3895
+ The Avatar component displays user profile images with fallback initials, status indicators, and various styling options.
3896
+
3897
+ ### Import
3898
+
3899
+ ```jsx
3900
+ import { Avatar } from "alope-ui";
3901
+ ```
3902
+
3903
+ ### Props
3904
+
3905
+ | Prop | Type | Default | Description |
3906
+ | --------------- | ------------------------------------------------------------------------- | ----------- | ------------------------------ |
3907
+ | `imageSrc` | `string` | `undefined` | URL of the avatar image |
3908
+ | `fallbackName` | `string` | `undefined` | Name to generate initials from |
3909
+ | `fallbackColor` | `'primary' \| 'error' \| 'warning' \| 'success' \| 'info' \| 'secondary'` | `'primary'` | Background color for fallback |
3910
+ | `shape` | `'sharp' \| 'cornered' \| 'rounded'` | `'rounded'` | Shape of the avatar |
3911
+ | `variant` | `'solid' \| 'outline'` | `'solid'` | Visual variant style |
3912
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of the avatar |
3913
+ | `ring` | `boolean` | `false` | Show ring around avatar |
3914
+ | `ringClassName` | `string` | `""` | Custom ring CSS class |
3915
+ | `alt` | `string` | `'avatar'` | Alt text for image |
3916
+ | `status` | `'online' \| 'offline'` | `undefined` | Status indicator |
3917
+ | `className` | `string` | `""` | Additional CSS class |
3918
+
3919
+ ### Basic Avatar
3920
+
3921
+ ```jsx
3922
+ import { Avatar } from "alope-ui";
3923
+
3924
+ const BasicExample = () => {
3925
+ return (
3926
+ <Avatar imageSrc="https://i.pravatar.cc/150?img=1" alt="User Avatar" />
3927
+ );
3928
+ };
3929
+ ```
3930
+
3931
+ ### Fallback with Initials
3932
+
3933
+ ```jsx
3934
+ {
3935
+ /* Avatar with fallback name */
3936
+ }
3937
+ <Avatar fallbackName="John Doe" />;
3938
+
3939
+ {
3940
+ /* Avatar with no image (shows initials) */
3941
+ }
3942
+ <Avatar imageSrc="invalid-url.jpg" fallbackName="Jane Smith" />;
3943
+ ```
3944
+
3945
+ ### Sizes
3946
+
3947
+ ```jsx
3948
+ {
3949
+ /* Small */
3950
+ }
3951
+ <Avatar imageSrc="https://i.pravatar.cc/150?img=1" size="sm" />;
3952
+
3953
+ {
3954
+ /* Medium */
3955
+ }
3956
+ <Avatar imageSrc="https://i.pravatar.cc/150?img=1" size="md" />;
3957
+
3958
+ {
3959
+ /* Large */
3960
+ }
3961
+ <Avatar imageSrc="https://i.pravatar.cc/150?img=1" size="lg" />;
3962
+ ```
3963
+
3964
+ ### Shapes
3965
+
3966
+ ```jsx
3967
+ {
3968
+ /* Sharp corners */
3969
+ }
3970
+ <Avatar imageSrc="https://i.pravatar.cc/150?img=1" shape="sharp" />;
3971
+
3972
+ {
3973
+ /* Cornered (slightly rounded) */
3974
+ }
3975
+ <Avatar imageSrc="https://i.pravatar.cc/150?img=1" shape="cornered" />;
3976
+
3977
+ {
3978
+ /* Rounded (circular) */
3979
+ }
3980
+ <Avatar imageSrc="https://i.pravatar.cc/150?img=1" shape="rounded" />;
3981
+ ```
3982
+
3983
+ ### Variants
3984
+
3985
+ ```jsx
3986
+ {
3987
+ /* Solid variant */
3988
+ }
3989
+ <Avatar fallbackName="John Doe" variant="solid" />;
3990
+
3991
+ {
3992
+ /* Outline variant */
3993
+ }
3994
+ <Avatar fallbackName="John Doe" variant="outline" />;
3995
+ ```
3996
+
3997
+ ### Fallback Colors
3998
+
3999
+ ```jsx
4000
+ {
4001
+ /* Primary color */
4002
+ }
4003
+ <Avatar fallbackName="John Doe" fallbackColor="primary" />;
4004
+
4005
+ {
4006
+ /* Error color */
4007
+ }
4008
+ <Avatar fallbackName="John Doe" fallbackColor="error" />;
4009
+
4010
+ {
4011
+ /* Warning color */
4012
+ }
4013
+ <Avatar fallbackName="John Doe" fallbackColor="warning" />;
4014
+
4015
+ {
4016
+ /* Success color */
4017
+ }
4018
+ <Avatar fallbackName="John Doe" fallbackColor="success" />;
4019
+
4020
+ {
4021
+ /* Info color */
4022
+ }
4023
+ <Avatar fallbackName="John Doe" fallbackColor="info" />;
4024
+
4025
+ {
4026
+ /* Secondary color */
4027
+ }
4028
+ <Avatar fallbackName="John Doe" fallbackColor="secondary" />;
4029
+ ```
4030
+
4031
+ ### With Ring
4032
+
4033
+ ```jsx
4034
+ {
4035
+ /* Default ring */
4036
+ }
4037
+ <Avatar imageSrc="https://i.pravatar.cc/150?img=1" ring />;
4038
+
4039
+ {
4040
+ /* Custom ring color */
4041
+ }
4042
+ <Avatar
4043
+ imageSrc="https://i.pravatar.cc/150?img=1"
4044
+ ring
4045
+ ringClassName="ring-success dark:ring-success-dark"
4046
+ />;
4047
+ ```
4048
+
4049
+ ### Status Indicator
4050
+
4051
+ ```jsx
4052
+ {
4053
+ /* Online status */
4054
+ }
4055
+ <Avatar imageSrc="https://i.pravatar.cc/150?img=1" status="online" />;
4056
+
4057
+ {
4058
+ /* Offline status */
4059
+ }
4060
+ <Avatar imageSrc="https://i.pravatar.cc/150?img=1" status="offline" />;
4061
+ ```
4062
+
4063
+ ### Complete Example
4064
+
4065
+ ```jsx
4066
+ import { Avatar } from "alope-ui";
4067
+
4068
+ const CompleteExample = () => {
4069
+ return (
4070
+ <div className="flex gap-4">
4071
+ <Avatar
4072
+ imageSrc="https://i.pravatar.cc/150?img=1"
4073
+ fallbackName="John Doe"
4074
+ size="lg"
4075
+ shape="rounded"
4076
+ ring
4077
+ status="online"
4078
+ alt="John Doe Avatar"
4079
+ />
4080
+
4081
+ <Avatar
4082
+ imageSrc="https://i.pravatar.cc/150?img=2"
4083
+ fallbackName="Jane Smith"
4084
+ size="lg"
4085
+ shape="rounded"
4086
+ ring
4087
+ status="offline"
4088
+ alt="Jane Smith Avatar"
4089
+ />
4090
+ </div>
4091
+ );
4092
+ };
4093
+ ```
4094
+
4095
+ ## AvatarGroup
4096
+
4097
+ The AvatarGroup component displays multiple avatars in an overlapping stack with a counter for additional avatars.
4098
+
4099
+ ### Import
4100
+
4101
+ ```jsx
4102
+ import { AvatarGroup } from "alope-ui";
4103
+ import type { AvatarGroupProps } from "alope-ui";
4104
+ ```
4105
+
4106
+ ### Props
4107
+
4108
+ | Prop | Type | Default | Description |
4109
+ | ------------ | ---------------------- | ---------- | --------------------------------- |
4110
+ | `avatars` | `AvatarProps[]` | `required` | Array of avatar configurations |
4111
+ | `maxVisible` | `number` | `3` | Maximum number of visible avatars |
4112
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of all avatars in the group |
4113
+ | `overlap` | `string` | `'-ml-3'` | CSS class for overlap spacing |
4114
+ | `className` | `string` | `""` | Additional CSS class |
4115
+
4116
+ ### Basic AvatarGroup
4117
+
4118
+ ```jsx
4119
+ import { AvatarGroup } from "alope-ui";
4120
+
4121
+ const BasicGroupExample = () => {
4122
+ const avatars = [
4123
+ { imageSrc: "https://i.pravatar.cc/150?img=1", fallbackName: "John Doe" },
4124
+ { imageSrc: "https://i.pravatar.cc/150?img=2", fallbackName: "Jane Smith" },
4125
+ { imageSrc: "https://i.pravatar.cc/150?img=3", fallbackName: "Bob Wilson" },
4126
+ ];
4127
+
4128
+ return <AvatarGroup avatars={avatars} />;
4129
+ };
4130
+ ```
4131
+
4132
+ ### With Max Visible Limit
4133
+
4134
+ ```jsx
4135
+ const avatars = [
4136
+ { imageSrc: "https://i.pravatar.cc/150?img=1", fallbackName: "User 1" },
4137
+ { imageSrc: "https://i.pravatar.cc/150?img=2", fallbackName: "User 2" },
4138
+ { imageSrc: "https://i.pravatar.cc/150?img=3", fallbackName: "User 3" },
4139
+ { imageSrc: "https://i.pravatar.cc/150?img=4", fallbackName: "User 4" },
4140
+ { imageSrc: "https://i.pravatar.cc/150?img=5", fallbackName: "User 5" },
4141
+ ];
4142
+
4143
+ {
4144
+ /* Show only 3 avatars, rest shown as "+2" */
4145
+ }
4146
+ <AvatarGroup avatars={avatars} maxVisible={3} />;
4147
+ ```
4148
+
4149
+ ### Different Sizes
4150
+
4151
+ ```jsx
4152
+ {
4153
+ /* Small size */
4154
+ }
4155
+ <AvatarGroup avatars={avatars} size="sm" />;
4156
+
4157
+ {
4158
+ /* Medium size */
4159
+ }
4160
+ <AvatarGroup avatars={avatars} size="md" />;
4161
+
4162
+ {
4163
+ /* Large size */
4164
+ }
4165
+ <AvatarGroup avatars={avatars} size="lg" />;
4166
+ ```
4167
+
4168
+ ### Custom Overlap
4169
+
4170
+ ```jsx
4171
+ {
4172
+ /* More overlap */
4173
+ }
4174
+ <AvatarGroup avatars={avatars} overlap="-ml-4" />;
4175
+
4176
+ {
4177
+ /* Less overlap */
4178
+ }
4179
+ <AvatarGroup avatars={avatars} overlap="-ml-2" />;
4180
+
4181
+ {
4182
+ /* No overlap */
4183
+ }
4184
+ <AvatarGroup avatars={avatars} overlap="" />;
4185
+ ```
4186
+
4187
+ ### Complete AvatarGroup Example
4188
+
4189
+ ```jsx
4190
+ import { AvatarGroup } from "alope-ui";
4191
+
4192
+ const CompleteGroupExample = () => {
4193
+ const teamMembers = [
4194
+ {
4195
+ imageSrc: "https://i.pravatar.cc/150?img=1",
4196
+ fallbackName: "John Doe",
4197
+ status: "online",
4198
+ },
4199
+ {
4200
+ imageSrc: "https://i.pravatar.cc/150?img=2",
4201
+ fallbackName: "Jane Smith",
4202
+ status: "online",
4203
+ },
4204
+ {
4205
+ imageSrc: "https://i.pravatar.cc/150?img=3",
4206
+ fallbackName: "Bob Wilson",
4207
+ status: "offline",
4208
+ },
4209
+ {
4210
+ fallbackName: "Alice Brown",
4211
+ fallbackColor: "success",
4212
+ },
4213
+ {
4214
+ fallbackName: "Charlie Davis",
4215
+ fallbackColor: "info",
4216
+ },
4217
+ ];
4218
+
4219
+ return (
4220
+ <AvatarGroup
4221
+ avatars={teamMembers}
4222
+ maxVisible={3}
4223
+ size="md"
4224
+ overlap="-ml-3"
4225
+ />
4226
+ );
4227
+ };
4228
+ ```
4229
+
4230
+ ---
4231
+
1706
4232
  ## 💡 Contributing
1707
4233
 
1708
4234
  We welcome contributions! If you want to add features, fix bugs, or improve documentation, feel free to fork this project and submit a pull request.