@attrus-ds/core 0.1.0 → 0.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 +80 -8
- package/dist/css/components/buttons.css +1 -1
- package/dist/css/components/combobox.css +17 -1
- package/dist/css/components/inputs.css +3 -55
- package/dist/css/components/overlays.css +28 -59
- package/dist/css/components/primitives.css +10 -1
- package/dist/css/components/sidebar.css +3 -3
- package/dist/css/components/smart-search.css +91 -0
- package/dist/css/components/table.css +10 -8
- package/dist/css/components/tabs.css +2 -2
- package/dist/css/components/toast.css +4 -0
- package/dist/css/components/tooltip.css +5 -0
- package/dist/css/index.css +1 -0
- package/dist/css/tokens.css +100 -96
- package/dist/react/AppSidebar/AppSidebar.d.ts +50 -0
- package/dist/react/AppSidebar/AppSidebar.tsx +197 -0
- package/dist/react/AppTopbar/AppTopbar.d.ts +58 -0
- package/dist/react/AppTopbar/AppTopbar.tsx +203 -0
- package/dist/react/Atoms/Atoms.d.ts +149 -0
- package/dist/react/Atoms/Atoms.tsx +407 -0
- package/dist/react/Button/Button.d.ts +25 -0
- package/dist/react/Button/Button.tsx +83 -0
- package/dist/react/Callout/Callout.d.ts +29 -0
- package/dist/react/Callout/Callout.tsx +98 -0
- package/dist/react/Card/Card.d.ts +38 -0
- package/dist/react/Card/Card.tsx +93 -0
- package/dist/react/Combobox/Combobox.d.ts +52 -0
- package/dist/react/Combobox/Combobox.tsx +254 -0
- package/dist/react/DataTable/DataTable.d.ts +81 -0
- package/dist/react/DataTable/DataTable.tsx +308 -0
- package/dist/react/Drawer/Drawer.d.ts +35 -0
- package/dist/react/Drawer/Drawer.tsx +226 -0
- package/dist/react/Input/Input.d.ts +155 -0
- package/dist/react/Input/Input.tsx +478 -0
- package/dist/react/Modal/Modal.d.ts +27 -0
- package/dist/react/Modal/Modal.tsx +173 -0
- package/dist/react/NumberInput/NumberInput.d.ts +25 -0
- package/dist/react/NumberInput/NumberInput.tsx +117 -0
- package/dist/react/Pill/Pill.d.ts +31 -0
- package/dist/react/Pill/Pill.tsx +104 -0
- package/dist/react/SearchPill/SearchPill.d.ts +14 -0
- package/dist/react/SearchPill/SearchPill.tsx +40 -0
- package/dist/react/SmartSearch/SmartSearch.d.ts +65 -0
- package/dist/react/SmartSearch/SmartSearch.tsx +271 -0
- package/dist/react/Stepper/Stepper.d.ts +25 -0
- package/dist/react/Stepper/Stepper.tsx +94 -0
- package/dist/react/Tabs/Tabs.d.ts +32 -0
- package/dist/react/Tabs/Tabs.tsx +147 -0
- package/dist/react/Toast/Toast.d.ts +30 -0
- package/dist/react/Toast/Toast.tsx +94 -0
- package/dist/react/Tooltip/Tooltip.d.ts +23 -0
- package/dist/react/Tooltip/Tooltip.tsx +80 -0
- package/dist/react/ZeroState/ZeroState.d.ts +17 -0
- package/dist/react/ZeroState/ZeroState.tsx +45 -0
- package/dist/react/index.ts +35 -0
- package/dist/tokens/attrus-tokens.flat.json +1 -1
- package/dist/tokens/attrus-tokens.json +1 -1
- package/package.json +20 -2
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ATTRUS AppTopbar — typed wrapper over the canonical `.topbar` system
|
|
5
|
+
* (preview/components/topbar.css). The app's top edge: primary action,
|
|
6
|
+
* global search, and a right-aligned cluster of 36px controls
|
|
7
|
+
* (--tb-control-h drives every item). Includes the live indicator,
|
|
8
|
+
* notification bell and the account menu (identity + dropdown).
|
|
9
|
+
* (Named AppTopbar — "Shell"/"TopBar" collide with legacy app files.)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface AppTopbarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
13
|
+
/** Left CTA slot — the canonical Button (primary). */
|
|
14
|
+
primary?: React.ReactNode;
|
|
15
|
+
/** Search slot — the canonical SearchPill (grows to fill). */
|
|
16
|
+
search?: React.ReactNode;
|
|
17
|
+
/** Right-aligned cluster (.tb-spacer): TopbarControl/Live/Bell/Account. */
|
|
18
|
+
children?: React.ReactNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const AppTopbar: React.FC<AppTopbarProps> = ({ primary, search, children, className, ...rest }) => (
|
|
22
|
+
<div className={['topbar', className || ''].filter(Boolean).join(' ')} {...rest}>
|
|
23
|
+
{primary}
|
|
24
|
+
{search}
|
|
25
|
+
<div className="tb-spacer">{children}</div>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
/* ---------- Generic 36px control ---------- */
|
|
30
|
+
|
|
31
|
+
export interface TopbarControlProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
32
|
+
/** Leading slot (icon, flag…). */
|
|
33
|
+
icon?: React.ReactNode;
|
|
34
|
+
/** Square icon-only control (.tb-control.icon). */
|
|
35
|
+
iconOnly?: boolean;
|
|
36
|
+
/** Trailing disclosure chevron. */
|
|
37
|
+
chev?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const ChevDown: React.FC = () => (
|
|
41
|
+
<span className="chev" aria-hidden="true">
|
|
42
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
|
|
43
|
+
<path d="m6 9 6 6 6-6" />
|
|
44
|
+
</svg>
|
|
45
|
+
</span>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
export const TopbarControl: React.FC<TopbarControlProps> = ({ icon, iconOnly = false, chev = false, className, children, ...rest }) => (
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
className={['tb-control', iconOnly ? 'icon' : '', className || ''].filter(Boolean).join(' ')}
|
|
52
|
+
{...rest}
|
|
53
|
+
>
|
|
54
|
+
{icon}
|
|
55
|
+
{children}
|
|
56
|
+
{chev ? <ChevDown /> : null}
|
|
57
|
+
</button>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
/* ---------- Live session indicator (non-interactive) ---------- */
|
|
61
|
+
|
|
62
|
+
export interface TopbarLiveProps {
|
|
63
|
+
label?: React.ReactNode;
|
|
64
|
+
/** Elapsed mono clock (e.g. "0:19"). */
|
|
65
|
+
clock?: React.ReactNode;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const TopbarLive: React.FC<TopbarLiveProps> = ({ label = 'Live', clock }) => (
|
|
69
|
+
<div className="tb-control tb-live">
|
|
70
|
+
<span className="dot" />
|
|
71
|
+
<span className="lbl">{label}</span>
|
|
72
|
+
{clock != null ? <span className="tb-clock">{clock}</span> : null}
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
/* ---------- Notification bell ---------- */
|
|
77
|
+
|
|
78
|
+
export interface TopbarBellProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
79
|
+
/** Unread red dot. */
|
|
80
|
+
badge?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const TopbarBell: React.FC<TopbarBellProps> = ({ badge = false, className, ...rest }) => (
|
|
84
|
+
<button
|
|
85
|
+
type="button"
|
|
86
|
+
className={['tb-control', 'icon', 'tb-bell', className || ''].filter(Boolean).join(' ')}
|
|
87
|
+
aria-label="Notifications"
|
|
88
|
+
{...rest}
|
|
89
|
+
>
|
|
90
|
+
<span className="badge-anchor">
|
|
91
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.75} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" style={{ width: 14, height: 14 }}>
|
|
92
|
+
<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" />
|
|
93
|
+
<path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" />
|
|
94
|
+
</svg>
|
|
95
|
+
{badge ? <span className="badge" /> : null}
|
|
96
|
+
</span>
|
|
97
|
+
</button>
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
/* ---------- Account menu (identity + dropdown) ---------- */
|
|
101
|
+
|
|
102
|
+
export interface TopbarAccountItem {
|
|
103
|
+
key: string;
|
|
104
|
+
label: React.ReactNode;
|
|
105
|
+
icon?: React.ReactNode;
|
|
106
|
+
danger?: boolean;
|
|
107
|
+
/** Renders a separator before this item. */
|
|
108
|
+
sep?: boolean;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface TopbarAccountProps {
|
|
112
|
+
initials: string;
|
|
113
|
+
name: React.ReactNode;
|
|
114
|
+
company: React.ReactNode;
|
|
115
|
+
/** Second line in the dropdown head (e.g. "Acme Corp · Admin"). */
|
|
116
|
+
detail?: React.ReactNode;
|
|
117
|
+
items?: TopbarAccountItem[];
|
|
118
|
+
onSelect?: (key: string) => void;
|
|
119
|
+
/** Hide name/company in the trigger (mobile). */
|
|
120
|
+
compact?: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const TopbarAccount: React.FC<TopbarAccountProps> = ({
|
|
124
|
+
initials,
|
|
125
|
+
name,
|
|
126
|
+
company,
|
|
127
|
+
detail,
|
|
128
|
+
items = [],
|
|
129
|
+
onSelect,
|
|
130
|
+
compact = false,
|
|
131
|
+
}) => {
|
|
132
|
+
const [open, setOpen] = React.useState(false);
|
|
133
|
+
const wrapRef = React.useRef<HTMLDivElement>(null);
|
|
134
|
+
|
|
135
|
+
React.useEffect(() => {
|
|
136
|
+
if (!open) return undefined;
|
|
137
|
+
const onDoc = (e: MouseEvent) => {
|
|
138
|
+
if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) setOpen(false);
|
|
139
|
+
};
|
|
140
|
+
const onKey = (e: KeyboardEvent) => {
|
|
141
|
+
if (e.key === 'Escape') setOpen(false);
|
|
142
|
+
};
|
|
143
|
+
document.addEventListener('mousedown', onDoc);
|
|
144
|
+
document.addEventListener('keydown', onKey);
|
|
145
|
+
return () => {
|
|
146
|
+
document.removeEventListener('mousedown', onDoc);
|
|
147
|
+
document.removeEventListener('keydown', onKey);
|
|
148
|
+
};
|
|
149
|
+
}, [open]);
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div className="tb-account-wrap" ref={wrapRef}>
|
|
153
|
+
<button
|
|
154
|
+
type="button"
|
|
155
|
+
className={['tb-account', open ? 'is-open' : ''].filter(Boolean).join(' ')}
|
|
156
|
+
aria-haspopup="menu"
|
|
157
|
+
aria-expanded={open}
|
|
158
|
+
aria-label="Account menu"
|
|
159
|
+
onClick={() => setOpen((o) => !o)}
|
|
160
|
+
>
|
|
161
|
+
<span className="tb-avatar">{initials}</span>
|
|
162
|
+
{!compact ? (
|
|
163
|
+
<span className="tb-account-id">
|
|
164
|
+
<span className="nm">{name}</span>
|
|
165
|
+
<span className="co">{company}</span>
|
|
166
|
+
</span>
|
|
167
|
+
) : null}
|
|
168
|
+
{!compact ? <ChevDown /> : null}
|
|
169
|
+
</button>
|
|
170
|
+
{open ? (
|
|
171
|
+
<div className="tb-account-menu" role="menu">
|
|
172
|
+
<div className="tb-account-menu-head">
|
|
173
|
+
<span className="tb-avatar lg">{initials}</span>
|
|
174
|
+
<div className="tb-account-menu-id">
|
|
175
|
+
<span className="nm">{name}</span>
|
|
176
|
+
<span className="co">{detail != null ? detail : company}</span>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
<div className="tb-account-menu-sep" />
|
|
180
|
+
{items.map((it) => (
|
|
181
|
+
<React.Fragment key={it.key}>
|
|
182
|
+
{it.sep ? <div className="tb-account-menu-sep" /> : null}
|
|
183
|
+
<button
|
|
184
|
+
type="button"
|
|
185
|
+
className={['tb-account-menu-item', it.danger ? 'is-danger' : ''].filter(Boolean).join(' ')}
|
|
186
|
+
role="menuitem"
|
|
187
|
+
onClick={() => {
|
|
188
|
+
setOpen(false);
|
|
189
|
+
if (onSelect) onSelect(it.key);
|
|
190
|
+
}}
|
|
191
|
+
>
|
|
192
|
+
{it.icon}
|
|
193
|
+
{it.label}
|
|
194
|
+
</button>
|
|
195
|
+
</React.Fragment>
|
|
196
|
+
))}
|
|
197
|
+
</div>
|
|
198
|
+
) : null}
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export default AppTopbar;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type MoneySize = 'sm' | 'md' | 'lg' | 'xl' | 'display';
|
|
4
|
+
export type MoneyTone = 'pos' | 'neg' | 'muted';
|
|
5
|
+
export interface MoneyProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
6
|
+
cur?: string;
|
|
7
|
+
amount: React.ReactNode;
|
|
8
|
+
frac?: React.ReactNode;
|
|
9
|
+
size?: MoneySize;
|
|
10
|
+
tone?: MoneyTone;
|
|
11
|
+
}
|
|
12
|
+
export declare const Money: React.FC<MoneyProps>;
|
|
13
|
+
|
|
14
|
+
export type MetricSize = 'sm' | 'md' | 'lg';
|
|
15
|
+
export type MetricTone = 'pos' | 'neg';
|
|
16
|
+
export interface MetricProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
17
|
+
cur?: string;
|
|
18
|
+
amount: React.ReactNode;
|
|
19
|
+
frac?: React.ReactNode;
|
|
20
|
+
size?: MetricSize;
|
|
21
|
+
tone?: MetricTone;
|
|
22
|
+
}
|
|
23
|
+
export declare const Metric: React.FC<MetricProps>;
|
|
24
|
+
|
|
25
|
+
export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
26
|
+
export type AvatarTone = 'brand' | 'success' | 'warning' | 'danger' | 'info' | 'neutral';
|
|
27
|
+
export type AvatarDot = boolean | 'busy' | 'away' | 'off';
|
|
28
|
+
export interface AvatarProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
29
|
+
name?: string;
|
|
30
|
+
src?: string;
|
|
31
|
+
size?: AvatarSize;
|
|
32
|
+
tone?: AvatarTone;
|
|
33
|
+
dot?: AvatarDot;
|
|
34
|
+
}
|
|
35
|
+
export declare const Avatar: React.FC<AvatarProps>;
|
|
36
|
+
|
|
37
|
+
export interface AvatarGroupProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
38
|
+
more?: number;
|
|
39
|
+
size?: AvatarSize;
|
|
40
|
+
}
|
|
41
|
+
export declare const AvatarGroup: React.FC<AvatarGroupProps>;
|
|
42
|
+
|
|
43
|
+
export type EyebrowSize = 'xs' | 'sm' | 'md';
|
|
44
|
+
export type EyebrowTone = 'muted' | 'secondary' | 'default' | 'brand' | 'inverse';
|
|
45
|
+
export interface EyebrowProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
46
|
+
size?: EyebrowSize;
|
|
47
|
+
tone?: EyebrowTone;
|
|
48
|
+
icon?: React.ReactNode;
|
|
49
|
+
}
|
|
50
|
+
export declare const Eyebrow: React.FC<EyebrowProps>;
|
|
51
|
+
|
|
52
|
+
export type FlagCountry = 'BR' | 'MX' | 'CO' | 'CL' | 'US' | 'EU';
|
|
53
|
+
export type FlagShape = 'rounded' | 'square';
|
|
54
|
+
export interface FlagProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'src'> {
|
|
55
|
+
country: FlagCountry;
|
|
56
|
+
size?: number;
|
|
57
|
+
shape?: FlagShape;
|
|
58
|
+
assetsBase?: string;
|
|
59
|
+
}
|
|
60
|
+
export declare const Flag: React.FC<FlagProps>;
|
|
61
|
+
|
|
62
|
+
export interface IconProps extends React.SVGAttributes<SVGSVGElement> {
|
|
63
|
+
/** Lucide name — kebab-case ("arrow-left-right") or PascalCase. */
|
|
64
|
+
name: string;
|
|
65
|
+
size?: number;
|
|
66
|
+
stroke?: number;
|
|
67
|
+
}
|
|
68
|
+
export declare const Icon: React.FC<IconProps>;
|
|
69
|
+
|
|
70
|
+
export type DividerWeight = 'default' | 'subtle' | 'strong';
|
|
71
|
+
export interface DividerProps extends React.HTMLAttributes<HTMLElement> {
|
|
72
|
+
weight?: DividerWeight;
|
|
73
|
+
dashed?: boolean;
|
|
74
|
+
/** Vertical hairline for inline rows (1px × 1em). */
|
|
75
|
+
vertical?: boolean;
|
|
76
|
+
/** Midline label — renders the `.divider-labelled` layout. */
|
|
77
|
+
label?: React.ReactNode;
|
|
78
|
+
}
|
|
79
|
+
export declare const Divider: React.FC<DividerProps>;
|
|
80
|
+
|
|
81
|
+
export interface FilterChipProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
82
|
+
/** Selected state (aria-pressed). */
|
|
83
|
+
selected?: boolean;
|
|
84
|
+
icon?: React.ReactNode;
|
|
85
|
+
/** Trailing dropdown indicator (`.chip-chevron`). */
|
|
86
|
+
chevron?: boolean;
|
|
87
|
+
}
|
|
88
|
+
export declare const FilterChip: React.FC<FilterChipProps>;
|
|
89
|
+
|
|
90
|
+
export interface ProgressBarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
91
|
+
/** 0–100. */
|
|
92
|
+
value: number;
|
|
93
|
+
/** Fill color — any token/var. */
|
|
94
|
+
fill?: string;
|
|
95
|
+
/** Track height px — feeds `--progress-h` (default 6). */
|
|
96
|
+
height?: number;
|
|
97
|
+
}
|
|
98
|
+
export declare const ProgressBar: React.FC<ProgressBarProps>;
|
|
99
|
+
|
|
100
|
+
export interface DescTermProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
101
|
+
label: React.ReactNode;
|
|
102
|
+
children?: React.ReactNode;
|
|
103
|
+
}
|
|
104
|
+
export declare const DescTerm: React.FC<DescTermProps>;
|
|
105
|
+
|
|
106
|
+
export type StatDeltaDirection = 'up' | 'down' | 'flat';
|
|
107
|
+
export interface StatProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
108
|
+
/** Mono overline label. */
|
|
109
|
+
label: React.ReactNode;
|
|
110
|
+
/** Big tabular figure. */
|
|
111
|
+
value: React.ReactNode;
|
|
112
|
+
/** Delta text, e.g. "+8.2%". Arrow glyph derives from deltaDirection. */
|
|
113
|
+
delta?: React.ReactNode;
|
|
114
|
+
/** up=success ↑ · down=danger ↓ · flat=muted. Default: 'flat'. */
|
|
115
|
+
deltaDirection?: StatDeltaDirection;
|
|
116
|
+
/** Footnote line (.stat-foot). */
|
|
117
|
+
foot?: React.ReactNode;
|
|
118
|
+
/** Leading icon beside the label. */
|
|
119
|
+
icon?: React.ReactNode;
|
|
120
|
+
/** No tile chrome — for use inside a Card (.stat.is-bare). */
|
|
121
|
+
bare?: boolean;
|
|
122
|
+
/** Dense paddings (.stat-compact). */
|
|
123
|
+
compact?: boolean;
|
|
124
|
+
}
|
|
125
|
+
export declare const Stat: React.FC<StatProps>;
|
|
126
|
+
|
|
127
|
+
export interface StatRowProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
128
|
+
/** Equal columns — feeds `--stat-cols`. Default 3. */
|
|
129
|
+
cols?: number;
|
|
130
|
+
}
|
|
131
|
+
export declare const StatRow: React.FC<StatRowProps>;
|
|
132
|
+
|
|
133
|
+
export declare const Atoms: {
|
|
134
|
+
Money: React.FC<MoneyProps>;
|
|
135
|
+
Metric: React.FC<MetricProps>;
|
|
136
|
+
Avatar: React.FC<AvatarProps>;
|
|
137
|
+
AvatarGroup: React.FC<AvatarGroupProps>;
|
|
138
|
+
Eyebrow: React.FC<EyebrowProps>;
|
|
139
|
+
Flag: React.FC<FlagProps>;
|
|
140
|
+
Icon: React.FC<IconProps>;
|
|
141
|
+
Divider: React.FC<DividerProps>;
|
|
142
|
+
FilterChip: React.FC<FilterChipProps>;
|
|
143
|
+
ProgressBar: React.FC<ProgressBarProps>;
|
|
144
|
+
DescTerm: React.FC<DescTermProps>;
|
|
145
|
+
Stat: React.FC<StatProps>;
|
|
146
|
+
StatRow: React.FC<StatRowProps>;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export default Atoms;
|