@womp/kookie-ui 0.22.1 → 0.23.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/components.css +57 -2
- package/dist/cjs/components/_internal/base-button.d.ts.map +1 -1
- package/dist/cjs/components/_internal/base-button.js +1 -1
- package/dist/cjs/components/_internal/base-button.js.map +4 -4
- package/dist/cjs/components/button.js +1 -1
- package/dist/cjs/components/button.js.map +4 -4
- package/dist/cjs/components/chatbar.js +1 -1
- package/dist/cjs/components/chatbar.js.map +4 -4
- package/dist/cjs/components/image.d.ts +23 -0
- package/dist/cjs/components/image.d.ts.map +1 -0
- package/dist/cjs/components/image.js +2 -0
- package/dist/cjs/components/image.js.map +7 -0
- package/dist/cjs/components/image.props.d.ts +19 -0
- package/dist/cjs/components/image.props.d.ts.map +1 -0
- package/dist/cjs/components/image.props.js +2 -0
- package/dist/cjs/components/image.props.js.map +7 -0
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/index.d.ts.map +1 -1
- package/dist/cjs/components/index.js +3 -3
- package/dist/cjs/components/index.js.map +4 -4
- package/dist/cjs/components/message-scroller.js +1 -1
- package/dist/cjs/components/message-scroller.js.map +4 -4
- package/dist/cjs/components/notice.js +1 -1
- package/dist/cjs/components/notice.js.map +4 -4
- package/dist/cjs/components/select.d.ts +1 -2
- package/dist/cjs/components/select.d.ts.map +1 -1
- package/dist/cjs/components/select.js +1 -1
- package/dist/cjs/components/select.js.map +3 -3
- package/dist/cjs/components/slider.d.ts.map +1 -1
- package/dist/cjs/components/slider.js +1 -1
- package/dist/cjs/components/slider.js.map +2 -2
- package/dist/cjs/components/spinner.d.ts.map +1 -1
- package/dist/cjs/components/spinner.js +1 -1
- package/dist/cjs/components/spinner.js.map +2 -2
- package/dist/cjs/components/toggle.js +2 -2
- package/dist/cjs/components/toggle.js.map +4 -4
- package/dist/cjs/components/toolbar.js +1 -1
- package/dist/cjs/components/toolbar.js.map +4 -4
- package/dist/cjs/index.js +4 -4
- package/dist/cjs/index.js.map +4 -4
- package/dist/esm/{chunk-PNAB6KCN.js → chunk-6DDOR73D.js} +2 -2
- package/dist/esm/{chunk-PNAB6KCN.js.map → chunk-6DDOR73D.js.map} +1 -1
- package/dist/esm/{chunk-LC5AK2ZD.js → chunk-BOL3VVSN.js} +2 -2
- package/dist/esm/chunk-EZYXYLR2.js +2 -0
- package/dist/esm/chunk-EZYXYLR2.js.map +7 -0
- package/dist/esm/{chunk-WKSXMFFF.js → chunk-FYT7TYGS.js} +2 -2
- package/dist/esm/chunk-GLV7VGBH.js +2 -0
- package/dist/esm/{chunk-KMDXIZAG.js.map → chunk-GLV7VGBH.js.map} +2 -2
- package/dist/esm/{chunk-NLXJZJNN.js → chunk-L2R62BOB.js} +2 -2
- package/dist/esm/chunk-M7I6ISPL.js +2 -0
- package/dist/esm/chunk-M7I6ISPL.js.map +7 -0
- package/dist/esm/chunk-MYYXOFU5.js +1 -0
- package/dist/esm/chunk-NQDMTOGB.js +2 -0
- package/dist/esm/chunk-NQDMTOGB.js.map +7 -0
- package/dist/esm/{chunk-YK3ZUMRQ.js → chunk-Q6MFPATL.js} +2 -2
- package/dist/esm/{chunk-T62PRLPN.js → chunk-QGCXTULQ.js} +2 -2
- package/dist/esm/chunk-UE3HATOA.js +2 -0
- package/dist/esm/chunk-UE3HATOA.js.map +7 -0
- package/dist/esm/chunk-XRCMPBAY.js +2 -0
- package/dist/esm/chunk-XRCMPBAY.js.map +7 -0
- package/dist/esm/{chunk-TJL47GIO.js → chunk-ZEX2DECM.js} +2 -2
- package/dist/esm/components/_internal/base-button.d.ts.map +1 -1
- package/dist/esm/components/_internal/base-button.js +1 -1
- package/dist/esm/components/button.js +1 -1
- package/dist/esm/components/chatbar.js +1 -1
- package/dist/esm/components/combobox.js +1 -1
- package/dist/esm/components/image.d.ts +23 -0
- package/dist/esm/components/image.d.ts.map +1 -0
- package/dist/esm/components/image.js +2 -0
- package/dist/esm/components/image.js.map +7 -0
- package/dist/esm/components/image.props.d.ts +19 -0
- package/dist/esm/components/image.props.d.ts.map +1 -0
- package/dist/esm/components/image.props.js +2 -0
- package/dist/esm/components/image.props.js.map +7 -0
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/components/index.js +1 -1
- package/dist/esm/components/message-scroller.js +1 -1
- package/dist/esm/components/notice.js +1 -1
- package/dist/esm/components/select.d.ts +1 -2
- package/dist/esm/components/select.d.ts.map +1 -1
- package/dist/esm/components/select.js +1 -1
- package/dist/esm/components/slider.d.ts.map +1 -1
- package/dist/esm/components/slider.js +1 -1
- package/dist/esm/components/spinner.d.ts.map +1 -1
- package/dist/esm/components/spinner.js +1 -1
- package/dist/esm/components/toggle.js +1 -1
- package/dist/esm/components/toolbar.js +1 -1
- package/dist/esm/helpers/index.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/props/index.js +1 -1
- package/package.json +1 -1
- package/src/components/_internal/base-button.css +6 -0
- package/src/components/_internal/base-button.tsx +5 -5
- package/src/components/accordion.css +0 -1
- package/src/components/combobox.css +13 -0
- package/src/components/image.css +33 -0
- package/src/components/image.props.ts +18 -0
- package/src/components/image.tsx +71 -0
- package/src/components/index.css +1 -0
- package/src/components/index.tsx +1 -0
- package/src/components/select.tsx +77 -29
- package/src/components/shell.css +12 -0
- package/src/components/slider.css +1 -0
- package/src/components/slider.tsx +3 -12
- package/src/components/spinner.css +19 -1
- package/src/components/spinner.tsx +11 -9
- package/src/styles/tokens/color.css +1 -22
- package/styles.css +57 -23
- package/tokens/base.css +0 -21
- package/tokens.css +0 -21
- package/dist/esm/chunk-3YWVE5GL.js +0 -2
- package/dist/esm/chunk-3YWVE5GL.js.map +0 -7
- package/dist/esm/chunk-A3YQIJDA.js +0 -2
- package/dist/esm/chunk-A3YQIJDA.js.map +0 -7
- package/dist/esm/chunk-D3U4MWDZ.js +0 -1
- package/dist/esm/chunk-IJRBX6EV.js +0 -2
- package/dist/esm/chunk-IJRBX6EV.js.map +0 -7
- package/dist/esm/chunk-KMDXIZAG.js +0 -2
- /package/dist/esm/{chunk-LC5AK2ZD.js.map → chunk-BOL3VVSN.js.map} +0 -0
- /package/dist/esm/{chunk-WKSXMFFF.js.map → chunk-FYT7TYGS.js.map} +0 -0
- /package/dist/esm/{chunk-NLXJZJNN.js.map → chunk-L2R62BOB.js.map} +0 -0
- /package/dist/esm/{chunk-D3U4MWDZ.js.map → chunk-MYYXOFU5.js.map} +0 -0
- /package/dist/esm/{chunk-YK3ZUMRQ.js.map → chunk-Q6MFPATL.js.map} +0 -0
- /package/dist/esm/{chunk-T62PRLPN.js.map → chunk-QGCXTULQ.js.map} +0 -0
- /package/dist/esm/{chunk-TJL47GIO.js.map → chunk-ZEX2DECM.js.map} +0 -0
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
.kui-ComboboxContent {
|
|
13
13
|
--combobox-list-max-height: calc(300px * var(--scale));
|
|
14
14
|
|
|
15
|
+
/* Stops the list's scroll chaining to the page. Not user-scrollable (overflow: hidden), so
|
|
16
|
+
Chromium does not composite it the way it does a scroller with this property. */
|
|
17
|
+
overscroll-behavior: contain;
|
|
18
|
+
|
|
15
19
|
min-width: var(--radix-popover-trigger-width);
|
|
16
20
|
max-height: var(--radix-popover-content-available-height);
|
|
17
21
|
transform-origin: var(--radix-popover-content-transform-origin);
|
|
@@ -55,6 +59,15 @@
|
|
|
55
59
|
scrollbar-color: var(--neutral-a8) transparent;
|
|
56
60
|
}
|
|
57
61
|
|
|
62
|
+
/* Chromium (the only engine with -webkit-app-region) composites a scroller whose
|
|
63
|
+
overscroll-behavior is not auto, even when the list fits: 2 layers. There the content above
|
|
64
|
+
stops the chain. WebKit ignores an ancestor that is not user-scrollable, so it keeps `contain`. */
|
|
65
|
+
@supports (-webkit-app-region: drag) {
|
|
66
|
+
.kui-ComboboxScrollArea {
|
|
67
|
+
overscroll-behavior: auto;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
58
71
|
.kui-ComboboxList {
|
|
59
72
|
outline: 0;
|
|
60
73
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Image: one <img>. It fills its box by `fit`, takes the theme's corner, rests on a quiet
|
|
3
|
+
* neutral fill until it has loaded, so the box holds its place and a failed image keeps it.
|
|
4
|
+
* No fade-in: v1 has had no motion since 0.15. The fill clears once loaded so a transparent
|
|
5
|
+
* picture shows what is behind it.
|
|
6
|
+
*/
|
|
7
|
+
.kui-Image {
|
|
8
|
+
display: block;
|
|
9
|
+
max-width: 100%;
|
|
10
|
+
object-fit: var(--image-object-fit, cover);
|
|
11
|
+
border-radius: var(--radius-2);
|
|
12
|
+
background-color: var(--neutral-a3);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.kui-Image:where([data-status='loaded']) {
|
|
16
|
+
background-color: transparent;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.kui-Image:where(.kui-r-fit-cover) {
|
|
20
|
+
--image-object-fit: cover;
|
|
21
|
+
}
|
|
22
|
+
.kui-Image:where(.kui-r-fit-contain) {
|
|
23
|
+
--image-object-fit: contain;
|
|
24
|
+
}
|
|
25
|
+
.kui-Image:where(.kui-r-fit-fill) {
|
|
26
|
+
--image-object-fit: fill;
|
|
27
|
+
}
|
|
28
|
+
.kui-Image:where(.kui-r-fit-scale-down) {
|
|
29
|
+
--image-object-fit: scale-down;
|
|
30
|
+
}
|
|
31
|
+
.kui-Image:where(.kui-r-fit-none) {
|
|
32
|
+
--image-object-fit: none;
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { radiusPropDef } from '../props/radius.prop.js';
|
|
2
|
+
|
|
3
|
+
import type { PropDef } from '../props/prop-def.js';
|
|
4
|
+
|
|
5
|
+
const objectFitValues = ['cover', 'contain', 'fill', 'scale-down', 'none'] as const;
|
|
6
|
+
|
|
7
|
+
const imagePropDefs = {
|
|
8
|
+
...radiusPropDef,
|
|
9
|
+
/**
|
|
10
|
+
* How the picture fills its box, as CSS `object-fit`.
|
|
11
|
+
* @link https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
|
|
12
|
+
*/
|
|
13
|
+
fit: { type: 'enum', className: 'kui-r-fit', values: objectFitValues, default: 'cover' },
|
|
14
|
+
} satisfies {
|
|
15
|
+
fit: PropDef<(typeof objectFitValues)[number]>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export { imagePropDefs };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { useComposedRefs, useLayoutEffect } from 'radix-ui/internal';
|
|
4
|
+
|
|
5
|
+
import { imagePropDefs } from './image.props.js';
|
|
6
|
+
import { extractProps } from '../helpers/extract-props.js';
|
|
7
|
+
import { widthPropDefs } from '../props/width.props.js';
|
|
8
|
+
import { heightPropDefs } from '../props/height.props.js';
|
|
9
|
+
|
|
10
|
+
import type { ComponentPropsWithout, RemovedProps } from '../helpers/component-props.js';
|
|
11
|
+
import type { WidthProps } from '../props/width.props.js';
|
|
12
|
+
import type { HeightProps } from '../props/height.props.js';
|
|
13
|
+
import type { GetPropDefTypes } from '../props/prop-def.js';
|
|
14
|
+
|
|
15
|
+
type ImageElement = React.ElementRef<'img'>;
|
|
16
|
+
type ImageOwnProps = GetPropDefTypes<typeof imagePropDefs>;
|
|
17
|
+
type ImageStatus = 'loading' | 'loaded' | 'error';
|
|
18
|
+
|
|
19
|
+
interface ImageProps extends ComponentPropsWithout<'img', RemovedProps | 'width' | 'height' | 'src' | 'alt'>, WidthProps, HeightProps, ImageOwnProps {
|
|
20
|
+
src: string;
|
|
21
|
+
/** Required. Pass `""` for a decorative picture. */
|
|
22
|
+
alt: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A plain `<img>` that loads lazily, fills its box by `fit` and takes the theme's corner.
|
|
27
|
+
* One element and no wrapper: `width`, `height`, `className` and `style` all land on the
|
|
28
|
+
* `<img>` itself.
|
|
29
|
+
*
|
|
30
|
+
* Until it loads it rests on a quiet neutral fill, which is also what stays if it fails.
|
|
31
|
+
*/
|
|
32
|
+
const Image = React.forwardRef<ImageElement, ImageProps>((props, forwardedRef) => {
|
|
33
|
+
const { className, alt, radius, loading = 'lazy', decoding = 'async', onLoad, onError, ...imageProps } = extractProps(props, imagePropDefs, widthPropDefs, heightPropDefs);
|
|
34
|
+
|
|
35
|
+
const [status, setStatus] = React.useState<ImageStatus>('loading');
|
|
36
|
+
const ref = React.useRef<HTMLImageElement>(null);
|
|
37
|
+
|
|
38
|
+
// A cached image, or one that finished before hydration, never fires onLoad for us.
|
|
39
|
+
useLayoutEffect(() => {
|
|
40
|
+
const img = ref.current;
|
|
41
|
+
if (img?.complete) setStatus(img.naturalWidth > 0 ? 'loaded' : 'error');
|
|
42
|
+
else setStatus('loading');
|
|
43
|
+
}, [props.src]);
|
|
44
|
+
|
|
45
|
+
const setRefs = useComposedRefs(ref, forwardedRef);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<img
|
|
49
|
+
{...imageProps}
|
|
50
|
+
ref={setRefs}
|
|
51
|
+
alt={alt}
|
|
52
|
+
loading={loading}
|
|
53
|
+
decoding={decoding}
|
|
54
|
+
data-radius={radius}
|
|
55
|
+
data-status={status}
|
|
56
|
+
className={classNames('kui-Image', className)}
|
|
57
|
+
onLoad={(event) => {
|
|
58
|
+
setStatus('loaded');
|
|
59
|
+
onLoad?.(event);
|
|
60
|
+
}}
|
|
61
|
+
onError={(event) => {
|
|
62
|
+
setStatus('error');
|
|
63
|
+
onError?.(event);
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
Image.displayName = 'Image';
|
|
69
|
+
|
|
70
|
+
export { Image };
|
|
71
|
+
export type { ImageProps };
|
package/src/components/index.css
CHANGED
package/src/components/index.tsx
CHANGED
|
@@ -24,6 +24,7 @@ export {
|
|
|
24
24
|
ThickChevronRightIcon,
|
|
25
25
|
ThickDividerHorizontalIcon,
|
|
26
26
|
} from './icons.js';
|
|
27
|
+
export { Image, type ImageProps } from './image.js';
|
|
27
28
|
export { Kbd, type KbdProps } from './kbd.js';
|
|
28
29
|
export { Link, type LinkProps } from './link.js';
|
|
29
30
|
export * as Navbar from './navbar.js';
|
|
@@ -21,16 +21,45 @@ import type { ComponentPropsWithout, RemovedProps } from '../helpers/component-p
|
|
|
21
21
|
|
|
22
22
|
type SelectRootOwnProps = GetPropDefTypes<typeof selectRootPropDefs>;
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
// Trigger and Content pass the size prop def as its own record. It is hoisted because
|
|
25
|
+
// extractProps caches its compiled prop defs on the identity of each record it is given:
|
|
26
|
+
// a literal written at the call site would be a new object, and a recompile, every render.
|
|
27
|
+
const selectSizePropDefs = { size: selectRootPropDefs.size };
|
|
28
|
+
|
|
29
|
+
interface SelectContextValue extends SelectRootOwnProps {
|
|
30
|
+
/** Whether the list is open. Undefined when Content is rendered outside a Kookie Select.Root. */
|
|
31
|
+
open?: boolean;
|
|
32
|
+
}
|
|
25
33
|
const SelectContext = React.createContext<SelectContextValue>({});
|
|
26
34
|
|
|
27
|
-
interface SelectRootProps extends SelectPrimitive.SelectProps,
|
|
35
|
+
interface SelectRootProps extends SelectPrimitive.SelectProps, SelectRootOwnProps {}
|
|
28
36
|
const SelectRoot: React.FC<SelectRootProps> = (props) => {
|
|
29
|
-
const {
|
|
37
|
+
const {
|
|
38
|
+
children,
|
|
39
|
+
size: sizeProp,
|
|
40
|
+
open: openProp,
|
|
41
|
+
defaultOpen,
|
|
42
|
+
onOpenChange,
|
|
43
|
+
...rootProps
|
|
44
|
+
} = props;
|
|
30
45
|
const size = useThemeSize(sizeProp, selectRootPropDefs.size);
|
|
46
|
+
|
|
47
|
+
// Root owns the open state and hands Radix a controlled `open`, so Content learns of a
|
|
48
|
+
// change in the same render Radix does. Radix's uncontrolled state reports changes from an
|
|
49
|
+
// effect, a render late, which would mount the open list once without its wrappers.
|
|
50
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen ?? false);
|
|
51
|
+
const open = openProp ?? uncontrolledOpen;
|
|
52
|
+
const handleOpenChange = React.useCallback(
|
|
53
|
+
(next: boolean) => {
|
|
54
|
+
setUncontrolledOpen(next);
|
|
55
|
+
onOpenChange?.(next);
|
|
56
|
+
},
|
|
57
|
+
[onOpenChange],
|
|
58
|
+
);
|
|
59
|
+
|
|
31
60
|
return (
|
|
32
|
-
<SelectPrimitive.Root {...rootProps}>
|
|
33
|
-
<SelectContext.Provider value={React.useMemo(() => ({ size }), [size])}>
|
|
61
|
+
<SelectPrimitive.Root {...rootProps} open={open} onOpenChange={handleOpenChange}>
|
|
62
|
+
<SelectContext.Provider value={React.useMemo(() => ({ size, open }), [size, open])}>
|
|
34
63
|
{children}
|
|
35
64
|
</SelectContext.Provider>
|
|
36
65
|
</SelectPrimitive.Root>
|
|
@@ -61,7 +90,7 @@ const SelectTrigger = React.forwardRef<SelectTriggerElement, SelectTriggerProps>
|
|
|
61
90
|
// Pass size value from the context to generate styles
|
|
62
91
|
{ size: context?.size, ...props },
|
|
63
92
|
// Pass size prop def to allow it to be extracted
|
|
64
|
-
|
|
93
|
+
selectSizePropDefs,
|
|
65
94
|
selectTriggerPropDefs,
|
|
66
95
|
);
|
|
67
96
|
|
|
@@ -143,12 +172,26 @@ const SelectContent = React.forwardRef<SelectContentElement, SelectContentProps>
|
|
|
143
172
|
// Pass size value from the context to generate styles
|
|
144
173
|
{ size: context?.size, ...props },
|
|
145
174
|
// Pass size prop def to allow it to be extracted
|
|
146
|
-
|
|
175
|
+
selectSizePropDefs,
|
|
147
176
|
selectContentPropDefs,
|
|
148
177
|
);
|
|
149
178
|
const themeContext = useThemeContext();
|
|
179
|
+
|
|
180
|
+
// While closed, Radix still renders Content's children, into a detached fragment: the
|
|
181
|
+
// items must exist for the trigger's value label, typeahead and the native form select.
|
|
182
|
+
// Nothing in that fragment is ever shown, so the theme wrapper and the scroll area, with
|
|
183
|
+
// its two resize observers, are left out until the list opens.
|
|
184
|
+
if (context.open === false && !contentProps.forceMount) {
|
|
185
|
+
return (
|
|
186
|
+
<SelectPrimitive.Portal container={container}>
|
|
187
|
+
<SelectPrimitive.Content {...contentProps} asChild={false} ref={forwardedRef}>
|
|
188
|
+
{children}
|
|
189
|
+
</SelectPrimitive.Content>
|
|
190
|
+
</SelectPrimitive.Portal>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
150
194
|
const resolvedColor = color || themeContext.accentColor;
|
|
151
|
-
const setThumbViewport = useThumbSync<HTMLDivElement>();
|
|
152
195
|
return (
|
|
153
196
|
<SelectPrimitive.Portal container={container}>
|
|
154
197
|
<Theme asChild>
|
|
@@ -161,31 +204,12 @@ const SelectContent = React.forwardRef<SelectContentElement, SelectContentProps>
|
|
|
161
204
|
className={classNames(
|
|
162
205
|
'kui-PopperContent',
|
|
163
206
|
'kui-BaseMenuContent',
|
|
164
|
-
|
|
207
|
+
'kui-variant-soft',
|
|
165
208
|
'kui-SelectContent',
|
|
166
209
|
className,
|
|
167
210
|
)}
|
|
168
211
|
>
|
|
169
|
-
<
|
|
170
|
-
<SelectPrimitive.Viewport
|
|
171
|
-
asChild
|
|
172
|
-
className={classNames('kui-BaseMenuViewport', 'kui-SelectViewport')}
|
|
173
|
-
>
|
|
174
|
-
<ScrollAreaPrimitive.Viewport
|
|
175
|
-
ref={setThumbViewport}
|
|
176
|
-
className="kui-ScrollAreaViewport"
|
|
177
|
-
style={{ overflowY: undefined }}
|
|
178
|
-
>
|
|
179
|
-
{children}
|
|
180
|
-
</ScrollAreaPrimitive.Viewport>
|
|
181
|
-
</SelectPrimitive.Viewport>
|
|
182
|
-
<ScrollAreaPrimitive.Scrollbar
|
|
183
|
-
className="kui-ScrollAreaScrollbar"
|
|
184
|
-
orientation="vertical"
|
|
185
|
-
>
|
|
186
|
-
<ScrollAreaPrimitive.Thumb className="kui-ScrollAreaThumb" />
|
|
187
|
-
</ScrollAreaPrimitive.Scrollbar>
|
|
188
|
-
</ScrollAreaPrimitive.Root>
|
|
212
|
+
<SelectScrollArea>{children}</SelectScrollArea>
|
|
189
213
|
</SelectPrimitive.Content>
|
|
190
214
|
</Theme>
|
|
191
215
|
</SelectPrimitive.Portal>
|
|
@@ -194,6 +218,30 @@ const SelectContent = React.forwardRef<SelectContentElement, SelectContentProps>
|
|
|
194
218
|
);
|
|
195
219
|
SelectContent.displayName = 'Select.Content';
|
|
196
220
|
|
|
221
|
+
/** The open list's scroll area. Its own component so the thumb sync hook runs only while open. */
|
|
222
|
+
function SelectScrollArea({ children }: { children: React.ReactNode }) {
|
|
223
|
+
const setThumbViewport = useThumbSync<HTMLDivElement>();
|
|
224
|
+
return (
|
|
225
|
+
<ScrollAreaPrimitive.Root type="auto" className="kui-ScrollAreaRoot">
|
|
226
|
+
<SelectPrimitive.Viewport
|
|
227
|
+
asChild
|
|
228
|
+
className={classNames('kui-BaseMenuViewport', 'kui-SelectViewport')}
|
|
229
|
+
>
|
|
230
|
+
<ScrollAreaPrimitive.Viewport
|
|
231
|
+
ref={setThumbViewport}
|
|
232
|
+
className="kui-ScrollAreaViewport"
|
|
233
|
+
style={{ overflowY: undefined }}
|
|
234
|
+
>
|
|
235
|
+
{children}
|
|
236
|
+
</ScrollAreaPrimitive.Viewport>
|
|
237
|
+
</SelectPrimitive.Viewport>
|
|
238
|
+
<ScrollAreaPrimitive.Scrollbar className="kui-ScrollAreaScrollbar" orientation="vertical">
|
|
239
|
+
<ScrollAreaPrimitive.Thumb className="kui-ScrollAreaThumb" />
|
|
240
|
+
</ScrollAreaPrimitive.Scrollbar>
|
|
241
|
+
</ScrollAreaPrimitive.Root>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
197
245
|
type SelectItemElement = React.ElementRef<typeof SelectPrimitive.Item>;
|
|
198
246
|
interface SelectItemProps
|
|
199
247
|
extends ComponentPropsWithout<typeof SelectPrimitive.Item, RemovedProps> {}
|
package/src/components/shell.css
CHANGED
|
@@ -88,6 +88,18 @@
|
|
|
88
88
|
isolation: isolate;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/*
|
|
92
|
+
* Chromium (the only engine with -webkit-app-region) gives a scroller whose overscroll-behavior is
|
|
93
|
+
* not auto two composited layers even when its content fits: about 3 MB for a 1280x800 pane. There
|
|
94
|
+
* the Root's own overscroll-behavior already stops the chain, so the content does not need one.
|
|
95
|
+
* WebKit ignores an ancestor that is not user-scrollable, so it keeps `contain` above.
|
|
96
|
+
*/
|
|
97
|
+
@supports (-webkit-app-region: drag) {
|
|
98
|
+
.kui-ShellContent {
|
|
99
|
+
overscroll-behavior: auto;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
91
103
|
/* ── Panes ─────────────────────────────────────────────────────────────────── */
|
|
92
104
|
|
|
93
105
|
/*
|
|
@@ -152,14 +152,7 @@ const Slider = React.forwardRef<SliderElement, SliderProps>((ownProps, forwarded
|
|
|
152
152
|
ref={announceRef}
|
|
153
153
|
aria-live="polite"
|
|
154
154
|
aria-atomic="true"
|
|
155
|
-
className="sr-only"
|
|
156
|
-
style={{
|
|
157
|
-
position: 'absolute',
|
|
158
|
-
left: '-10000px',
|
|
159
|
-
width: '1px',
|
|
160
|
-
height: '1px',
|
|
161
|
-
overflow: 'hidden',
|
|
162
|
-
}}
|
|
155
|
+
className="kui-sr-only"
|
|
163
156
|
/>
|
|
164
157
|
|
|
165
158
|
<SliderPrimitive.Root
|
|
@@ -205,10 +198,8 @@ const Slider = React.forwardRef<SliderElement, SliderProps>((ownProps, forwarded
|
|
|
205
198
|
<button
|
|
206
199
|
key={tick.index}
|
|
207
200
|
className="kui-SliderTick"
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
transform: 'translateX(-50%)',
|
|
211
|
-
}}
|
|
201
|
+
// Only `left` varies per tick; the centring transform lives in `.kui-SliderTick`.
|
|
202
|
+
style={{ left: `${tick.percentage}%` }}
|
|
212
203
|
onClick={() => {
|
|
213
204
|
const currentValues = sliderProps.value ?? sliderProps.defaultValue ?? [min];
|
|
214
205
|
const newValue = Array.isArray(currentValues)
|
|
@@ -8,6 +8,18 @@
|
|
|
8
8
|
position: relative;
|
|
9
9
|
width: var(--space-5);
|
|
10
10
|
height: var(--space-5);
|
|
11
|
+
/* The rotation runs on the inner wheel, clipped here. Unclipped, a running transform animation
|
|
12
|
+
makes Chrome give its own layer to everything painted after it that it might overlap. The
|
|
13
|
+
leaves stay inside this box at every step; the margin keeps their anti-aliased edges on the
|
|
14
|
+
diagonal steps, which a clip at the box itself shaves. */
|
|
15
|
+
contain: paint;
|
|
16
|
+
overflow-clip-margin: 4px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.kui-SpinnerWheel {
|
|
20
|
+
position: absolute;
|
|
21
|
+
inset: 0;
|
|
22
|
+
/* On the wheel, not the root: opacity on the clipping root costs Chrome a second layer per spinner. */
|
|
11
23
|
opacity: var(--spinner-opacity);
|
|
12
24
|
/* Single GPU-composited rotation instead of 8 opacity animations */
|
|
13
25
|
animation: kui-spinner-rotate var(--spinner-animation-duration) steps(8, end) infinite;
|
|
@@ -73,7 +85,13 @@
|
|
|
73
85
|
|
|
74
86
|
/* A busy indicator keeps moving when motion is reduced, more slowly. */
|
|
75
87
|
@media (prefers-reduced-motion: reduce) {
|
|
76
|
-
.kui-
|
|
88
|
+
.kui-SpinnerWheel {
|
|
77
89
|
animation-duration: calc(var(--spinner-animation-duration) * 3);
|
|
78
90
|
}
|
|
79
91
|
}
|
|
92
|
+
|
|
93
|
+
/* Children of a Spinner keep their space but are not shown while it spins. A class, not an inline style. */
|
|
94
|
+
.kui-SpinnerHiddenChildren {
|
|
95
|
+
display: contents;
|
|
96
|
+
visibility: hidden;
|
|
97
|
+
}
|
|
@@ -23,14 +23,16 @@ const Spinner = React.forwardRef<SpinnerElement, SpinnerProps>((props, forwarded
|
|
|
23
23
|
|
|
24
24
|
const spinner = (
|
|
25
25
|
<span {...spinnerProps} ref={forwardedRef} className={classNames('kui-Spinner', className)}>
|
|
26
|
-
<span className="kui-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
<span className="kui-SpinnerWheel">
|
|
27
|
+
<span className="kui-SpinnerLeaf" />
|
|
28
|
+
<span className="kui-SpinnerLeaf" />
|
|
29
|
+
<span className="kui-SpinnerLeaf" />
|
|
30
|
+
<span className="kui-SpinnerLeaf" />
|
|
31
|
+
<span className="kui-SpinnerLeaf" />
|
|
32
|
+
<span className="kui-SpinnerLeaf" />
|
|
33
|
+
<span className="kui-SpinnerLeaf" />
|
|
34
|
+
<span className="kui-SpinnerLeaf" />
|
|
35
|
+
</span>
|
|
34
36
|
</span>
|
|
35
37
|
);
|
|
36
38
|
|
|
@@ -43,7 +45,7 @@ const Spinner = React.forwardRef<SpinnerElement, SpinnerProps>((props, forwarded
|
|
|
43
45
|
* `display: contents` removes the content from the accessibility tree in some browsers,
|
|
44
46
|
* so we force remove it with `aria-hidden`
|
|
45
47
|
*/}
|
|
46
|
-
<span aria-hidden
|
|
48
|
+
<span aria-hidden className="kui-SpinnerHiddenChildren" inert={undefined}>
|
|
47
49
|
{children}
|
|
48
50
|
</span>
|
|
49
51
|
|
|
@@ -89,31 +89,10 @@
|
|
|
89
89
|
|
|
90
90
|
.kui-theme,
|
|
91
91
|
[data-accent-color]:where(:not([data-accent-color=''], [data-accent-color='gray'])) {
|
|
92
|
-
|
|
93
|
-
--focus-2: var(--accent-2);
|
|
94
|
-
--focus-3: var(--accent-3);
|
|
95
|
-
--focus-4: var(--accent-4);
|
|
96
|
-
--focus-5: var(--accent-5);
|
|
97
|
-
--focus-6: var(--accent-6);
|
|
98
|
-
--focus-7: var(--accent-7);
|
|
92
|
+
/* Only the aliases components read; this block runs on every coloured element. */
|
|
99
93
|
--focus-8: var(--accent-8);
|
|
100
|
-
--focus-9: var(--accent-9);
|
|
101
|
-
--focus-10: var(--accent-10);
|
|
102
|
-
--focus-11: var(--accent-11);
|
|
103
|
-
--focus-12: var(--accent-12);
|
|
104
|
-
|
|
105
|
-
--focus-a1: var(--accent-a1);
|
|
106
94
|
--focus-a2: var(--accent-a2);
|
|
107
|
-
--focus-a3: var(--accent-a3);
|
|
108
|
-
--focus-a4: var(--accent-a4);
|
|
109
95
|
--focus-a5: var(--accent-a5);
|
|
110
|
-
--focus-a6: var(--accent-a6);
|
|
111
|
-
--focus-a7: var(--accent-a7);
|
|
112
|
-
--focus-a8: var(--accent-a8);
|
|
113
|
-
--focus-a9: var(--accent-a9);
|
|
114
|
-
--focus-a10: var(--accent-a10);
|
|
115
|
-
--focus-a11: var(--accent-a11);
|
|
116
|
-
--focus-a12: var(--accent-a12);
|
|
117
96
|
}
|
|
118
97
|
|
|
119
98
|
.kui-theme ::selection {
|
package/styles.css
CHANGED
|
@@ -3480,30 +3480,9 @@
|
|
|
3480
3480
|
}
|
|
3481
3481
|
.kui-theme,
|
|
3482
3482
|
[data-accent-color]:where(:not([data-accent-color=''], [data-accent-color='gray'])) {
|
|
3483
|
-
--focus-1: var(--accent-1);
|
|
3484
|
-
--focus-2: var(--accent-2);
|
|
3485
|
-
--focus-3: var(--accent-3);
|
|
3486
|
-
--focus-4: var(--accent-4);
|
|
3487
|
-
--focus-5: var(--accent-5);
|
|
3488
|
-
--focus-6: var(--accent-6);
|
|
3489
|
-
--focus-7: var(--accent-7);
|
|
3490
3483
|
--focus-8: var(--accent-8);
|
|
3491
|
-
--focus-9: var(--accent-9);
|
|
3492
|
-
--focus-10: var(--accent-10);
|
|
3493
|
-
--focus-11: var(--accent-11);
|
|
3494
|
-
--focus-12: var(--accent-12);
|
|
3495
|
-
--focus-a1: var(--accent-a1);
|
|
3496
3484
|
--focus-a2: var(--accent-a2);
|
|
3497
|
-
--focus-a3: var(--accent-a3);
|
|
3498
|
-
--focus-a4: var(--accent-a4);
|
|
3499
3485
|
--focus-a5: var(--accent-a5);
|
|
3500
|
-
--focus-a6: var(--accent-a6);
|
|
3501
|
-
--focus-a7: var(--accent-a7);
|
|
3502
|
-
--focus-a8: var(--accent-a8);
|
|
3503
|
-
--focus-a9: var(--accent-a9);
|
|
3504
|
-
--focus-a10: var(--accent-a10);
|
|
3505
|
-
--focus-a11: var(--accent-a11);
|
|
3506
|
-
--focus-a12: var(--accent-a12);
|
|
3507
3486
|
--accent-ink-mix: 45%;
|
|
3508
3487
|
--accent-ink: color-mix(in oklab, var(--accent-11), var(--accent-12) var(--accent-ink-mix));
|
|
3509
3488
|
--accent-ink-muted: color-mix(in oklab, var(--accent-ink) 74%, transparent);
|
|
@@ -5915,7 +5894,6 @@
|
|
|
5915
5894
|
line-height: var(--line-height-4);
|
|
5916
5895
|
-webkit-transform-origin: top;
|
|
5917
5896
|
transform-origin: top;
|
|
5918
|
-
will-change: height, opacity, transform;
|
|
5919
5897
|
}
|
|
5920
5898
|
.kui-AccordionContentInner {
|
|
5921
5899
|
padding: 0 var(--space-5) var(--space-4);
|
|
@@ -7489,6 +7467,10 @@
|
|
|
7489
7467
|
position: relative;
|
|
7490
7468
|
cursor: progress;
|
|
7491
7469
|
}
|
|
7470
|
+
.kui-BaseButton :where(.kui-BaseButtonContent) {
|
|
7471
|
+
display: contents;
|
|
7472
|
+
visibility: hidden;
|
|
7473
|
+
}
|
|
7492
7474
|
.kui-BaseButton :where(svg) {
|
|
7493
7475
|
width: var(--base-button-icon-size);
|
|
7494
7476
|
height: var(--base-button-icon-size);
|
|
@@ -11099,6 +11081,8 @@
|
|
|
11099
11081
|
}
|
|
11100
11082
|
.kui-ComboboxContent {
|
|
11101
11083
|
--combobox-list-max-height: calc(300px * var(--scale));
|
|
11084
|
+
-ms-scroll-chaining: none;
|
|
11085
|
+
overscroll-behavior: contain;
|
|
11102
11086
|
min-width: var(--radix-popover-trigger-width);
|
|
11103
11087
|
max-height: var(--radix-popover-content-available-height);
|
|
11104
11088
|
-webkit-transform-origin: var(--radix-popover-content-transform-origin);
|
|
@@ -11143,6 +11127,12 @@
|
|
|
11143
11127
|
scrollbar-width: thin;
|
|
11144
11128
|
scrollbar-color: var(--neutral-a8) transparent;
|
|
11145
11129
|
}
|
|
11130
|
+
@supports (-webkit-app-region: drag) {
|
|
11131
|
+
.kui-ComboboxScrollArea {
|
|
11132
|
+
-ms-scroll-chaining: chained;
|
|
11133
|
+
overscroll-behavior: auto;
|
|
11134
|
+
}
|
|
11135
|
+
}
|
|
11146
11136
|
.kui-ComboboxList {
|
|
11147
11137
|
outline: 0;
|
|
11148
11138
|
}
|
|
@@ -11506,6 +11496,32 @@
|
|
|
11506
11496
|
letter-spacing: calc(var(--letter-spacing-9) + var(--heading-letter-spacing));
|
|
11507
11497
|
}
|
|
11508
11498
|
}
|
|
11499
|
+
.kui-Image {
|
|
11500
|
+
display: block;
|
|
11501
|
+
max-width: 100%;
|
|
11502
|
+
-o-object-fit: var(--image-object-fit, cover);
|
|
11503
|
+
object-fit: var(--image-object-fit, cover);
|
|
11504
|
+
border-radius: var(--radius-2);
|
|
11505
|
+
background-color: var(--neutral-a3);
|
|
11506
|
+
}
|
|
11507
|
+
.kui-Image:where([data-status='loaded']) {
|
|
11508
|
+
background-color: transparent;
|
|
11509
|
+
}
|
|
11510
|
+
.kui-Image:where(.kui-r-fit-cover) {
|
|
11511
|
+
--image-object-fit: cover;
|
|
11512
|
+
}
|
|
11513
|
+
.kui-Image:where(.kui-r-fit-contain) {
|
|
11514
|
+
--image-object-fit: contain;
|
|
11515
|
+
}
|
|
11516
|
+
.kui-Image:where(.kui-r-fit-fill) {
|
|
11517
|
+
--image-object-fit: fill;
|
|
11518
|
+
}
|
|
11519
|
+
.kui-Image:where(.kui-r-fit-scale-down) {
|
|
11520
|
+
--image-object-fit: scale-down;
|
|
11521
|
+
}
|
|
11522
|
+
.kui-Image:where(.kui-r-fit-none) {
|
|
11523
|
+
--image-object-fit: none;
|
|
11524
|
+
}
|
|
11509
11525
|
.kui-Kbd {
|
|
11510
11526
|
display: -webkit-inline-box;
|
|
11511
11527
|
display: -ms-inline-flexbox;
|
|
@@ -16370,6 +16386,12 @@
|
|
|
16370
16386
|
overscroll-behavior: contain;
|
|
16371
16387
|
isolation: isolate;
|
|
16372
16388
|
}
|
|
16389
|
+
@supports (-webkit-app-region: drag) {
|
|
16390
|
+
.kui-ShellContent {
|
|
16391
|
+
-ms-scroll-chaining: chained;
|
|
16392
|
+
overscroll-behavior: auto;
|
|
16393
|
+
}
|
|
16394
|
+
}
|
|
16373
16395
|
.kui-ShellPane {
|
|
16374
16396
|
--kui-pane-inset-start: initial;
|
|
16375
16397
|
--kui-pane-inset-end: initial;
|
|
@@ -17418,6 +17440,8 @@
|
|
|
17418
17440
|
.kui-SliderTick {
|
|
17419
17441
|
position: absolute;
|
|
17420
17442
|
top: 0;
|
|
17443
|
+
-webkit-transform: translateX(-50%);
|
|
17444
|
+
transform: translateX(-50%);
|
|
17421
17445
|
display: -webkit-box;
|
|
17422
17446
|
display: -ms-flexbox;
|
|
17423
17447
|
display: flex;
|
|
@@ -17481,6 +17505,12 @@
|
|
|
17481
17505
|
position: relative;
|
|
17482
17506
|
width: var(--space-5);
|
|
17483
17507
|
height: var(--space-5);
|
|
17508
|
+
contain: paint;
|
|
17509
|
+
overflow-clip-margin: 4px;
|
|
17510
|
+
}
|
|
17511
|
+
.kui-SpinnerWheel {
|
|
17512
|
+
position: absolute;
|
|
17513
|
+
inset: 0;
|
|
17484
17514
|
opacity: var(--spinner-opacity);
|
|
17485
17515
|
-webkit-animation: kui-spinner-rotate var(--spinner-animation-duration) steps(8, end) infinite;
|
|
17486
17516
|
animation: kui-spinner-rotate var(--spinner-animation-duration) steps(8, end) infinite;
|
|
@@ -17553,11 +17583,15 @@
|
|
|
17553
17583
|
}
|
|
17554
17584
|
}
|
|
17555
17585
|
@media (prefers-reduced-motion: reduce) {
|
|
17556
|
-
.kui-
|
|
17586
|
+
.kui-SpinnerWheel {
|
|
17557
17587
|
-webkit-animation-duration: calc(var(--spinner-animation-duration) * 3);
|
|
17558
17588
|
animation-duration: calc(var(--spinner-animation-duration) * 3);
|
|
17559
17589
|
}
|
|
17560
17590
|
}
|
|
17591
|
+
.kui-SpinnerHiddenChildren {
|
|
17592
|
+
display: contents;
|
|
17593
|
+
visibility: hidden;
|
|
17594
|
+
}
|
|
17561
17595
|
.kui-SwitchRoot {
|
|
17562
17596
|
position: relative;
|
|
17563
17597
|
display: -webkit-inline-box;
|
package/tokens/base.css
CHANGED
|
@@ -129,30 +129,9 @@
|
|
|
129
129
|
}
|
|
130
130
|
.kui-theme,
|
|
131
131
|
[data-accent-color]:where(:not([data-accent-color=''], [data-accent-color='gray'])) {
|
|
132
|
-
--focus-1: var(--accent-1);
|
|
133
|
-
--focus-2: var(--accent-2);
|
|
134
|
-
--focus-3: var(--accent-3);
|
|
135
|
-
--focus-4: var(--accent-4);
|
|
136
|
-
--focus-5: var(--accent-5);
|
|
137
|
-
--focus-6: var(--accent-6);
|
|
138
|
-
--focus-7: var(--accent-7);
|
|
139
132
|
--focus-8: var(--accent-8);
|
|
140
|
-
--focus-9: var(--accent-9);
|
|
141
|
-
--focus-10: var(--accent-10);
|
|
142
|
-
--focus-11: var(--accent-11);
|
|
143
|
-
--focus-12: var(--accent-12);
|
|
144
|
-
--focus-a1: var(--accent-a1);
|
|
145
133
|
--focus-a2: var(--accent-a2);
|
|
146
|
-
--focus-a3: var(--accent-a3);
|
|
147
|
-
--focus-a4: var(--accent-a4);
|
|
148
134
|
--focus-a5: var(--accent-a5);
|
|
149
|
-
--focus-a6: var(--accent-a6);
|
|
150
|
-
--focus-a7: var(--accent-a7);
|
|
151
|
-
--focus-a8: var(--accent-a8);
|
|
152
|
-
--focus-a9: var(--accent-a9);
|
|
153
|
-
--focus-a10: var(--accent-a10);
|
|
154
|
-
--focus-a11: var(--accent-a11);
|
|
155
|
-
--focus-a12: var(--accent-a12);
|
|
156
135
|
--accent-ink-mix: 45%;
|
|
157
136
|
--accent-ink: color-mix(in oklab, var(--accent-11), var(--accent-12) var(--accent-ink-mix));
|
|
158
137
|
--accent-ink-muted: color-mix(in oklab, var(--accent-ink) 74%, transparent);
|