oneslash-design-system 1.5.0 → 1.7.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 +18 -0
- package/dist/components/alert.jsx +31 -16
- package/dist/components/button.d.ts +1 -1
- package/dist/components/button.jsx +32 -108
- package/dist/components/checkBox.d.ts +2 -1
- package/dist/components/checkBox.jsx +24 -24
- package/dist/components/emptyBox.jsx +6 -4
- package/dist/components/icon.d.ts +1 -1
- package/dist/components/icon.jsx +1 -0
- package/dist/components/iconButton.d.ts +4 -1
- package/dist/components/iconButton.jsx +40 -40
- package/dist/components/link.jsx +29 -13
- package/dist/components/loadingScreen.jsx +4 -4
- package/dist/components/menu.jsx +3 -2
- package/dist/components/menuItem.d.ts +9 -2
- package/dist/components/menuItem.jsx +28 -97
- package/dist/components/modal.jsx +51 -12
- package/dist/components/navigation.jsx +1 -1
- package/dist/components/popover.jsx +22 -19
- package/dist/components/radioGroup.jsx +25 -15
- package/dist/components/select.jsx +102 -133
- package/dist/components/sharedStyles.d.ts +9 -0
- package/dist/components/sharedStyles.jsx +18 -0
- package/dist/components/tab.d.ts +2 -1
- package/dist/components/tab.jsx +38 -99
- package/dist/components/tableCell.jsx +1 -1
- package/dist/components/tableContainer.jsx +1 -1
- package/dist/components/tableHeader.jsx +1 -1
- package/dist/components/tableHeaderCell.jsx +1 -1
- package/dist/components/tableRow.jsx +1 -1
- package/dist/components/tabsContainer.d.ts +1 -0
- package/dist/components/tabsContainer.jsx +86 -4
- package/dist/components/tag.d.ts +1 -1
- package/dist/components/tag.jsx +48 -27
- package/dist/components/textField.d.ts +4 -2
- package/dist/components/textField.jsx +24 -32
- package/dist/components/textarea.d.ts +4 -2
- package/dist/components/textarea.jsx +39 -41
- package/dist/components/timeStamp.jsx +6 -4
- package/dist/components/tooltip.d.ts +1 -1
- package/dist/components/tooltip.jsx +81 -44
- package/dist/components/useOverlayTransition.d.ts +9 -0
- package/dist/components/useOverlayTransition.js +31 -0
- package/dist/components/userImage.jsx +2 -2
- package/dist/designTokens.js +16 -16
- package/dist/output.css +439 -136
- package/dist/stories/Button.stories.jsx +1 -1
- package/dist/stories/Icon.stories.jsx +1 -1
- package/dist/stories/MenuItem.stories.jsx +1 -1
- package/dist/stories/TabsContainer.stories.d.ts +2 -0
- package/dist/stories/TabsContainer.stories.jsx +14 -2
- package/dist/stories/Tag.stories.jsx +1 -1
- package/dist/stories/TextField.stories.jsx +1 -1
- package/dist/stories/Textarea.stories.jsx +1 -1
- package/dist/tailwind.config.js +12 -11
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
# oneslash-design-system
|
|
2
|
+
|
|
3
|
+
React components and design tokens for oneSlash apps.
|
|
4
|
+
|
|
5
|
+
## Using the design system in an app
|
|
6
|
+
|
|
7
|
+
The package ships Tailwind class names, not compiled CSS, and its components are untranspiled `.jsx`. Each app compiles both itself:
|
|
8
|
+
|
|
9
|
+
1. **Next.js:** add `transpilePackages: ["oneslash-design-system"]` to `next.config`.
|
|
10
|
+
2. **Tailwind v3:** add `./node_modules/oneslash-design-system/dist/**/*.{js,jsx}` to `content`, and build `theme.extend.colors` and `fontSize` from `require("oneslash-design-system/tokens")`.
|
|
11
|
+
3. **Tailwind v4:** add `@source "../node_modules/oneslash-design-system/dist/**/*.jsx";` to your CSS. For the tokens, point `@config` at a JS config that builds them from `oneslash-design-system/tokens` (see oneslash-homepage's `tailwind.config.ts`).
|
|
12
|
+
|
|
13
|
+
Use class names that mean the same thing in Tailwind v3 and v4 inside components, since apps on both versions compile them.
|
|
14
|
+
|
|
15
|
+
## Releasing
|
|
16
|
+
|
|
17
|
+
Bump `version` in `package.json`, run `npm run build`, and merge to `main`. The Release workflow publishes to npm, creates the tag and GitHub Release with notes from the merged PR titles, and updates the docs site.
|
|
18
|
+
|
|
1
19
|
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
|
2
20
|
|
|
3
21
|
## Getting Started
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import IconButton from './iconButton';
|
|
4
4
|
var bgClassMap = {
|
|
5
5
|
error: 'bg-light-error-alertBg dark:bg-dark-error-alertBg',
|
|
@@ -8,26 +8,27 @@ var bgClassMap = {
|
|
|
8
8
|
success: 'bg-light-success-alertBg dark:bg-dark-success-alertBg',
|
|
9
9
|
default: 'bg-light-secondary-light dark:bg-dark-secondary-light',
|
|
10
10
|
};
|
|
11
|
+
var AUTO_DISMISS_MS = 5000;
|
|
12
|
+
var EXIT_MS = 150;
|
|
13
|
+
// Toasts pop in with a slight overshoot and leave quickly
|
|
14
|
+
var motionClasses = {
|
|
15
|
+
enter: 'duration-[220ms] ease-[cubic-bezier(0.34,1.56,0.64,1)]',
|
|
16
|
+
exit: 'duration-150 ease-in',
|
|
17
|
+
};
|
|
11
18
|
export default function Alert(_a) {
|
|
12
19
|
var _b;
|
|
13
20
|
var _c = _a.open, open = _c === void 0 ? true : _c, type = _a.type, message = _a.message, secondMessage = _a.secondMessage, onClose = _a.onClose, _d = _a.showCloseButton, showCloseButton = _d === void 0 ? false : _d, _e = _a.variant, variant = _e === void 0 ? 'toast' : _e;
|
|
14
21
|
var _f = useState(false), isVisible = _f[0], setIsVisible = _f[1];
|
|
15
22
|
var _g = useState(false), shouldRender = _g[0], setShouldRender = _g[1];
|
|
23
|
+
// Auto-dismiss pauses while the toast is hovered or focused
|
|
24
|
+
var _h = useState(false), isPaused = _h[0], setIsPaused = _h[1];
|
|
25
|
+
var remainingMs = useRef(AUTO_DISMISS_MS);
|
|
16
26
|
useEffect(function () {
|
|
17
27
|
if (open) {
|
|
18
28
|
setShouldRender(true);
|
|
29
|
+
remainingMs.current = AUTO_DISMISS_MS;
|
|
19
30
|
// Small delay to trigger the animation
|
|
20
31
|
var showTimer_1 = setTimeout(function () { return setIsVisible(true); }, 10);
|
|
21
|
-
// Only auto-dismiss if there's no close button
|
|
22
|
-
if (!showCloseButton) {
|
|
23
|
-
var dismissTimer_1 = setTimeout(function () {
|
|
24
|
-
handleClose();
|
|
25
|
-
}, 5000);
|
|
26
|
-
return function () {
|
|
27
|
-
clearTimeout(showTimer_1);
|
|
28
|
-
clearTimeout(dismissTimer_1);
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
32
|
return function () { return clearTimeout(showTimer_1); };
|
|
32
33
|
}
|
|
33
34
|
else if (shouldRender) {
|
|
@@ -35,30 +36,44 @@ export default function Alert(_a) {
|
|
|
35
36
|
setIsVisible(false);
|
|
36
37
|
var hideTimer_1 = setTimeout(function () {
|
|
37
38
|
setShouldRender(false);
|
|
38
|
-
},
|
|
39
|
+
}, EXIT_MS);
|
|
39
40
|
return function () { return clearTimeout(hideTimer_1); };
|
|
40
41
|
}
|
|
41
42
|
}, [open, showCloseButton, shouldRender]);
|
|
43
|
+
// Only auto-dismiss if there's no close button
|
|
44
|
+
useEffect(function () {
|
|
45
|
+
if (!open || !shouldRender || showCloseButton || isPaused)
|
|
46
|
+
return;
|
|
47
|
+
var startedAt = Date.now();
|
|
48
|
+
var dismissTimer = setTimeout(function () { return handleClose(); }, remainingMs.current);
|
|
49
|
+
return function () {
|
|
50
|
+
clearTimeout(dismissTimer);
|
|
51
|
+
remainingMs.current -= Date.now() - startedAt;
|
|
52
|
+
};
|
|
53
|
+
}, [open, shouldRender, showCloseButton, isPaused]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
42
54
|
var handleClose = function () {
|
|
43
55
|
setIsVisible(false);
|
|
44
56
|
// Wait for animation to complete before unmounting
|
|
45
57
|
setTimeout(function () {
|
|
46
58
|
setShouldRender(false);
|
|
47
59
|
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
48
|
-
},
|
|
60
|
+
}, EXIT_MS);
|
|
49
61
|
};
|
|
50
62
|
if (!shouldRender)
|
|
51
63
|
return null;
|
|
52
64
|
var bgClasses = (_b = bgClassMap[type]) !== null && _b !== void 0 ? _b : bgClassMap.default;
|
|
53
|
-
var alertContent = (<div
|
|
65
|
+
var alertContent = (<div
|
|
66
|
+
// Errors interrupt screen readers; everything else is announced politely
|
|
67
|
+
role={type === 'error' ? 'alert' : 'status'} onMouseEnter={function () { return setIsPaused(true); }} onMouseLeave={function () { return setIsPaused(false); }} onFocus={function () { return setIsPaused(true); }} onBlur={function () { return setIsPaused(false); }} className={"flex ".concat(secondMessage ? 'items-start' : 'items-center', " justify-between w-full p-2 rounded-[8px] transition-[opacity,transform] ").concat(isVisible ? motionClasses.enter : motionClasses.exit, " motion-reduce:transition-none ").concat(bgClasses, " ").concat(isVisible ? 'opacity-100 translate-y-0 scale-100' : variant === 'toast' ? 'opacity-0 -translate-y-2 scale-90' : 'opacity-0', " ").concat(variant === 'toast' ? 'max-w-md pointer-events-auto' : '')}>
|
|
54
68
|
<div className="flex items-start gap-2 text-light-text-primary dark:text-dark-text-primary">
|
|
55
|
-
|
|
69
|
+
{/* With two lines, nudge the text so its first line centers on the close button */}
|
|
70
|
+
<div className={"flex flex-col text-left ".concat(secondMessage && showCloseButton ? 'pt-[3px]' : '')}>
|
|
56
71
|
<span className="text-body2">{message}</span>
|
|
57
72
|
{secondMessage && (<span className="text-body2 opacity-70">{secondMessage}</span>)}
|
|
58
73
|
</div>
|
|
59
74
|
</div>
|
|
60
75
|
{showCloseButton && (<div className="ml-4">
|
|
61
|
-
<IconButton color="iconOnly" state="enabled" size="small" iconName="X" onClick={handleClose}/>
|
|
76
|
+
<IconButton color="iconOnly" state="enabled" size="small" iconName="X" ariaLabel="Dismiss" onClick={handleClose}/>
|
|
62
77
|
</div>)}
|
|
63
78
|
</div>);
|
|
64
79
|
if (variant === 'inline') {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
interface ButtonProps {
|
|
3
|
-
size: 'small' | 'medium' | 'large';
|
|
3
|
+
size: 'tiny' | 'small' | 'medium' | 'large';
|
|
4
4
|
type: 'primary' | 'secondary' | 'tertiary' | 'textOnly';
|
|
5
5
|
color?: 'default' | 'danger';
|
|
6
6
|
state: 'enabled' | 'hovered' | 'focused' | 'disabled' | 'selected';
|
|
@@ -1,104 +1,24 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
-
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
-
function step(op) {
|
|
16
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
-
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
-
switch (op[0]) {
|
|
21
|
-
case 0: case 1: t = op; break;
|
|
22
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
-
default:
|
|
26
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
-
if (t[2]) _.ops.pop();
|
|
31
|
-
_.trys.pop(); continue;
|
|
32
|
-
}
|
|
33
|
-
op = body.call(thisArg, _);
|
|
34
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import * as LucideIcons from 'lucide-react';
|
|
4
|
+
import { focusRing, focusRingDanger, pressFeedback, stateTransition } from './sharedStyles';
|
|
39
5
|
export default function Button(_a) {
|
|
40
|
-
var _this = this;
|
|
41
6
|
var size = _a.size, type = _a.type, _b = _a.color, color = _b === void 0 ? 'default' : _b, state = _a.state, label = _a.label, secondLabel = _a.secondLabel, decoIcon = _a.decoIcon, actionIcon = _a.actionIcon, onClickButton = _a.onClickButton, onClickActionIcon = _a.onClickActionIcon, _c = _a.className, className = _c === void 0 ? '' : _c;
|
|
42
|
-
|
|
43
|
-
var
|
|
44
|
-
var
|
|
45
|
-
|
|
46
|
-
var module_1, Icon, error_1;
|
|
47
|
-
return __generator(this, function (_a) {
|
|
48
|
-
switch (_a.label) {
|
|
49
|
-
case 0:
|
|
50
|
-
if (!iconName)
|
|
51
|
-
return [2 /*return*/, null];
|
|
52
|
-
_a.label = 1;
|
|
53
|
-
case 1:
|
|
54
|
-
_a.trys.push([1, 3, , 4]);
|
|
55
|
-
return [4 /*yield*/, import('lucide-react')];
|
|
56
|
-
case 2:
|
|
57
|
-
module_1 = _a.sent();
|
|
58
|
-
Icon = module_1[iconName];
|
|
59
|
-
return [2 /*return*/, Icon || null];
|
|
60
|
-
case 3:
|
|
61
|
-
error_1 = _a.sent();
|
|
62
|
-
console.error("Failed to load icon ".concat(iconName, ":"), error_1);
|
|
63
|
-
return [2 /*return*/, null];
|
|
64
|
-
case 4: return [2 /*return*/];
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}); }, []);
|
|
68
|
-
useEffect(function () {
|
|
69
|
-
var fetchIcons = function () { return __awaiter(_this, void 0, void 0, function () {
|
|
70
|
-
var _a, _b;
|
|
71
|
-
return __generator(this, function (_c) {
|
|
72
|
-
switch (_c.label) {
|
|
73
|
-
case 0:
|
|
74
|
-
if (!decoIcon) return [3 /*break*/, 2];
|
|
75
|
-
_a = setIconLeft;
|
|
76
|
-
return [4 /*yield*/, loadIcon(decoIcon)];
|
|
77
|
-
case 1:
|
|
78
|
-
_a.apply(void 0, [_c.sent()]);
|
|
79
|
-
_c.label = 2;
|
|
80
|
-
case 2:
|
|
81
|
-
if (!actionIcon) return [3 /*break*/, 4];
|
|
82
|
-
_b = setIconRight;
|
|
83
|
-
return [4 /*yield*/, loadIcon(actionIcon)];
|
|
84
|
-
case 3:
|
|
85
|
-
_b.apply(void 0, [_c.sent()]);
|
|
86
|
-
_c.label = 4;
|
|
87
|
-
case 4: return [2 /*return*/];
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
}); };
|
|
91
|
-
fetchIcons();
|
|
92
|
-
}, [decoIcon, actionIcon, loadIcon]);
|
|
7
|
+
// Synchronous lookup so icons render on the first paint
|
|
8
|
+
var IconLeft = decoIcon ? LucideIcons[decoIcon] : null;
|
|
9
|
+
var IconRight = actionIcon ? LucideIcons[actionIcon] : null;
|
|
10
|
+
// Horizontal padding sits on the button so an icon and the label are inset equally
|
|
93
11
|
var sizeClasses = {
|
|
94
|
-
large: 'text-body1
|
|
95
|
-
medium: 'text-body1
|
|
96
|
-
small: 'text-body2
|
|
12
|
+
large: 'text-body1 py-2 px-3 gap-1',
|
|
13
|
+
medium: 'text-body1 py-1 px-2 gap-1',
|
|
14
|
+
small: 'text-body2 py-1 px-2 gap-1',
|
|
15
|
+
tiny: 'text-caption py-px px-1 gap-0.5',
|
|
97
16
|
}[size];
|
|
98
17
|
var sizeIcon = {
|
|
99
18
|
large: 'w-6 h-6',
|
|
100
19
|
medium: 'w-5 h-5',
|
|
101
20
|
small: 'w-4 h-4',
|
|
21
|
+
tiny: 'w-3.5 h-3.5',
|
|
102
22
|
}[size];
|
|
103
23
|
var baseTypeClasses = {
|
|
104
24
|
default: {
|
|
@@ -110,19 +30,20 @@ export default function Button(_a) {
|
|
|
110
30
|
danger: {
|
|
111
31
|
primary: 'bg-light-error-main dark:bg-dark-error-main text-light-error-contrast dark:text-dark-error-contrast',
|
|
112
32
|
secondary: 'bg-light-error-alertBg dark:bg-dark-error-alertBg text-light-error-dark dark:text-dark-error-light',
|
|
113
|
-
|
|
33
|
+
// Inset ring instead of a border so it adds no height
|
|
34
|
+
tertiary: 'bg-transparent ring-1 ring-inset ring-light-error-main dark:ring-dark-error-main text-light-error-main dark:text-dark-error-main',
|
|
114
35
|
textOnly: 'text-light-error-main dark:text-dark-error-main',
|
|
115
36
|
},
|
|
116
37
|
}[color][type];
|
|
117
38
|
var hoverTypeClasses = {
|
|
118
39
|
default: {
|
|
119
|
-
primary: 'hover:bg-light-accent-dark hover:
|
|
40
|
+
primary: 'hover:bg-light-accent-dark dark:hover:bg-dark-accent-dark',
|
|
120
41
|
secondary: 'hover:bg-light-secondary-dark dark:hover:bg-dark-secondary-dark',
|
|
121
|
-
tertiary: 'hover:bg-light-background-accent200 hover:
|
|
122
|
-
textOnly: 'hover:bg-light-background-accent100 hover:
|
|
42
|
+
tertiary: 'hover:bg-light-background-accent200 dark:hover:bg-dark-background-accent200',
|
|
43
|
+
textOnly: 'hover:bg-light-background-accent100 dark:hover:bg-dark-background-accent100',
|
|
123
44
|
},
|
|
124
45
|
danger: {
|
|
125
|
-
primary: 'hover:bg-light-error-dark hover:
|
|
46
|
+
primary: 'hover:bg-light-error-dark dark:hover:bg-dark-error-dark',
|
|
126
47
|
secondary: 'hover:bg-light-error-light/60 dark:hover:bg-dark-error-dark/60',
|
|
127
48
|
tertiary: 'hover:bg-light-error-main/10 dark:hover:bg-dark-error-main/10',
|
|
128
49
|
textOnly: 'hover:bg-light-error-main/10 dark:hover:bg-dark-error-main/10',
|
|
@@ -139,20 +60,23 @@ export default function Button(_a) {
|
|
|
139
60
|
: 'cursor-not-allowed text-light-text-disabled dark:text-dark-text-disabled bg-light-actionBackground-disabled dark:bg-dark-actionBackground-disabled',
|
|
140
61
|
selected: 'cursor-pointer bg-light-primary-dark dark:bg-dark-primary-dark text-light-text-contrast dark:text-dark-text-contrast',
|
|
141
62
|
};
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
setIsHovered(false); }} onClick={onClickButton}>
|
|
63
|
+
// Hover is pure CSS; `state="hovered"` and `state="focused"` only exist to show those states in docs
|
|
64
|
+
var buttonClasses = "\n flex flex-row items-center rounded-[8px] justify-between ".concat(stateTransition, " ").concat(pressFeedback, "\n ").concat(color === 'danger' ? focusRingDanger : focusRing, "\n ").concat(sizeClasses, "\n ").concat(state === 'selected' ? stateClasses.selected : '', "\n ").concat(state === 'enabled' || state === 'hovered' ? "".concat(stateClasses.enabled, " ").concat(baseTypeClasses, " ").concat(hoverTypeClasses) : '', "\n ").concat(state === 'focused' ? "".concat(baseTypeClasses, " ").concat(hoverTypeClasses, " ").concat(stateClasses.focused) : '', "\n ").concat(state === 'disabled' ? stateClasses.disabled : '', "\n ").concat(className, "\n ");
|
|
65
|
+
return (<button type="button" className={buttonClasses} disabled={state === 'disabled'} onClick={onClickButton}>
|
|
146
66
|
{/* Group IconLeft and label in a flex container for left alignment */}
|
|
147
|
-
<div className="flex items-center">
|
|
148
|
-
{IconLeft && <IconLeft className={sizeIcon} strokeWidth={2}/>}
|
|
149
|
-
<div className="flex flex-col text-center px-
|
|
67
|
+
<div className={"flex items-center ".concat(size === 'tiny' ? 'gap-0.5' : 'gap-1')}>
|
|
68
|
+
{IconLeft && <IconLeft className={"".concat(sizeIcon, " flex-shrink-0")} strokeWidth={2}/>}
|
|
69
|
+
<div className={"flex flex-col text-center px-1"}>
|
|
150
70
|
<span className="whitespace-nowrap overflow-hidden truncate">{label}</span>
|
|
151
|
-
{secondLabel && (<span className="whitespace-nowrap overflow-hidden truncate text-body2 opacity-70">{secondLabel}</span>)}
|
|
71
|
+
{secondLabel && (<span className={"whitespace-nowrap overflow-hidden truncate ".concat(size === 'tiny' ? 'text-caption' : 'text-body2', " opacity-70")}>{secondLabel}</span>)}
|
|
152
72
|
</div>
|
|
153
73
|
</div>
|
|
154
|
-
{IconRight && (<
|
|
155
|
-
|
|
156
|
-
|
|
74
|
+
{IconRight && (<span onClick={function (e) {
|
|
75
|
+
// Don't also trigger the button's own click
|
|
76
|
+
e.stopPropagation();
|
|
77
|
+
onClickActionIcon === null || onClickActionIcon === void 0 ? void 0 : onClickActionIcon();
|
|
78
|
+
}} className="cursor-pointer">
|
|
79
|
+
<IconRight className={"".concat(sizeIcon, " flex-shrink-0")} strokeWidth={2}/>
|
|
80
|
+
</span>)}
|
|
157
81
|
</button>);
|
|
158
82
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
interface CheckboxProps {
|
|
2
3
|
label?: string;
|
|
3
4
|
secondLabel?: string;
|
|
@@ -5,5 +6,5 @@ interface CheckboxProps {
|
|
|
5
6
|
onChange?: (checked: boolean) => void;
|
|
6
7
|
disabled?: boolean;
|
|
7
8
|
}
|
|
8
|
-
export default function Checkbox({ label, secondLabel, checked, onChange, disabled }: CheckboxProps):
|
|
9
|
+
export default function Checkbox({ label, secondLabel, checked, onChange, disabled }: CheckboxProps): React.JSX.Element;
|
|
9
10
|
export {};
|
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useState } from 'react';
|
|
2
|
+
import React, { useEffect, useState } from 'react';
|
|
3
|
+
import { popTransition, stateTransition } from './sharedStyles';
|
|
3
4
|
export default function Checkbox(_a) {
|
|
4
5
|
var label = _a.label, secondLabel = _a.secondLabel, _b = _a.checked, checked = _b === void 0 ? false : _b, onChange = _a.onChange, _c = _a.disabled, disabled = _c === void 0 ? false : _c;
|
|
5
6
|
var _d = useState(checked), isChecked = _d[0], setIsChecked = _d[1];
|
|
6
|
-
|
|
7
|
+
// Follow the parent when it changes `checked` (e.g. resetting a form)
|
|
8
|
+
useEffect(function () {
|
|
9
|
+
setIsChecked(checked);
|
|
10
|
+
}, [checked]);
|
|
11
|
+
var handleChange = function (event) {
|
|
7
12
|
if (disabled)
|
|
8
13
|
return;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (onChange) {
|
|
12
|
-
onChange(newChecked);
|
|
13
|
-
}
|
|
14
|
+
setIsChecked(event.target.checked);
|
|
15
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event.target.checked);
|
|
14
16
|
};
|
|
15
|
-
return (<label className={"flex items-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
? ''
|
|
20
|
-
: 'group-hover:bg-light-action-selected dark:group-hover:bg-dark-action-selected')}></div>
|
|
21
|
-
|
|
17
|
+
return (<label className={"group flex items-start ".concat(disabled ? 'cursor-not-allowed' : 'cursor-pointer')}>
|
|
18
|
+
{/* Native input keeps keyboard focus, Space to toggle and screen-reader semantics */}
|
|
19
|
+
<input type="checkbox" className="peer sr-only" checked={isChecked} disabled={disabled} onChange={handleChange}/>
|
|
20
|
+
<span className={"relative flex flex-shrink-0 items-center justify-center w-6 h-6 rounded-full ".concat(stateTransition, "\n peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-0 peer-focus-visible:outline-light-accent-main dark:peer-focus-visible:outline-dark-accent-main\n ").concat(disabled ? '' : 'group-hover:bg-light-action-selected dark:group-hover:bg-dark-action-selected')}>
|
|
22
21
|
{/* Checkbox */}
|
|
23
|
-
<
|
|
22
|
+
<span className={"relative w-4 h-4 border-2 rounded ".concat(stateTransition, " ").concat(disabled
|
|
24
23
|
? isChecked
|
|
25
|
-
? 'bg-light-text-disabled dark:bg-dark-text-disabled border-
|
|
24
|
+
? 'bg-light-text-disabled dark:bg-dark-text-disabled border-transparent'
|
|
26
25
|
: 'border-light-text-disabled dark:border-dark-text-disabled'
|
|
27
26
|
: isChecked
|
|
28
|
-
? 'bg-light-text-primary dark:bg-dark-text-primary border-
|
|
27
|
+
? 'bg-light-text-primary dark:bg-dark-text-primary border-transparent'
|
|
29
28
|
: 'border-light-text-secondary dark:border-dark-text-secondary', " flex items-center justify-center")}>
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
{/* Always rendered so the tick can scale in and out */}
|
|
30
|
+
<svg className={"w-3 h-3 text-light-text-contrast dark:text-dark-text-contrast ".concat(popTransition, " ").concat(isChecked ? 'scale-100' : 'scale-0')} fill="none" stroke="currentColor" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" aria-hidden>
|
|
31
|
+
<path strokeWidth="2" d="M1 6l4 3 6-7"/>
|
|
32
|
+
</svg>
|
|
33
|
+
</span>
|
|
34
|
+
</span>
|
|
35
|
+
{(label || secondLabel) && (<span className="flex flex-col ml-2">
|
|
36
36
|
{label && (<span className={"text-body1 ".concat(disabled
|
|
37
37
|
? 'text-light-text-disabled dark:text-dark-text-disabled'
|
|
38
38
|
: 'text-light-text-primary dark:text-dark-text-primary')}>
|
|
@@ -43,6 +43,6 @@ export default function Checkbox(_a) {
|
|
|
43
43
|
: 'text-light-text-primary dark:text-dark-text-primary')}>
|
|
44
44
|
{secondLabel}
|
|
45
45
|
</span>)}
|
|
46
|
-
</
|
|
46
|
+
</span>)}
|
|
47
47
|
</label>);
|
|
48
48
|
}
|
|
@@ -3,16 +3,18 @@ import React from 'react';
|
|
|
3
3
|
import { AlertCircle } from 'lucide-react';
|
|
4
4
|
export default function EmptyBox(_a) {
|
|
5
5
|
var text = _a.text, secondText = _a.secondText, size = _a.size;
|
|
6
|
-
|
|
6
|
+
// The icon can be faint; the text needs readable contrast
|
|
7
|
+
var iconColor = 'text-light-text-disabled dark:text-dark-text-disabled';
|
|
8
|
+
var textColor = 'text-light-text-secondary dark:text-dark-text-secondary';
|
|
7
9
|
var height = size === 'small' ? 'py-6' : 'h-full';
|
|
8
10
|
var iconSize = 'size-6';
|
|
9
11
|
return (<div className={"flex flex-col space-y-2 justify-center items-center w-full ".concat(height)}>
|
|
10
|
-
<AlertCircle className={"".concat(iconSize, " ").concat(
|
|
12
|
+
<AlertCircle className={"".concat(iconSize, " ").concat(iconColor)} strokeWidth={2} aria-hidden/>
|
|
11
13
|
<div className="flex flex-col text-center">
|
|
12
|
-
<p className={"text-body1 ".concat(
|
|
14
|
+
<p className={"text-body1 ".concat(textColor)}>
|
|
13
15
|
{text}
|
|
14
16
|
</p>
|
|
15
|
-
{secondText && (<p className={"text-body2
|
|
17
|
+
{secondText && (<p className={"text-body2 ".concat(textColor)}>
|
|
16
18
|
{secondText}
|
|
17
19
|
</p>)}
|
|
18
20
|
</div>
|
|
@@ -3,7 +3,7 @@ import { icons } from 'lucide-react';
|
|
|
3
3
|
type IconName = keyof typeof icons;
|
|
4
4
|
interface IconProps {
|
|
5
5
|
name: IconName;
|
|
6
|
-
size?: 'small' | 'medium' | 'large' | number;
|
|
6
|
+
size?: 'tiny' | 'small' | 'medium' | 'large' | number;
|
|
7
7
|
color?: 'primary' | 'secondary' | 'disabled' | 'error' | 'warning' | 'success' | 'info' | 'accent' | 'inherit';
|
|
8
8
|
strokeWidth?: number;
|
|
9
9
|
className?: string;
|
package/dist/components/icon.jsx
CHANGED
|
@@ -7,6 +7,9 @@ interface IconButtonProps {
|
|
|
7
7
|
iconName: keyof typeof LucideIcons;
|
|
8
8
|
onClick?: any;
|
|
9
9
|
loading?: boolean;
|
|
10
|
+
/** Screen-reader label. Defaults to the icon name, e.g. "Settings". */
|
|
11
|
+
ariaLabel?: string;
|
|
10
12
|
}
|
|
11
|
-
export default function IconButton({ color, state, size, iconName, onClick, loading,
|
|
13
|
+
export default function IconButton({ color, state, size, iconName, onClick, loading, // Default to false
|
|
14
|
+
ariaLabel, }: IconButtonProps): React.JSX.Element;
|
|
12
15
|
export {};
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import React
|
|
2
|
+
import React from 'react';
|
|
3
3
|
import * as LucideIcons from 'lucide-react';
|
|
4
4
|
import { LoadingSmall } from './loadingScreen';
|
|
5
|
+
import { focusRing, pressFeedback, stateTransition } from './sharedStyles';
|
|
5
6
|
export default function IconButton(_a) {
|
|
6
|
-
var color = _a.color, state = _a.state, size = _a.size, iconName = _a.iconName, onClick = _a.onClick, _b = _a.loading, loading = _b === void 0 ? false : _b
|
|
7
|
-
|
|
8
|
-
var
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
setIcon(LucideIcons[iconName]);
|
|
13
|
-
}
|
|
14
|
-
}, [iconName]);
|
|
7
|
+
var color = _a.color, state = _a.state, size = _a.size, iconName = _a.iconName, onClick = _a.onClick, _b = _a.loading, loading = _b === void 0 ? false : _b, // Default to false
|
|
8
|
+
ariaLabel = _a.ariaLabel;
|
|
9
|
+
var Icon = LucideIcons[iconName];
|
|
10
|
+
// "ChevronDown" -> "Chevron Down"
|
|
11
|
+
var label = ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : String(iconName).replace(/([a-z])([A-Z0-9])/g, '$1 $2');
|
|
12
|
+
var isDisabled = state === 'disabled';
|
|
15
13
|
// Size-based classes
|
|
16
14
|
var sizeClasses = {
|
|
17
15
|
large: 'p-2', // 8px padding
|
|
@@ -28,47 +26,49 @@ export default function IconButton(_a) {
|
|
|
28
26
|
// Base classes (padding and corner radius)
|
|
29
27
|
var baseClasses = "".concat(sizeClasses[size], " rounded-[8px] leading-none relative");
|
|
30
28
|
// Background color
|
|
31
|
-
var bgColor =
|
|
32
|
-
? 'bg-light-
|
|
33
|
-
:
|
|
34
|
-
? 'bg-light-
|
|
35
|
-
: color === '
|
|
36
|
-
? 'bg-light-
|
|
37
|
-
: color === '
|
|
38
|
-
? 'bg-light-
|
|
39
|
-
:
|
|
40
|
-
|
|
29
|
+
var bgColor = isDisabled
|
|
30
|
+
? (color === 'iconOnly' ? '' : 'bg-light-actionBackground-disabled dark:bg-dark-actionBackground-disabled')
|
|
31
|
+
: state === 'selected'
|
|
32
|
+
? 'bg-light-primary-dark dark:bg-dark-primary-dark' // Selected state
|
|
33
|
+
: color === 'primary'
|
|
34
|
+
? 'bg-light-accent-main dark:bg-dark-accent-main' // Primary
|
|
35
|
+
: color === 'secondary'
|
|
36
|
+
? 'bg-light-secondary-main dark:bg-dark-secondary-main' // Secondary
|
|
37
|
+
: color === 'tertiary'
|
|
38
|
+
? 'bg-light-background-accent100 dark:bg-dark-background-accent100' // Tertiary
|
|
39
|
+
: ' '; // iconOnly: none
|
|
40
|
+
// Background hover color; `enabled:` skips it while disabled or loading
|
|
41
41
|
var bgColorHover = state === 'selected'
|
|
42
42
|
? '' // No hover effect when selected
|
|
43
43
|
: color === 'primary'
|
|
44
|
-
? 'hover:bg-light-accent-dark hover:
|
|
44
|
+
? 'enabled:hover:bg-light-accent-dark/75 dark:enabled:hover:bg-dark-accent-dark/75' // Primary
|
|
45
45
|
: color === 'secondary'
|
|
46
|
-
? 'hover:bg-light-secondary-dark hover:
|
|
46
|
+
? 'enabled:hover:bg-light-secondary-dark/75 dark:enabled:hover:bg-dark-secondary-dark/75' // Secondary
|
|
47
47
|
: color === 'tertiary'
|
|
48
|
-
? 'hover:bg-light-background-accent200 hover:
|
|
49
|
-
: 'hover:bg-light-background-accent200 hover:
|
|
48
|
+
? 'enabled:hover:bg-light-background-accent200/75 dark:enabled:hover:bg-dark-background-accent200/75' // Tertiary
|
|
49
|
+
: 'enabled:hover:bg-light-background-accent200/75 dark:enabled:hover:bg-dark-background-accent200/75'; // iconOnly
|
|
50
50
|
// Icon color
|
|
51
|
-
var iconColor =
|
|
52
|
-
? 'text-light-text-
|
|
53
|
-
:
|
|
54
|
-
? 'text-light-text-
|
|
55
|
-
: color === '
|
|
56
|
-
? 'text-light-text-primary dark:text-dark-text-
|
|
57
|
-
: color === '
|
|
58
|
-
? 'text-light-text-primary dark:text-dark-text-primary' //
|
|
59
|
-
:
|
|
51
|
+
var iconColor = isDisabled
|
|
52
|
+
? 'text-light-text-disabled dark:text-dark-text-disabled'
|
|
53
|
+
: state === 'selected'
|
|
54
|
+
? 'text-light-text-contrast dark:text-dark-text-contrast' // Selected state
|
|
55
|
+
: color === 'primary'
|
|
56
|
+
? 'text-light-text-primary dark:text-dark-text-contrast' // Primary
|
|
57
|
+
: color === 'secondary'
|
|
58
|
+
? 'text-light-text-primary dark:text-dark-text-primary' // Secondary
|
|
59
|
+
: color === 'tertiary'
|
|
60
|
+
? 'text-light-text-primary dark:text-dark-text-primary' // Tertiary
|
|
61
|
+
: 'text-light-text-primary dark:text-dark-text-primary'; // iconOnly
|
|
60
62
|
// State classes, including loading
|
|
61
63
|
var stateClasses = loading
|
|
62
64
|
? 'cursor-wait' // Show a waiting cursor during loading
|
|
63
|
-
:
|
|
64
|
-
? 'cursor-not-allowed
|
|
65
|
+
: isDisabled
|
|
66
|
+
? 'cursor-not-allowed'
|
|
65
67
|
: state === 'selected'
|
|
66
68
|
? 'cursor-pointer'
|
|
67
|
-
:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return (<button className={"".concat(baseClasses, " ").concat(bgColor, " ").concat(iconColor, " ").concat(bgColorHover, " ").concat(stateClasses, " transition-colors duration-200 ease-in-out flex items-center justify-center")} disabled={state === 'disabled' || loading} // Disable button during loading
|
|
71
|
-
onMouseEnter={function () { return setIsHovered(true); }} onMouseLeave={function () { return setIsHovered(false); }} onClick={onClick} aria-label={loading ? 'Loading' : 'Reload'}>
|
|
69
|
+
: 'cursor-pointer';
|
|
70
|
+
return (<button type="button" className={"".concat(baseClasses, " ").concat(bgColor, " ").concat(iconColor, " ").concat(bgColorHover, " ").concat(stateClasses, " ").concat(stateTransition, " ").concat(pressFeedback, " ").concat(focusRing, " flex items-center justify-center")} disabled={isDisabled || loading} // Disable button during loading
|
|
71
|
+
onClick={onClick} aria-label={loading ? 'Loading' : label}>
|
|
72
72
|
{loading ? (<LoadingSmall size={size}/>) : (Icon && <Icon className={iconSizeClasses[size]} strokeWidth={2}/>)}
|
|
73
73
|
</button>);
|
|
74
74
|
}
|