Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0a9bcdb
v2: carousel class
thejackshelton-kunaico Nov 23, 2025
782a450
feat: working carousel with classes
thejackshelton-kunaico Nov 23, 2025
c38e96e
remove deprecated fns
thejackshelton-kunaico Nov 24, 2025
1bcfabb
refactor: remove unused comments
thejackshelton-kunaico Nov 24, 2025
00963a3
feat: simpler serializer call
thejackshelton-kunaico Nov 24, 2025
67e418a
feat: all carousel tests passing
thejackshelton-kunaico Nov 24, 2025
7e30bfc
feat: use qresume instead of hack
thejackshelton-kunaico Nov 24, 2025
bfebd3d
faster
thejackshelton-kunaico Nov 24, 2025
68d3cdd
fix: format
thejackshelton-kunaico Nov 24, 2025
72a20d1
swipe detection script
thejackshelton-kunaico Nov 24, 2025
1a8ec94
feat: implement swipe detection script
thejackshelton-kunaico Nov 24, 2025
d1e959e
feat: implement swipe detection script
thejackshelton-kunaico Nov 24, 2025
150835b
visible task :/
thejackshelton-kunaico Nov 24, 2025
0785cbe
feat: improved prevent
thejackshelton-kunaico Nov 24, 2025
9e074c0
improve perf
thejackshelton-kunaico Nov 24, 2025
af91bdf
back to old
thejackshelton-kunaico Nov 24, 2025
19a7225
feat: pre-warm on dev mode
thejackshelton-kunaico Nov 24, 2025
fb7b873
fix: format
thejackshelton-kunaico Nov 24, 2025
8c5d53f
fix: checkbox flaky test
thejackshelton-kunaico Nov 24, 2025
e0e5009
feat: latest deps
thejackshelton-kunaico Dec 1, 2025
a4a4650
feat: all tests passing
thejackshelton-kunaico Dec 1, 2025
4a7aacf
feat: improved gaps
thejackshelton-kunaico Dec 2, 2025
9ec9b5d
feat: simpler gap handling
thejackshelton-kunaico Dec 2, 2025
6ac8778
feat: better gap dx
thejackshelton-kunaico Dec 2, 2025
c232141
feat: better gap handling
thejackshelton-kunaico Dec 2, 2025
283f8af
remove gap api
thejackshelton-kunaico Dec 2, 2025
22beafa
feat: better scroll markers
thejackshelton-kunaico Dec 2, 2025
a8036c4
feat: no more scroll markers
thejackshelton-kunaico Dec 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .cursor/worktrees.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"setup-worktree": ["npm install"]
}
298 changes: 298 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
"@qds.dev/tools": "workspace:*",
"@qds.dev/ui": "workspace:*",
"@qds.dev/utils": "workspace:*",
"@qwik.dev/core": "https://pkg.pr.new/QwikDev/qwik/@qwik.dev/core@8144",
"@qwik.dev/router": "https://pkg.pr.new/QwikDev/qwik/@qwik.dev/router@8144",
"@qwik.dev/core": "2.0.0-beta.15",
"@qwik.dev/router": "2.0.0-beta.15",
"@qwikest/icons": "^0.0.13",
"@tailwindcss/vite": "^4.1.3",
"@types/estree-jsx": "1.0.5",
Expand Down
9 changes: 7 additions & 2 deletions docs/src/routes/components/carousel/examples/basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@ export default component$(() => {
class="carousel-root"
autoplayInterval={1000}
bind:value={selectedSlide}
itemsPerView={3}
>
<Carousel.PrevTrigger>Prev</Carousel.PrevTrigger>
<Carousel.NextTrigger>Next</Carousel.NextTrigger>
<div class="border border-red-500">
<Carousel.ScrollArea class="h-40">
<Carousel.ScrollArea class="h-40 gap-4">
{colors.map((color) => (
<Carousel.Item value={color} key={color} class="carousel-item h-20">
<Carousel.Item
value={color}
key={color}
class="bg-red-50 h-20 border-4 border-red-500"
>
{color}
</Carousel.Item>
))}
Expand Down
6 changes: 3 additions & 3 deletions libs/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@
"devDependencies": {
"@fluejs/noscroll": "^1.0.0",
"@qds.dev/utils": "workspace:*",
"@qwik.dev/core": "https://pkg.pr.new/QwikDev/qwik/@qwik.dev/core@8144",
"@qwik.dev/core": "2.0.0-beta.15",
"@qds.dev/tools": "workspace:*",
"@types/node": "24.9.0",
"rolldown": "1.0.0-beta.45",
"typescript": "5.4.5",
"uqr": "^0.1.2",
"vitest-browser-qwik": "0.0.12"
"vitest-browser-qwik": "0.1"
},
"peerDependencies": {
"@qwik.dev/core": ">=https://pkg.pr.new/QwikDev/qwik/@qwik.dev/core@8144"
"@qwik.dev/core": ">=2.0.0-beta.15"
},
"dependencies": {
"@oddbird/css-anchor-positioning": "^0.6"
Expand Down
67 changes: 17 additions & 50 deletions libs/components/src/carousel/carousel-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,56 +77,23 @@ export const CarouselItem = component$((props: CarouselItemProps) => {
});

return (
<>
<ScrollMarker align="start" index={index} value={itemValue} />
<Render
{...rest}
fallback="div"
internalRef={itemRef}
id={itemId}
inert={!isVisible.value}
hidden={isInactive.value}
aria-roledescription="slide"
role={context.navTriggerRefsArray.value.length > 0 ? "tabpanel" : undefined}
ui-qds-carousel-item
ui-qds-scope
ui-current={isVisible.value ? "" : undefined}
aria-label={`${index + 1} of ${context.totalItems.value}`}
onFocusIn$={[handleFocusIn$, rest.onFocusIn$]}
>
<Slot />
<ScrollMarker align="center" index={index} value={itemValue} />
</Render>
<ScrollMarker align="end" index={index} value={itemValue} />
</>
);
});

type ScrollMarkerProps = PropsOf<"div"> & {
align: "start" | "center" | "end";
index: number;
value: string;
};

const ScrollMarker = component$((props: ScrollMarkerProps) => {
const context = useContext(carouselContextId);

const { align, index, value, ...rest } = props;

const isVisible = useComputed$(
() =>
context.startValue === value &&
context.currentIndex.value === index &&
context.align.value === align
);

return (
<div
ref={context.scrollStartRef}
ui-qds-scroll-start
hidden={!isVisible.value}
{...{ [`ui-${align}`]: "" }}
<Render
{...rest}
/>
fallback="div"
internalRef={itemRef}
id={itemId}
inert={!isVisible.value}
hidden={isInactive.value}
aria-roledescription="slide"
role={context.navTriggerRefsArray.value.length > 0 ? "tabpanel" : undefined}
ui-qds-carousel-item
ui-qds-scope
ui-current={isVisible.value ? "" : undefined}
ui-initial-item={context.startValue === itemValue ? "" : undefined}
aria-label={`${index + 1} of ${context.totalItems.value}`}
onFocusIn$={[handleFocusIn$, rest.onFocusIn$]}
>
<Slot />
</Render>
);
});
10 changes: 0 additions & 10 deletions libs/components/src/carousel/carousel-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export type CarouselContext = {
// core state
localId: string;
scrollAreaRef: Signal<HTMLDivElement | undefined>;
scrollStartRef: Signal<HTMLDivElement | undefined>;
nextButtonRef: Signal<HTMLButtonElement | undefined>;
prevButtonRef: Signal<HTMLButtonElement | undefined>;
isMouseDragging: Signal<boolean>;
Expand All @@ -39,7 +38,6 @@ export type CarouselContext = {
// derived
isDraggable: Signal<boolean>;
itemsPerView: Signal<number>;
gap: Signal<number>;
align: Signal<"start" | "center" | "end">;
isRewind: Signal<boolean>;
autoPlayIntervalMs: Signal<number>;
Expand All @@ -56,9 +54,6 @@ export type CarouselContext = {
};

export type PublicCarouselRootProps = PropsOf<"div"> & {
/** The gap between items */
gap?: number;

/** Number of items to show at once */
itemsPerView?: number;

Expand Down Expand Up @@ -107,7 +102,6 @@ export const CarouselRoot = component$((props: PublicCarouselRootProps) => {
const scrollAreaRef = useSignal<HTMLDivElement>();
const nextButtonRef = useSignal<HTMLButtonElement>();
const prevButtonRef = useSignal<HTMLButtonElement>();
const scrollStartRef = useSignal<HTMLDivElement>();
const isMouseDragging = useSignal<boolean>(false);
const itemRefsArray = useSignal<Array<Signal>>([]);
const itemValuesArray = useSignal<string[]>([]);
Expand All @@ -128,7 +122,6 @@ export const CarouselRoot = component$((props: PublicCarouselRootProps) => {
// derived
const isDraggable = useComputed$(() => props.draggable ?? true);
const itemsPerView = useComputed$(() => props.itemsPerView ?? 1);
const gap = useComputed$(() => props.gap ?? 0);
const align = useComputed$(() => props.align ?? "start");
const isRewind = useComputed$(() => props.rewind ?? false);

Expand Down Expand Up @@ -202,7 +195,6 @@ export const CarouselRoot = component$((props: PublicCarouselRootProps) => {
scrollAreaRef,
nextButtonRef,
prevButtonRef,
scrollStartRef,
isMouseDragging,
isMouseWheel,
itemRefsArray,
Expand All @@ -215,7 +207,6 @@ export const CarouselRoot = component$((props: PublicCarouselRootProps) => {
autoplayValue,
isDraggable,
itemsPerView,
gap,
align,
isRewind,
autoPlayIntervalMs,
Expand Down Expand Up @@ -247,7 +238,6 @@ export const CarouselRoot = component$((props: PublicCarouselRootProps) => {
ui-vertical={orientation.value === "vertical"}
style={{
"--items-per-view": itemsPerView.value,
"--gap": `${gap.value}px`,
"--scroll-snap-align": align.value,
"--orientation": orientation.value === "vertical" ? "column" : "row"
}}
Expand Down
Loading
Loading