oneslash-design-system 1.6.0 → 1.7.1
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.jsx +30 -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/iconButton.d.ts +4 -1
- package/dist/components/iconButton.jsx +39 -36
- 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 +8 -1
- package/dist/components/menuItem.jsx +23 -94
- 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.jsx +20 -92
- 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.jsx +20 -2
- package/dist/components/tag.jsx +43 -26
- package/dist/components/textField.d.ts +3 -1
- package/dist/components/textField.jsx +23 -32
- package/dist/components/textarea.d.ts +3 -1
- package/dist/components/textarea.jsx +38 -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 +12 -12
- package/dist/output.css +382 -151
- 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,100 +1,18 @@
|
|
|
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
|
|
97
|
-
tiny: 'text-caption px-1
|
|
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',
|
|
98
16
|
}[size];
|
|
99
17
|
var sizeIcon = {
|
|
100
18
|
large: 'w-6 h-6',
|
|
@@ -112,19 +30,20 @@ export default function Button(_a) {
|
|
|
112
30
|
danger: {
|
|
113
31
|
primary: 'bg-light-error-main dark:bg-dark-error-main text-light-error-contrast dark:text-dark-error-contrast',
|
|
114
32
|
secondary: 'bg-light-error-alertBg dark:bg-dark-error-alertBg text-light-error-dark dark:text-dark-error-light',
|
|
115
|
-
|
|
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',
|
|
116
35
|
textOnly: 'text-light-error-main dark:text-dark-error-main',
|
|
117
36
|
},
|
|
118
37
|
}[color][type];
|
|
119
38
|
var hoverTypeClasses = {
|
|
120
39
|
default: {
|
|
121
|
-
primary: 'hover:bg-light-accent-dark hover:
|
|
40
|
+
primary: 'hover:bg-light-accent-dark dark:hover:bg-dark-accent-dark',
|
|
122
41
|
secondary: 'hover:bg-light-secondary-dark dark:hover:bg-dark-secondary-dark',
|
|
123
|
-
tertiary: 'hover:bg-light-background-accent200 hover:
|
|
124
|
-
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',
|
|
125
44
|
},
|
|
126
45
|
danger: {
|
|
127
|
-
primary: 'hover:bg-light-error-dark hover:
|
|
46
|
+
primary: 'hover:bg-light-error-dark dark:hover:bg-dark-error-dark',
|
|
128
47
|
secondary: 'hover:bg-light-error-light/60 dark:hover:bg-dark-error-dark/60',
|
|
129
48
|
tertiary: 'hover:bg-light-error-main/10 dark:hover:bg-dark-error-main/10',
|
|
130
49
|
textOnly: 'hover:bg-light-error-main/10 dark:hover:bg-dark-error-main/10',
|
|
@@ -141,20 +60,23 @@ export default function Button(_a) {
|
|
|
141
60
|
: 'cursor-not-allowed text-light-text-disabled dark:text-dark-text-disabled bg-light-actionBackground-disabled dark:bg-dark-actionBackground-disabled',
|
|
142
61
|
selected: 'cursor-pointer bg-light-primary-dark dark:bg-dark-primary-dark text-light-text-contrast dark:text-dark-text-contrast',
|
|
143
62
|
};
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
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}>
|
|
148
66
|
{/* Group IconLeft and label in a flex container for left alignment */}
|
|
149
|
-
<div className="flex items-center">
|
|
150
|
-
{IconLeft && <IconLeft className={sizeIcon} strokeWidth={2}/>}
|
|
151
|
-
<div className={"flex flex-col text-center
|
|
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"}>
|
|
152
70
|
<span className="whitespace-nowrap overflow-hidden truncate">{label}</span>
|
|
153
71
|
{secondLabel && (<span className={"whitespace-nowrap overflow-hidden truncate ".concat(size === 'tiny' ? 'text-caption' : 'text-body2', " opacity-70")}>{secondLabel}</span>)}
|
|
154
72
|
</div>
|
|
155
73
|
</div>
|
|
156
|
-
{IconRight && (<
|
|
157
|
-
|
|
158
|
-
|
|
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>)}
|
|
159
81
|
</button>);
|
|
160
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>
|
|
@@ -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,16 +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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
}, [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';
|
|
14
13
|
// Size-based classes
|
|
15
14
|
var sizeClasses = {
|
|
16
15
|
large: 'p-2', // 8px padding
|
|
@@ -27,45 +26,49 @@ export default function IconButton(_a) {
|
|
|
27
26
|
// Base classes (padding and corner radius)
|
|
28
27
|
var baseClasses = "".concat(sizeClasses[size], " rounded-[8px] leading-none relative");
|
|
29
28
|
// Background color
|
|
30
|
-
var bgColor =
|
|
31
|
-
? 'bg-light-
|
|
32
|
-
:
|
|
33
|
-
? 'bg-light-
|
|
34
|
-
: color === '
|
|
35
|
-
? 'bg-light-
|
|
36
|
-
: color === '
|
|
37
|
-
? 'bg-light-
|
|
38
|
-
:
|
|
39
|
-
|
|
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
|
|
40
41
|
var bgColorHover = state === 'selected'
|
|
41
42
|
? '' // No hover effect when selected
|
|
42
43
|
: color === 'primary'
|
|
43
|
-
? 'hover:bg-light-accent-dark/75 hover:
|
|
44
|
+
? 'enabled:hover:bg-light-accent-dark/75 dark:enabled:hover:bg-dark-accent-dark/75' // Primary
|
|
44
45
|
: color === 'secondary'
|
|
45
|
-
? 'hover:bg-light-secondary-dark/75 hover:
|
|
46
|
+
? 'enabled:hover:bg-light-secondary-dark/75 dark:enabled:hover:bg-dark-secondary-dark/75' // Secondary
|
|
46
47
|
: color === 'tertiary'
|
|
47
|
-
? 'hover:bg-light-background-accent200/75 hover:
|
|
48
|
-
: 'hover:bg-light-background-accent200/75 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
|
|
49
50
|
// Icon color
|
|
50
|
-
var iconColor =
|
|
51
|
-
? 'text-light-text-
|
|
52
|
-
:
|
|
53
|
-
? 'text-light-text-
|
|
54
|
-
: color === '
|
|
55
|
-
? 'text-light-text-primary dark:text-dark-text-
|
|
56
|
-
: color === '
|
|
57
|
-
? 'text-light-text-primary dark:text-dark-text-primary' //
|
|
58
|
-
:
|
|
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
|
|
59
62
|
// State classes, including loading
|
|
60
63
|
var stateClasses = loading
|
|
61
64
|
? 'cursor-wait' // Show a waiting cursor during loading
|
|
62
|
-
:
|
|
63
|
-
? 'cursor-not-allowed
|
|
65
|
+
: isDisabled
|
|
66
|
+
? 'cursor-not-allowed'
|
|
64
67
|
: state === 'selected'
|
|
65
68
|
? 'cursor-pointer'
|
|
66
69
|
: 'cursor-pointer';
|
|
67
|
-
return (<button className={"".concat(baseClasses, " ").concat(bgColor, " ").concat(iconColor, " ").concat(bgColorHover, " ").concat(stateClasses, "
|
|
68
|
-
onClick={onClick} aria-label={loading ? 'Loading' :
|
|
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}>
|
|
69
72
|
{loading ? (<LoadingSmall size={size}/>) : (Icon && <Icon className={iconSizeClasses[size]} strokeWidth={2}/>)}
|
|
70
73
|
</button>);
|
|
71
74
|
}
|
package/dist/components/link.jsx
CHANGED
|
@@ -12,29 +12,31 @@ var __assign = (this && this.__assign) || function () {
|
|
|
12
12
|
};
|
|
13
13
|
import React from 'react';
|
|
14
14
|
import NextLink from 'next/link';
|
|
15
|
+
import { focusRing } from './sharedStyles';
|
|
15
16
|
export default function Link(_a) {
|
|
16
17
|
var href = _a.href, children = _a.children, _b = _a.color, color = _b === void 0 ? 'info' : _b, _c = _a.size, size = _c === void 0 ? 'body1' : _c, _d = _a.openInNewTab, openInNewTab = _d === void 0 ? false : _d, _e = _a.className, className = _e === void 0 ? '' : _e;
|
|
17
18
|
// Color classes based on the color prop
|
|
19
|
+
// Light mode uses the -dark shade so link text keeps 4.5:1 on light backgrounds
|
|
18
20
|
var colorClasses = {
|
|
19
21
|
info: {
|
|
20
|
-
default: 'text-light-info-
|
|
21
|
-
hover: 'hover:
|
|
22
|
-
visited: 'visited:text-light-info-
|
|
22
|
+
default: 'text-light-info-dark dark:text-dark-info-main',
|
|
23
|
+
hover: 'hover:underline-offset-[4px] dark:hover:text-dark-info-light',
|
|
24
|
+
visited: 'visited:text-light-info-dark dark:visited:text-dark-info-light',
|
|
23
25
|
},
|
|
24
26
|
error: {
|
|
25
|
-
default: 'text-light-error-
|
|
26
|
-
hover: 'hover:
|
|
27
|
-
visited: 'visited:text-light-error-
|
|
27
|
+
default: 'text-light-error-dark dark:text-dark-error-main',
|
|
28
|
+
hover: 'hover:underline-offset-[4px] dark:hover:text-dark-error-light',
|
|
29
|
+
visited: 'visited:text-light-error-dark dark:visited:text-dark-error-light',
|
|
28
30
|
},
|
|
29
31
|
warning: {
|
|
30
|
-
default: 'text-light-warning-
|
|
31
|
-
hover: 'hover:
|
|
32
|
-
visited: 'visited:text-light-warning-
|
|
32
|
+
default: 'text-light-warning-dark dark:text-dark-warning-main',
|
|
33
|
+
hover: 'hover:underline-offset-[4px] dark:hover:text-dark-warning-light',
|
|
34
|
+
visited: 'visited:text-light-warning-dark dark:visited:text-dark-warning-light',
|
|
33
35
|
},
|
|
34
36
|
success: {
|
|
35
|
-
default: 'text-light-success-
|
|
36
|
-
hover: 'hover:
|
|
37
|
-
visited: 'visited:text-light-success-
|
|
37
|
+
default: 'text-light-success-dark dark:text-dark-success-main',
|
|
38
|
+
hover: 'hover:underline-offset-[4px] dark:hover:text-dark-success-light',
|
|
39
|
+
visited: 'visited:text-light-success-dark dark:visited:text-dark-success-light',
|
|
38
40
|
},
|
|
39
41
|
};
|
|
40
42
|
var selectedColorClasses = colorClasses[color];
|
|
@@ -42,7 +44,21 @@ export default function Link(_a) {
|
|
|
42
44
|
var isExternal = href.startsWith('http://') || href.startsWith('https://');
|
|
43
45
|
// Determine if we should open in new tab
|
|
44
46
|
var shouldOpenInNewTab = openInNewTab || isExternal;
|
|
45
|
-
|
|
47
|
+
// Literal class names so Tailwind can find them when scanning this file
|
|
48
|
+
var sizeClass = {
|
|
49
|
+
h1: 'text-h1',
|
|
50
|
+
h2: 'text-h2',
|
|
51
|
+
h3: 'text-h3',
|
|
52
|
+
h4: 'text-h4',
|
|
53
|
+
h5: 'text-h5',
|
|
54
|
+
h6: 'text-h6',
|
|
55
|
+
subtitle1: 'text-subtitle1',
|
|
56
|
+
subtitle2: 'text-subtitle2',
|
|
57
|
+
body1: 'text-body1',
|
|
58
|
+
body2: 'text-body2',
|
|
59
|
+
caption: 'text-caption',
|
|
60
|
+
}[size];
|
|
61
|
+
var linkClasses = "\n ".concat(sizeClass, "\n ").concat(selectedColorClasses.default, "\n ").concat(selectedColorClasses.hover, "\n ").concat(selectedColorClasses.visited, "\n underline\n underline-offset-[2px]\n decoration-2\n cursor-pointer\n rounded-[2px]\n transition-[color,text-underline-offset]\n duration-150\n ease-out\n motion-reduce:transition-none\n ").concat(focusRing, "\n ").concat(className, "\n ").trim().replace(/\s+/g, ' ');
|
|
46
62
|
var commonProps = __assign({ className: linkClasses }, (shouldOpenInNewTab && {
|
|
47
63
|
target: '_blank',
|
|
48
64
|
rel: 'noopener noreferrer',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
export default function LoadingScreen() {
|
|
4
|
-
return (<div className="flex justify-center items-center h-full w-full min-h-[200px]">
|
|
5
|
-
<div className="w-12 h-12 rounded-full border-4 border-
|
|
4
|
+
return (<div role="status" aria-label="Loading" className="flex justify-center items-center h-full w-full min-h-[200px]">
|
|
5
|
+
<div className="w-12 h-12 rounded-full border-4 border-current border-t-transparent text-light-text-primary dark:text-dark-text-primary animate-spin"></div>
|
|
6
6
|
</div>);
|
|
7
7
|
}
|
|
8
8
|
;
|
|
@@ -14,7 +14,7 @@ export function LoadingSmall(_a) {
|
|
|
14
14
|
small: 'w-5 h-5 border-2', // 20px, slightly thinner border
|
|
15
15
|
tiny: 'w-4 h-4 border-2', // 16px
|
|
16
16
|
};
|
|
17
|
-
return (<div className="flex justify-center items-center">
|
|
18
|
-
<div className={"rounded-full border-
|
|
17
|
+
return (<div role="status" aria-label="Loading" className="flex justify-center items-center">
|
|
18
|
+
<div className={"rounded-full border-current border-t-transparent text-light-text-primary dark:text-dark-text-primary animate-spin ".concat(spinnerSizeClasses[size])}/>
|
|
19
19
|
</div>);
|
|
20
20
|
}
|