@adecore/ui 0.20.1 → 0.21.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/dist/Pill.d.ts +2 -1
- package/dist/Pill.js +9 -4
- package/dist/Slider.d.ts +15 -0
- package/dist/Slider.js +16 -0
- package/dist/Stepper.js +1 -1
- package/dist/Toasts.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/step-places.d.ts +1 -0
- package/dist/step-places.js +2 -0
- package/package.json +3 -3
- package/src/Pill.tsx +11 -4
- package/src/Slider.tsx +68 -0
- package/src/Stepper.tsx +1 -2
- package/src/Toasts.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/step-places.ts +2 -0
package/dist/Pill.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface PillProps {
|
|
|
16
16
|
children: ReactNode;
|
|
17
17
|
tone?: keyof typeof TONES;
|
|
18
18
|
shape?: keyof typeof SHAPES;
|
|
19
|
+
size?: 'md' | 'sm';
|
|
19
20
|
mono?: boolean;
|
|
20
21
|
onClick?: () => void;
|
|
21
22
|
pressed?: boolean;
|
|
@@ -23,5 +24,5 @@ export interface PillProps {
|
|
|
23
24
|
className?: string;
|
|
24
25
|
ref?: Ref<HTMLElement>;
|
|
25
26
|
}
|
|
26
|
-
export declare function Pill({ icon, children, tone, shape, mono, onClick, pressed, disabled, className, ref }: PillProps): import("react").JSX.Element;
|
|
27
|
+
export declare function Pill({ icon, children, tone, shape, size, mono, onClick, pressed, disabled, className, ref }: PillProps): import("react").JSX.Element;
|
|
27
28
|
export {};
|
package/dist/Pill.js
CHANGED
|
@@ -16,13 +16,18 @@ const TONES = {
|
|
|
16
16
|
};
|
|
17
17
|
/* The corners of a button, so a pill reads as a small label of the same family and not as a badge. A tag, at the end of a line of prose, is tighter and heavier. */
|
|
18
18
|
const SHAPES = {
|
|
19
|
-
pill: 'rounded-md
|
|
20
|
-
tag: 'rounded-md
|
|
19
|
+
pill: 'rounded-md',
|
|
20
|
+
tag: 'rounded-md font-medium'
|
|
21
|
+
};
|
|
22
|
+
/* The text stays at the theme's smallest size, so `sm` gives up padding: 20px tall instead of 24. */
|
|
23
|
+
const PADDING = {
|
|
24
|
+
pill: { md: 'px-2 py-0.5', sm: 'px-1.5' },
|
|
25
|
+
tag: { md: 'px-1.5 py-0.5', sm: 'px-1' }
|
|
21
26
|
};
|
|
22
27
|
/* The small rounded label in a header, a sidebar row or a settings line: a count, a branch, a status. */
|
|
23
|
-
export function Pill({ icon, children, tone = 'muted', shape = 'pill', mono = false, onClick, pressed, disabled, className, ref }) {
|
|
28
|
+
export function Pill({ icon, children, tone = 'muted', shape = 'pill', size = 'md', mono = false, onClick, pressed, disabled, className, ref }) {
|
|
24
29
|
// Never broken inside itself: in a row that wraps, a pill that does not fit moves to the next line whole.
|
|
25
|
-
const shared = clsx('inline-flex shrink-0 items-center gap-1 whitespace-nowrap text-2xs', SHAPES[shape], TONES[tone], mono && 'font-mono', className);
|
|
30
|
+
const shared = clsx('inline-flex shrink-0 items-center gap-1 whitespace-nowrap text-2xs', SHAPES[shape], PADDING[shape][size], TONES[tone], mono && 'font-mono', className);
|
|
26
31
|
if (!onClick) {
|
|
27
32
|
return (_jsxs("span", { ref: ref, className: shared, children: [icon, children] }));
|
|
28
33
|
}
|
package/dist/Slider.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Ref } from 'react';
|
|
2
|
+
export interface SliderProps {
|
|
3
|
+
value: number;
|
|
4
|
+
onValueChange(value: number): void;
|
|
5
|
+
min: number;
|
|
6
|
+
max: number;
|
|
7
|
+
step?: number;
|
|
8
|
+
decimals?: number;
|
|
9
|
+
unit?: string;
|
|
10
|
+
label: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
className?: string;
|
|
13
|
+
ref?: Ref<HTMLDivElement>;
|
|
14
|
+
}
|
|
15
|
+
export declare function Slider({ value, onValueChange, min, max, step, decimals, unit, label, disabled, className, ref }: SliderProps): import("react").JSX.Element;
|
package/dist/Slider.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId } from 'react';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { Slider as BaseSlider } from '@base-ui-components/react/slider';
|
|
5
|
+
import { formatFixed } from './format/number.js';
|
|
6
|
+
import { placesOf } from './step-places.js';
|
|
7
|
+
/* A number picked along a range: the label and the value on one line, the track under them. */
|
|
8
|
+
export function Slider({ value, onValueChange, min, max, step = 1, decimals = placesOf(step), unit, label, disabled, className, ref }) {
|
|
9
|
+
const labelId = useId();
|
|
10
|
+
const reading = formatFixed(value, decimals);
|
|
11
|
+
// Base UI adds steps in floating point, so 0.2 plus 0.1 would arrive as 0.30000000000000004.
|
|
12
|
+
const places = Math.max(placesOf(step), placesOf(min));
|
|
13
|
+
// Base UI's Page Up jumps 10 whatever the range; a tenth of the range suits 0 to 1 as well as 0 to 1000.
|
|
14
|
+
const largeStep = Math.max(step, Math.round((max - min) / 10 / step) * step);
|
|
15
|
+
return (_jsxs(BaseSlider.Root, { ref: ref, value: value, onValueChange: (next) => onValueChange(Number(next.toFixed(places))), min: min, max: max, step: step, largeStep: largeStep, disabled: disabled, "aria-labelledby": labelId, className: clsx('flex flex-col gap-1 data-disabled:opacity-50', className), children: [_jsxs("div", { className: "flex items-baseline justify-between gap-3 text-xs", children: [_jsx("span", { id: labelId, className: "min-w-0 truncate text-text", children: label }), _jsxs("span", { "aria-hidden": true, className: "shrink-0 text-text-muted tabular-nums", children: [reading, unit] })] }), _jsx(BaseSlider.Control, { className: "flex h-5 touch-none items-center px-2 select-none", children: _jsxs(BaseSlider.Track, { className: "relative h-1 w-full rounded-full bg-border-strong", children: [_jsx(BaseSlider.Indicator, { className: "rounded-full bg-accent" }), _jsx(BaseSlider.Thumb, { getAriaValueText: () => `${reading}${unit ?? ''}`, className: "block size-4 rounded-full bg-surface-raised shadow-raised outline-offset-2 outline-accent has-focus-visible:outline-2" })] }) })] }));
|
|
16
|
+
}
|
package/dist/Stepper.js
CHANGED
|
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
5
5
|
import { ButtonGroup } from './ButtonGroup.js';
|
|
6
6
|
import { formatFixed } from './format/number.js';
|
|
7
7
|
import { IconButton } from './IconButton.js';
|
|
8
|
-
|
|
8
|
+
import { placesOf } from './step-places.js';
|
|
9
9
|
/* A number between minus and plus, in one sunken group so the three read as a single control. */
|
|
10
10
|
export function Stepper({ value, onValueChange, min, max, step, decimals = placesOf(step), unit, label, className, ref }) {
|
|
11
11
|
const { t } = useTranslation('ui');
|
package/dist/Toasts.js
CHANGED
|
@@ -42,7 +42,7 @@ function ToastCard({ toast, footer, onDismiss }) {
|
|
|
42
42
|
const actions = [...(toast.action === undefined ? [] : [toast.action]), ...(toast.actions ?? [])];
|
|
43
43
|
// An offer that runs out shows how long it has left instead.
|
|
44
44
|
const countdown = actions.length > 0 ? toast.deadline : undefined;
|
|
45
|
-
return (_jsxs(Surface, { className: "group flex items-start gap-3 rounded-[10px] py-2.5 pr-2 pl-3", children: [_jsx("span", { className: clsx(TITLE_LINE, 'w-4 justify-center'), children: toast.kind === 'progress' ? (_jsx(Spinner, { size: 16, className: TONE.progress })) : (_jsx(Icon, { icon: ICON[toast.kind], size: 16, className: TONE[toast.kind] })) }), _jsxs("div", { className: "flex min-w-0 grow flex-col gap-1", children: [_jsx("span", { className: "text-sm text-text", children: toast.title }), toast.description !== undefined && toast.description !== '' && (_jsx("span", { className: "text-xs break-words text-pretty text-text-muted", children: toast.description })), actions.length > 1 && (_jsx("span", { className: "mt-1 flex flex-wrap items-center gap-x-4 gap-y-1", children: actions.map((action) => (_jsx(ActionButton, { action: action }, action.label))) })), footer?.(toast)] }), actions.length === 1 && (_jsx("span", { className: TITLE_LINE, children: _jsx(ActionButton, { action: actions[0] }) })), countdown !== undefined ? (_jsx(TimerRing, { deadline: countdown, onDismiss: onDismiss }, countdown.start)) : (_jsx(IconButton, { icon: X, size: "xs", label: t('action.dismiss'), className: clsx('-my-px', leaves && 'opacity-0 group-focus-within:opacity-100 group-hover:opacity-100'), onClick: onDismiss }))] }));
|
|
45
|
+
return (_jsxs(Surface, { className: "group flex items-start gap-3 rounded-[10px] py-2.5 pr-2 pl-3", children: [_jsx("span", { className: clsx(TITLE_LINE, 'w-4 justify-center'), children: toast.kind === 'progress' ? (_jsx(Spinner, { size: 16, className: TONE.progress })) : (_jsx(Icon, { icon: ICON[toast.kind], size: 16, className: TONE[toast.kind] })) }), _jsxs("div", { className: "flex min-w-0 grow flex-col gap-1", children: [_jsx("span", { className: "text-sm break-words text-text", children: toast.title }), toast.description !== undefined && toast.description !== '' && (_jsx("span", { className: "text-xs break-words text-pretty text-text-muted", children: toast.description })), actions.length > 1 && (_jsx("span", { className: "mt-1 flex flex-wrap items-center gap-x-4 gap-y-1", children: actions.map((action) => (_jsx(ActionButton, { action: action }, action.label))) })), footer?.(toast)] }), actions.length === 1 && (_jsx("span", { className: TITLE_LINE, children: _jsx(ActionButton, { action: actions[0] }) })), countdown !== undefined ? (_jsx(TimerRing, { deadline: countdown, onDismiss: onDismiss }, countdown.start)) : (_jsx(IconButton, { icon: X, size: "xs", label: t('action.dismiss'), className: clsx('-my-px', leaves && 'opacity-0 group-focus-within:opacity-100 group-hover:opacity-100'), onClick: onDismiss }))] }));
|
|
46
46
|
}
|
|
47
47
|
/*
|
|
48
48
|
* Bottom right, over everything: what an action is doing while it runs and how it went when it is
|
package/dist/index.d.ts
CHANGED
|
@@ -46,6 +46,7 @@ export { Select, type SelectGroup, type SelectItem, type SelectProps } from './S
|
|
|
46
46
|
export { Separator, type SeparatorProps } from './Separator.tsx';
|
|
47
47
|
export { ShortcutHints } from './ShortcutHints.tsx';
|
|
48
48
|
export { Skeleton, type SkeletonProps } from './Skeleton.tsx';
|
|
49
|
+
export { Slider, type SliderProps } from './Slider.tsx';
|
|
49
50
|
export { SlidingColumn, type SlidingColumnProps } from './SlidingColumn.tsx';
|
|
50
51
|
export { Spinner, type SpinnerProps } from './Spinner.tsx';
|
|
51
52
|
export { Stepper, type StepperProps } from './Stepper.tsx';
|
package/dist/index.js
CHANGED
|
@@ -45,6 +45,7 @@ export { Select } from './Select.js';
|
|
|
45
45
|
export { Separator } from './Separator.js';
|
|
46
46
|
export { ShortcutHints } from './ShortcutHints.js';
|
|
47
47
|
export { Skeleton } from './Skeleton.js';
|
|
48
|
+
export { Slider } from './Slider.js';
|
|
48
49
|
export { SlidingColumn } from './SlidingColumn.js';
|
|
49
50
|
export { Spinner } from './Spinner.js';
|
|
50
51
|
export { Stepper } from './Stepper.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const placesOf: (step: number) => number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "React components, a theme, formatters and a settings dialog for desktop-grade apps on React 19, Base UI and Tailwind 4.",
|
|
5
5
|
"license": "FSL-1.1-MIT",
|
|
6
6
|
"author": "Bas Milius",
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
"react-i18next": "^17.0.15"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
|
+
"@happy-dom/global-registrator": "^20.14.5",
|
|
81
82
|
"@types/bun": "^1.4.2",
|
|
82
83
|
"@types/opentype.js": "^1.3.10",
|
|
83
84
|
"@types/react": "^19.3.0",
|
|
@@ -88,7 +89,6 @@
|
|
|
88
89
|
"react": "^19.3.0",
|
|
89
90
|
"react-dom": "^19.3.0",
|
|
90
91
|
"react-i18next": "^17.0.15",
|
|
91
|
-
"typescript": "^7.0.2"
|
|
92
|
-
"@happy-dom/global-registrator": "^20.14.5"
|
|
92
|
+
"typescript": "^7.0.2"
|
|
93
93
|
}
|
|
94
94
|
}
|
package/src/Pill.tsx
CHANGED
|
@@ -18,8 +18,14 @@ const TONES = {
|
|
|
18
18
|
|
|
19
19
|
/* The corners of a button, so a pill reads as a small label of the same family and not as a badge. A tag, at the end of a line of prose, is tighter and heavier. */
|
|
20
20
|
const SHAPES = {
|
|
21
|
-
pill: 'rounded-md
|
|
22
|
-
tag: 'rounded-md
|
|
21
|
+
pill: 'rounded-md',
|
|
22
|
+
tag: 'rounded-md font-medium'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/* The text stays at the theme's smallest size, so `sm` gives up padding: 20px tall instead of 24. */
|
|
26
|
+
const PADDING = {
|
|
27
|
+
pill: { md: 'px-2 py-0.5', sm: 'px-1.5' },
|
|
28
|
+
tag: { md: 'px-1.5 py-0.5', sm: 'px-1' }
|
|
23
29
|
};
|
|
24
30
|
|
|
25
31
|
export interface PillProps {
|
|
@@ -27,6 +33,7 @@ export interface PillProps {
|
|
|
27
33
|
children: ReactNode;
|
|
28
34
|
tone?: keyof typeof TONES;
|
|
29
35
|
shape?: keyof typeof SHAPES;
|
|
36
|
+
size?: 'md' | 'sm';
|
|
30
37
|
/* A branch name or a count reads better in the monospace face. */
|
|
31
38
|
mono?: boolean;
|
|
32
39
|
/* With a handler the pill is a button; without one it is a label. */
|
|
@@ -39,9 +46,9 @@ export interface PillProps {
|
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
/* The small rounded label in a header, a sidebar row or a settings line: a count, a branch, a status. */
|
|
42
|
-
export function Pill({ icon, children, tone = 'muted', shape = 'pill', mono = false, onClick, pressed, disabled, className, ref }: PillProps) {
|
|
49
|
+
export function Pill({ icon, children, tone = 'muted', shape = 'pill', size = 'md', mono = false, onClick, pressed, disabled, className, ref }: PillProps) {
|
|
43
50
|
// Never broken inside itself: in a row that wraps, a pill that does not fit moves to the next line whole.
|
|
44
|
-
const shared = clsx('inline-flex shrink-0 items-center gap-1 whitespace-nowrap text-2xs', SHAPES[shape], TONES[tone], mono && 'font-mono', className);
|
|
51
|
+
const shared = clsx('inline-flex shrink-0 items-center gap-1 whitespace-nowrap text-2xs', SHAPES[shape], PADDING[shape][size], TONES[tone], mono && 'font-mono', className);
|
|
45
52
|
if (!onClick) {
|
|
46
53
|
return (
|
|
47
54
|
<span ref={ref} className={shared}>
|
package/src/Slider.tsx
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useId, type Ref } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { Slider as BaseSlider } from '@base-ui-components/react/slider';
|
|
4
|
+
import { formatFixed } from './format/number.ts';
|
|
5
|
+
import { placesOf } from './step-places.ts';
|
|
6
|
+
|
|
7
|
+
export interface SliderProps {
|
|
8
|
+
value: number;
|
|
9
|
+
onValueChange(value: number): void;
|
|
10
|
+
min: number;
|
|
11
|
+
max: number;
|
|
12
|
+
step?: number;
|
|
13
|
+
/* Places after the separator, always shown. Defaults to the places `step` is written with. */
|
|
14
|
+
decimals?: number;
|
|
15
|
+
/* Printed right after the number, such as `px` or `%`. */
|
|
16
|
+
unit?: string;
|
|
17
|
+
/* Shown above the track and read as the slider's name. */
|
|
18
|
+
label: string;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
className?: string;
|
|
21
|
+
ref?: Ref<HTMLDivElement>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* A number picked along a range: the label and the value on one line, the track under them. */
|
|
25
|
+
export function Slider({ value, onValueChange, min, max, step = 1, decimals = placesOf(step), unit, label, disabled, className, ref }: SliderProps) {
|
|
26
|
+
const labelId = useId();
|
|
27
|
+
const reading = formatFixed(value, decimals);
|
|
28
|
+
// Base UI adds steps in floating point, so 0.2 plus 0.1 would arrive as 0.30000000000000004.
|
|
29
|
+
const places = Math.max(placesOf(step), placesOf(min));
|
|
30
|
+
// Base UI's Page Up jumps 10 whatever the range; a tenth of the range suits 0 to 1 as well as 0 to 1000.
|
|
31
|
+
const largeStep = Math.max(step, Math.round((max - min) / 10 / step) * step);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<BaseSlider.Root
|
|
35
|
+
ref={ref}
|
|
36
|
+
value={value}
|
|
37
|
+
onValueChange={(next) => onValueChange(Number(next.toFixed(places)))}
|
|
38
|
+
min={min}
|
|
39
|
+
max={max}
|
|
40
|
+
step={step}
|
|
41
|
+
largeStep={largeStep}
|
|
42
|
+
disabled={disabled}
|
|
43
|
+
aria-labelledby={labelId}
|
|
44
|
+
className={clsx('flex flex-col gap-1 data-disabled:opacity-50', className)}
|
|
45
|
+
>
|
|
46
|
+
<div className="flex items-baseline justify-between gap-3 text-xs">
|
|
47
|
+
<span id={labelId} className="min-w-0 truncate text-text">
|
|
48
|
+
{label}
|
|
49
|
+
</span>
|
|
50
|
+
{/* The thumb already says the value through `aria-valuetext`. */}
|
|
51
|
+
<span aria-hidden className="shrink-0 text-text-muted tabular-nums">
|
|
52
|
+
{reading}
|
|
53
|
+
{unit}
|
|
54
|
+
</span>
|
|
55
|
+
</div>
|
|
56
|
+
{/* The padding gives the thumb room at either end; Base UI leaves it out of the pointer's position. */}
|
|
57
|
+
<BaseSlider.Control className="flex h-5 touch-none items-center px-2 select-none">
|
|
58
|
+
<BaseSlider.Track className="relative h-1 w-full rounded-full bg-border-strong">
|
|
59
|
+
<BaseSlider.Indicator className="rounded-full bg-accent" />
|
|
60
|
+
<BaseSlider.Thumb
|
|
61
|
+
getAriaValueText={() => `${reading}${unit ?? ''}`}
|
|
62
|
+
className="block size-4 rounded-full bg-surface-raised shadow-raised outline-offset-2 outline-accent has-focus-visible:outline-2"
|
|
63
|
+
/>
|
|
64
|
+
</BaseSlider.Track>
|
|
65
|
+
</BaseSlider.Control>
|
|
66
|
+
</BaseSlider.Root>
|
|
67
|
+
);
|
|
68
|
+
}
|
package/src/Stepper.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
5
5
|
import { ButtonGroup } from './ButtonGroup.tsx';
|
|
6
6
|
import { formatFixed } from './format/number.ts';
|
|
7
7
|
import { IconButton } from './IconButton.tsx';
|
|
8
|
+
import { placesOf } from './step-places.ts';
|
|
8
9
|
|
|
9
10
|
export interface StepperProps {
|
|
10
11
|
value: number;
|
|
@@ -21,8 +22,6 @@ export interface StepperProps {
|
|
|
21
22
|
ref?: Ref<HTMLDivElement>;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
const placesOf = (step: number): number => String(step).split('.')[1]?.length ?? 0;
|
|
25
|
-
|
|
26
25
|
/* A number between minus and plus, in one sunken group so the three read as a single control. */
|
|
27
26
|
export function Stepper({ value, onValueChange, min, max, step, decimals = placesOf(step), unit, label, className, ref }: StepperProps) {
|
|
28
27
|
const { t } = useTranslation('ui');
|
package/src/Toasts.tsx
CHANGED
|
@@ -82,7 +82,7 @@ function ToastCard<T extends Toast>({ toast, footer, onDismiss }: { toast: T; fo
|
|
|
82
82
|
)}
|
|
83
83
|
</span>
|
|
84
84
|
<div className="flex min-w-0 grow flex-col gap-1">
|
|
85
|
-
<span className="text-sm text-text">{toast.title}</span>
|
|
85
|
+
<span className="text-sm break-words text-text">{toast.title}</span>
|
|
86
86
|
{/* Wraps rather than clips: the line under the title is the one that says what went
|
|
87
87
|
wrong, and a reason cut off at the card's edge is no reason at all. */}
|
|
88
88
|
{toast.description !== undefined && toast.description !== '' && (
|
package/src/index.ts
CHANGED
|
@@ -61,6 +61,7 @@ export { Select, type SelectGroup, type SelectItem, type SelectProps } from './S
|
|
|
61
61
|
export { Separator, type SeparatorProps } from './Separator.tsx';
|
|
62
62
|
export { ShortcutHints } from './ShortcutHints.tsx';
|
|
63
63
|
export { Skeleton, type SkeletonProps } from './Skeleton.tsx';
|
|
64
|
+
export { Slider, type SliderProps } from './Slider.tsx';
|
|
64
65
|
export { SlidingColumn, type SlidingColumnProps } from './SlidingColumn.tsx';
|
|
65
66
|
export { Spinner, type SpinnerProps } from './Spinner.tsx';
|
|
66
67
|
export { Stepper, type StepperProps } from './Stepper.tsx';
|