omnira-ui 0.4.1 → 0.5.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.
Files changed (28) hide show
  1. package/components/ui/Alert/Alert.module.css +224 -0
  2. package/components/ui/Alert/Alert.tsx +138 -0
  3. package/components/ui/Alert/index.ts +2 -0
  4. package/components/ui/Breadcrumbs/Breadcrumbs.module.css +134 -0
  5. package/components/ui/Breadcrumbs/Breadcrumbs.tsx +139 -0
  6. package/components/ui/Breadcrumbs/index.ts +2 -0
  7. package/components/ui/Calendar/Calendar.module.css +235 -6
  8. package/components/ui/Calendar/Calendar.tsx +212 -11
  9. package/components/ui/Calendar/config.ts +54 -4
  10. package/components/ui/CodeSnippet/CodeSnippet.module.css +158 -0
  11. package/components/ui/CodeSnippet/CodeSnippet.tsx +106 -0
  12. package/components/ui/CodeSnippet/index.ts +2 -0
  13. package/components/ui/ContentDivider/ContentDivider.module.css +214 -0
  14. package/components/ui/ContentDivider/ContentDivider.tsx +197 -0
  15. package/components/ui/ContentDivider/index.ts +10 -0
  16. package/components/ui/DatePicker/DatePicker.module.css +319 -0
  17. package/components/ui/DatePicker/DatePicker.tsx +614 -0
  18. package/components/ui/DatePicker/index.ts +14 -0
  19. package/components/ui/FileUpload/FileUpload.module.css +322 -0
  20. package/components/ui/FileUpload/FileUpload.tsx +413 -0
  21. package/components/ui/FileUpload/index.ts +8 -0
  22. package/components/ui/LoadingIndicator/LoadingIndicator.module.css +381 -0
  23. package/components/ui/LoadingIndicator/LoadingIndicator.tsx +226 -0
  24. package/components/ui/LoadingIndicator/index.ts +8 -0
  25. package/components/ui/Tabs/Tabs.module.css +257 -0
  26. package/components/ui/Tabs/Tabs.tsx +149 -0
  27. package/components/ui/Tabs/index.ts +2 -0
  28. package/package.json +1 -1
@@ -0,0 +1,224 @@
1
+ /* ============================================
2
+ Alert — Glassmorphism Styles
3
+ ============================================ */
4
+
5
+ /* ── Base ── */
6
+
7
+ .alert {
8
+ display: flex;
9
+ align-items: flex-start;
10
+ gap: 12px;
11
+ padding: 16px;
12
+ border-radius: var(--radius-xl);
13
+ border: 1px solid var(--color-border-subtle);
14
+ background: var(--color-bg-card);
15
+ position: relative;
16
+ }
17
+
18
+ /* ── Full Width ── */
19
+
20
+ .fullWidth {
21
+ border-radius: 0;
22
+ border-left: none;
23
+ border-right: none;
24
+ width: 100%;
25
+ }
26
+
27
+ /* ── Icon ── */
28
+
29
+ .iconWrap {
30
+ width: 20px;
31
+ height: 20px;
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ flex-shrink: 0;
36
+ margin-top: 1px;
37
+ }
38
+
39
+ /* ── Content ── */
40
+
41
+ .content {
42
+ flex: 1;
43
+ min-width: 0;
44
+ display: flex;
45
+ flex-direction: column;
46
+ gap: 4px;
47
+ }
48
+
49
+ .title {
50
+ font-family: var(--font-body);
51
+ font-size: 14px;
52
+ font-weight: 600;
53
+ color: var(--color-text-primary);
54
+ line-height: 1.4;
55
+ }
56
+
57
+ .description {
58
+ font-family: var(--font-body);
59
+ font-size: 13px;
60
+ color: var(--color-text-secondary);
61
+ line-height: 1.5;
62
+ }
63
+
64
+ /* ── Actions ── */
65
+
66
+ .actions {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 8px;
70
+ margin-top: 8px;
71
+ }
72
+
73
+ .actionBtn {
74
+ font-family: var(--font-body);
75
+ font-size: 13px;
76
+ font-weight: 600;
77
+ padding: 6px 12px;
78
+ border-radius: var(--radius-md);
79
+ border: none;
80
+ cursor: pointer;
81
+ transition: all 0.15s;
82
+ }
83
+
84
+ .actionPrimary {
85
+ background: var(--color-lime);
86
+ color: var(--color-bg-primary);
87
+ }
88
+
89
+ .actionPrimary:hover {
90
+ opacity: 0.9;
91
+ }
92
+
93
+ .actionSecondary {
94
+ background: transparent;
95
+ color: var(--color-text-secondary);
96
+ border: 1px solid var(--color-border-subtle);
97
+ }
98
+
99
+ .actionSecondary:hover {
100
+ background: var(--color-bg-hover);
101
+ }
102
+
103
+ /* ── Close Button ── */
104
+
105
+ .closeBtn {
106
+ width: 24px;
107
+ height: 24px;
108
+ display: flex;
109
+ align-items: center;
110
+ justify-content: center;
111
+ border-radius: var(--radius-md);
112
+ border: none;
113
+ background: transparent;
114
+ color: var(--color-text-tertiary);
115
+ cursor: pointer;
116
+ flex-shrink: 0;
117
+ padding: 0;
118
+ transition: all 0.15s;
119
+ }
120
+
121
+ .closeBtn:hover {
122
+ background: var(--color-bg-hover);
123
+ color: var(--color-text-primary);
124
+ }
125
+
126
+ /* ══════════════════════════════════════════════
127
+ Color Variants
128
+ ══════════════════════════════════════════════ */
129
+
130
+ /* Default */
131
+ .colorDefault {
132
+ border-color: var(--color-border-subtle);
133
+ background: var(--color-bg-card);
134
+ }
135
+
136
+ .colorDefault .iconWrap {
137
+ color: var(--color-text-tertiary);
138
+ }
139
+
140
+ /* Brand */
141
+ .colorBrand {
142
+ border-color: var(--color-lime);
143
+ background: var(--color-bg-lime-subtle);
144
+ }
145
+
146
+ .colorBrand .iconWrap {
147
+ color: var(--color-lime);
148
+ }
149
+
150
+ .colorBrand .title {
151
+ color: var(--color-lime);
152
+ }
153
+
154
+ .colorBrand .actionPrimary {
155
+ background: var(--color-lime);
156
+ color: var(--color-bg-primary);
157
+ }
158
+
159
+ /* Gray */
160
+ .colorGray {
161
+ border-color: var(--color-border-medium);
162
+ background: var(--color-bg-elevated);
163
+ }
164
+
165
+ .colorGray .iconWrap {
166
+ color: var(--color-text-tertiary);
167
+ }
168
+
169
+ /* Error */
170
+ .colorError {
171
+ border-color: var(--color-error);
172
+ background: var(--color-error-bg);
173
+ }
174
+
175
+ .colorError .iconWrap {
176
+ color: var(--color-error);
177
+ }
178
+
179
+ .colorError .title {
180
+ color: var(--color-error);
181
+ }
182
+
183
+ .colorError .actionPrimary {
184
+ background: var(--color-error);
185
+ color: #fff;
186
+ }
187
+
188
+ /* Warning */
189
+ .colorWarning {
190
+ border-color: var(--color-warning);
191
+ background: var(--color-warning-bg);
192
+ }
193
+
194
+ .colorWarning .iconWrap {
195
+ color: var(--color-warning);
196
+ }
197
+
198
+ .colorWarning .title {
199
+ color: var(--color-warning);
200
+ }
201
+
202
+ .colorWarning .actionPrimary {
203
+ background: var(--color-warning);
204
+ color: var(--color-bg-primary);
205
+ }
206
+
207
+ /* Success */
208
+ .colorSuccess {
209
+ border-color: var(--color-success);
210
+ background: var(--color-success-bg);
211
+ }
212
+
213
+ .colorSuccess .iconWrap {
214
+ color: var(--color-success);
215
+ }
216
+
217
+ .colorSuccess .title {
218
+ color: var(--color-success);
219
+ }
220
+
221
+ .colorSuccess .actionPrimary {
222
+ background: var(--color-success);
223
+ color: #fff;
224
+ }
@@ -0,0 +1,138 @@
1
+ "use client";
2
+
3
+ import React, { useState } from "react";
4
+ import { InfoCircle, TickCircle, Danger, Warning2, CloseCircle } from "iconsax-react";
5
+ import { cn } from "@/lib/cn";
6
+ import s from "./Alert.module.css";
7
+
8
+ /* ══════════════════════════════════════════════
9
+ Types
10
+ ══════════════════════════════════════════════ */
11
+
12
+ export type AlertColor = "default" | "brand" | "gray" | "error" | "warning" | "success";
13
+ export type AlertVariant = "floating" | "fullWidth";
14
+
15
+ export interface AlertAction {
16
+ label: string;
17
+ onClick?: () => void;
18
+ variant?: "primary" | "secondary";
19
+ }
20
+
21
+ export interface AlertProps {
22
+ /** Alert title */
23
+ title: string;
24
+ /** Alert description */
25
+ description?: string;
26
+ /** Color theme */
27
+ color?: AlertColor;
28
+ /** Layout variant */
29
+ variant?: AlertVariant;
30
+ /** Custom icon element. Pass null to hide the icon. */
31
+ icon?: React.ReactNode | null;
32
+ /** Action buttons */
33
+ actions?: AlertAction[];
34
+ /** Show close button */
35
+ dismissible?: boolean;
36
+ /** Callback when dismissed */
37
+ onDismiss?: () => void;
38
+ /** Additional CSS class */
39
+ className?: string;
40
+ /** Children rendered after description */
41
+ children?: React.ReactNode;
42
+ }
43
+
44
+ /* ══════════════════════════════════════════════
45
+ Helpers
46
+ ══════════════════════════════════════════════ */
47
+
48
+ function getDefaultIcon(color: AlertColor) {
49
+ switch (color) {
50
+ case "error": return <Danger size={20} variant="Bulk" color="currentColor" />;
51
+ case "warning": return <Warning2 size={20} variant="Bulk" color="currentColor" />;
52
+ case "success": return <TickCircle size={20} variant="Bulk" color="currentColor" />;
53
+ default: return <InfoCircle size={20} variant="Bulk" color="currentColor" />;
54
+ }
55
+ }
56
+
57
+ function getColorCls(color: AlertColor) {
58
+ switch (color) {
59
+ case "brand": return s.colorBrand;
60
+ case "gray": return s.colorGray;
61
+ case "error": return s.colorError;
62
+ case "warning": return s.colorWarning;
63
+ case "success": return s.colorSuccess;
64
+ default: return s.colorDefault;
65
+ }
66
+ }
67
+
68
+ /* ══════════════════════════════════════════════
69
+ Alert
70
+ ══════════════════════════════════════════════ */
71
+
72
+ export function Alert({
73
+ title,
74
+ description,
75
+ color = "default",
76
+ variant = "floating",
77
+ icon,
78
+ actions,
79
+ dismissible = false,
80
+ onDismiss,
81
+ className,
82
+ children,
83
+ }: AlertProps) {
84
+ const [visible, setVisible] = useState(true);
85
+
86
+ if (!visible) return null;
87
+
88
+ const handleDismiss = () => {
89
+ setVisible(false);
90
+ onDismiss?.();
91
+ };
92
+
93
+ const showIcon = icon !== null;
94
+ const iconElement = icon ?? getDefaultIcon(color);
95
+
96
+ return (
97
+ <div
98
+ className={cn(
99
+ s.alert,
100
+ variant === "fullWidth" && s.fullWidth,
101
+ getColorCls(color),
102
+ className,
103
+ )}
104
+ role="alert"
105
+ >
106
+ {showIcon && <div className={s.iconWrap}>{iconElement}</div>}
107
+
108
+ <div className={s.content}>
109
+ <div className={s.title}>{title}</div>
110
+ {description && <div className={s.description}>{description}</div>}
111
+ {children}
112
+ {actions && actions.length > 0 && (
113
+ <div className={s.actions}>
114
+ {actions.map((action, i) => (
115
+ <button
116
+ key={i}
117
+ type="button"
118
+ className={cn(
119
+ s.actionBtn,
120
+ action.variant === "secondary" ? s.actionSecondary : s.actionPrimary,
121
+ )}
122
+ onClick={action.onClick}
123
+ >
124
+ {action.label}
125
+ </button>
126
+ ))}
127
+ </div>
128
+ )}
129
+ </div>
130
+
131
+ {dismissible && (
132
+ <button type="button" className={s.closeBtn} onClick={handleDismiss} aria-label="Dismiss">
133
+ <CloseCircle size={16} variant="Linear" color="currentColor" />
134
+ </button>
135
+ )}
136
+ </div>
137
+ );
138
+ }
@@ -0,0 +1,2 @@
1
+ export { Alert } from "./Alert";
2
+ export type { AlertProps, AlertAction, AlertColor, AlertVariant } from "./Alert";
@@ -0,0 +1,134 @@
1
+ /* ============================================
2
+ Breadcrumbs — Glassmorphism Styles
3
+ ============================================ */
4
+
5
+ /* ── Base ── */
6
+
7
+ .nav {
8
+ display: flex;
9
+ align-items: center;
10
+ }
11
+
12
+ /* ── List ── */
13
+
14
+ .list {
15
+ display: flex;
16
+ align-items: center;
17
+ gap: 0;
18
+ list-style: none;
19
+ margin: 0;
20
+ padding: 0;
21
+ flex-wrap: wrap;
22
+ }
23
+
24
+ /* ── Item ── */
25
+
26
+ .item {
27
+ display: flex;
28
+ align-items: center;
29
+ gap: 0;
30
+ }
31
+
32
+ /* ── Separator ── */
33
+
34
+ .separator {
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ color: var(--color-text-tertiary);
39
+ padding: 0 6px;
40
+ user-select: none;
41
+ font-size: 13px;
42
+ }
43
+
44
+ .separatorLine {
45
+ width: 1px;
46
+ height: 14px;
47
+ background: var(--color-border-medium);
48
+ transform: rotate(15deg);
49
+ margin: 0 8px;
50
+ }
51
+
52
+ /* ── Link ── */
53
+
54
+ .link {
55
+ font-family: var(--font-body);
56
+ font-size: 13px;
57
+ font-weight: 500;
58
+ color: var(--color-text-tertiary);
59
+ text-decoration: none;
60
+ transition: color 0.15s;
61
+ white-space: nowrap;
62
+ cursor: pointer;
63
+ }
64
+
65
+ .link:hover {
66
+ color: var(--color-text-primary);
67
+ }
68
+
69
+ /* ── Current (last item) ── */
70
+
71
+ .current {
72
+ font-family: var(--font-body);
73
+ font-size: 13px;
74
+ font-weight: 600;
75
+ color: var(--color-text-primary);
76
+ white-space: nowrap;
77
+ }
78
+
79
+ /* ── Button variant ── */
80
+
81
+ .btnLink {
82
+ font-family: var(--font-body);
83
+ font-size: 13px;
84
+ font-weight: 500;
85
+ color: var(--color-text-tertiary);
86
+ text-decoration: none;
87
+ padding: 4px 10px;
88
+ border-radius: var(--radius-md);
89
+ border: none;
90
+ background: transparent;
91
+ cursor: pointer;
92
+ transition: all 0.15s;
93
+ white-space: nowrap;
94
+ }
95
+
96
+ .btnLink:hover {
97
+ background: var(--color-bg-hover);
98
+ color: var(--color-text-primary);
99
+ }
100
+
101
+ .btnCurrent {
102
+ font-family: var(--font-body);
103
+ font-size: 13px;
104
+ font-weight: 600;
105
+ color: var(--color-text-primary);
106
+ padding: 4px 10px;
107
+ border-radius: var(--radius-md);
108
+ background: var(--color-bg-elevated);
109
+ white-space: nowrap;
110
+ }
111
+
112
+ /* ── Ellipsis ── */
113
+
114
+ .ellipsis {
115
+ font-family: var(--font-body);
116
+ font-size: 13px;
117
+ color: var(--color-text-tertiary);
118
+ padding: 0 2px;
119
+ user-select: none;
120
+ }
121
+
122
+ /* ── Home Icon ── */
123
+
124
+ .homeIcon {
125
+ display: flex;
126
+ align-items: center;
127
+ justify-content: center;
128
+ color: var(--color-text-tertiary);
129
+ transition: color 0.15s;
130
+ }
131
+
132
+ .homeIcon:hover {
133
+ color: var(--color-text-primary);
134
+ }
@@ -0,0 +1,139 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { Home2, ArrowRight2 } from "iconsax-react";
5
+ import { cn } from "@/lib/cn";
6
+ import s from "./Breadcrumbs.module.css";
7
+
8
+ /* ══════════════════════════════════════════════
9
+ Types
10
+ ══════════════════════════════════════════════ */
11
+
12
+ export type BreadcrumbVariant = "text" | "textLine" | "button";
13
+
14
+ export interface BreadcrumbItem {
15
+ /** Display label */
16
+ label: string;
17
+ /** URL to navigate to. Omit for the current (last) item. */
18
+ href?: string;
19
+ /** Custom icon element */
20
+ icon?: React.ReactNode;
21
+ }
22
+
23
+ export interface BreadcrumbsProps {
24
+ /** Array of breadcrumb items */
25
+ items: BreadcrumbItem[];
26
+ /** Visual variant */
27
+ variant?: BreadcrumbVariant;
28
+ /** Show a home icon as the first item */
29
+ showHome?: boolean;
30
+ /** Home URL */
31
+ homeHref?: string;
32
+ /** Max items to show before collapsing with ellipsis. 0 = no collapse. */
33
+ maxItems?: number;
34
+ /** Custom separator element */
35
+ separator?: React.ReactNode;
36
+ /** Additional CSS class */
37
+ className?: string;
38
+ }
39
+
40
+ /* ══════════════════════════════════════════════
41
+ Breadcrumbs
42
+ ══════════════════════════════════════════════ */
43
+
44
+ export function Breadcrumbs({
45
+ items,
46
+ variant = "text",
47
+ showHome = false,
48
+ homeHref = "/",
49
+ maxItems = 0,
50
+ separator,
51
+ className,
52
+ }: BreadcrumbsProps) {
53
+ const isButton = variant === "button";
54
+ const isLine = variant === "textLine";
55
+
56
+ /* Collapse items if maxItems is set */
57
+ let displayItems = items;
58
+ let collapsed = false;
59
+ if (maxItems > 0 && items.length > maxItems) {
60
+ const first = items.slice(0, 1);
61
+ const last = items.slice(-(maxItems - 1));
62
+ displayItems = [...first, { label: "…" }, ...last];
63
+ collapsed = true;
64
+ }
65
+
66
+ const renderSeparator = (key: string) => {
67
+ if (separator) {
68
+ return <span key={key} className={s.separator}>{separator}</span>;
69
+ }
70
+ if (isLine) {
71
+ return <span key={key} className={s.separatorLine} />;
72
+ }
73
+ return (
74
+ <span key={key} className={s.separator}>
75
+ <ArrowRight2 size={14} variant="Linear" color="currentColor" />
76
+ </span>
77
+ );
78
+ };
79
+
80
+ const renderItem = (item: BreadcrumbItem, index: number, isLast: boolean) => {
81
+ /* Ellipsis */
82
+ if (item.label === "…" && collapsed) {
83
+ return <span className={s.ellipsis}>…</span>;
84
+ }
85
+
86
+ /* Current (last) item */
87
+ if (isLast) {
88
+ return (
89
+ <span className={isButton ? s.btnCurrent : s.current} aria-current="page">
90
+ {item.icon}
91
+ {item.label}
92
+ </span>
93
+ );
94
+ }
95
+
96
+ /* Link item */
97
+ if (item.href) {
98
+ return (
99
+ <a href={item.href} className={isButton ? s.btnLink : s.link}>
100
+ {item.icon}
101
+ {item.label}
102
+ </a>
103
+ );
104
+ }
105
+
106
+ return (
107
+ <span className={isButton ? s.btnLink : s.link}>
108
+ {item.icon}
109
+ {item.label}
110
+ </span>
111
+ );
112
+ };
113
+
114
+ return (
115
+ <nav className={cn(s.nav, className)} aria-label="Breadcrumb">
116
+ <ol className={s.list}>
117
+ {showHome && (
118
+ <>
119
+ <li className={s.item}>
120
+ <a href={homeHref} className={cn(isButton ? s.btnLink : s.link, s.homeIcon)} aria-label="Home">
121
+ <Home2 size={16} variant="Bulk" color="currentColor" />
122
+ </a>
123
+ </li>
124
+ {renderSeparator("home-sep")}
125
+ </>
126
+ )}
127
+ {displayItems.map((item, i) => {
128
+ const isLast = i === displayItems.length - 1;
129
+ return (
130
+ <React.Fragment key={i}>
131
+ <li className={s.item}>{renderItem(item, i, isLast)}</li>
132
+ {!isLast && renderSeparator(`sep-${i}`)}
133
+ </React.Fragment>
134
+ );
135
+ })}
136
+ </ol>
137
+ </nav>
138
+ );
139
+ }
@@ -0,0 +1,2 @@
1
+ export { Breadcrumbs } from "./Breadcrumbs";
2
+ export type { BreadcrumbsProps, BreadcrumbItem, BreadcrumbVariant } from "./Breadcrumbs";