Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support refs for all components #974

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion apps/website/auto-api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as fs from 'fs';
import { resolve } from 'path';
import { inspect } from 'util';
import { ViteDevServer } from 'vite';
export default function autoAPI() {
return {
Expand Down
14 changes: 9 additions & 5 deletions apps/website/src/components/api-table/auto-api.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { JSXOutput, component$, $, QRL, useTask$, useSignal } from '@builder.io/qwik';
import { APITable, type APITableProps } from './api-table';
import { packages } from '../install-snippet/install-snippet';

//This is a workaround for not being able to export across packages due to nx rule:
// https://nx.dev/features/enforce-module-boundaries#enforce-module-boundaries
Expand Down Expand Up @@ -32,7 +31,7 @@ type ParsedCommentsProps = {
parsedProps: PublicType;
config: AutoAPIConfig;
};
const currentHeader = $((_: string) => {
const currentHeader = $(() => {
//cannot send h2 from here because current TOC can only read md
return null;
});
Expand Down Expand Up @@ -75,8 +74,8 @@ export const AutoAPI = component$<AnatomyTableProps>(
return (
<>
{topHeaderSig.value}
{subComponents.map((e) => (
<SubComponent subComponent={e} config={config} />
{subComponents.map((e, index) => (
<SubComponent key={index} subComponent={e} config={config} />
))}
</>
);
Expand Down Expand Up @@ -109,10 +108,15 @@ const ParsedComments = component$<ParsedCommentsProps>(({ parsedProps, config })
useTask$(async () => {
const translation: APITableProps = {
propDescriptors: parsedProps[key].map((e) => {
const isObject = e.type.includes('{');
const isUnion = e.type.includes('|');
const isPopup = isObject || isUnion;

return {
name: e.prop,
type: e.type,
type: isObject ? 'object' : isUnion ? 'union' : e.type,
description: e.comment,
info: (isPopup && e.type) || undefined,
};
}),
};
Expand Down
122 changes: 122 additions & 0 deletions apps/website/src/routes/docs/headless/carousel/auto-api/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
export const api = {
carousel: [
{
bullet: [],
},
{
inline: [],
},
{
next: [],
},
{
pagination: [],
},
{
player: [],
},
{
previous: [],
},
{
root: [
{
CarouselRootProps: [
{
comment: 'The gap between slides',
prop: 'gap?',
type: 'number',
},
{
comment: 'Number of slides to show at once',
prop: 'slidesPerView?',
type: 'number',
},
{
comment: 'Whether the carousel is draggable',
prop: 'draggable?',
type: 'boolean',
},
{
comment: 'Alignment of slides within the viewport',
prop: 'align?',
type: "'start' | 'center' | 'end'",
},
{
comment: 'Whether the carousel should rewind',
prop: 'rewind?',
type: 'boolean',
},
{
comment: 'Bind the selected index to a signal',
prop: "'bind:selectedIndex'?",
type: 'Signal<number>',
},
{
comment: 'change the initial index of the carousel on render',
prop: 'startIndex?',
type: 'number',
},
{
comment:
'@deprecated Use bind:selectedIndex instead\n Bind the current slide index to a signal',
prop: "'bind:currSlideIndex'?",
type: 'Signal<number>',
},
{
comment: 'Whether the carousel should autoplay',
prop: "'bind:autoplay'?",
type: 'Signal<boolean>',
},
{
comment: 'the current progress of the carousel',
prop: "'bind:progress'?",
type: 'Signal<number>',
},
{
comment: 'Time in milliseconds before the next slide plays during autoplay',
prop: 'autoPlayIntervalMs?',
type: 'number',
},
{
comment: '@internal Total number of slides',
prop: '_numSlides?',
type: 'number',
},
{
comment: '@internal Whether this carousel has a title',
prop: '_isTitle?',
type: 'boolean',
},
{
comment: 'The sensitivity of the carousel dragging',
prop: 'sensitivity?',
type: '{',
},
],
},
],
},
{
scroller: [],
},
{
slide: [],
},
{
step: [],
},
{
stepper: [],
},
{
title: [],
},
{
'use-carousel': [],
},
{
'use-scroller': [],
},
],
};
50 changes: 50 additions & 0 deletions apps/website/src/routes/docs/headless/carousel/examples/ref.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { component$, useSignal, useStyles$ } from '@builder.io/qwik';
import { Carousel } from '@qwik-ui/headless';

export default component$(() => {
useStyles$(styles);

const firstSlideRef = useSignal<HTMLDivElement>();
const secondSlideRef = useSignal<HTMLDivElement>();

return (
<>
<Carousel.Root class="carousel-root" gap={30}>
<div class="carousel-buttons">
<Carousel.Previous>Prev</Carousel.Previous>
<Carousel.Next>Next</Carousel.Next>
</div>
<Carousel.Scroller class="carousel-scroller">
<Carousel.Slide class="carousel-slide" ref={firstSlideRef}>
Option 1
</Carousel.Slide>

<Carousel.Slide class="carousel-slide" ref={secondSlideRef}>
Option 2
</Carousel.Slide>
</Carousel.Scroller>
</Carousel.Root>

<button
onClick$={() => {
if (!firstSlideRef.value) return;
firstSlideRef.value.style.backgroundColor = '#FFCCCB';
firstSlideRef.value.style.border = '2px dotted #FF0000';
}}
>
Turn first slide red
</button>

<button
onClick$={() => {
if (!secondSlideRef.value) return;
secondSlideRef.value.style.backgroundColor = '#E6F3FF';
}}
>
Turn second slide blue
</button>
</>
);
});
// internal
import styles from './carousel.css?inline';
75 changes: 11 additions & 64 deletions apps/website/src/routes/docs/headless/carousel/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { FeatureList } from '~/components/feature-list/feature-list';

import { Note } from '~/components/note/note';

import { AutoAPI } from '~/components/api-table/auto-api';

import { api } from './auto-api/api';

<StatusBanner status={statusByComponent.headless.Carousel} />

# Carousel
Expand Down Expand Up @@ -234,6 +238,12 @@ To create a custom animation, use a CSS transition on the scroller with the `tra

> Want to help make animations extend to keyframes and other goodies? Reach out to Jack in the [discord](https://discord.gg/3Vej3ehVVQ).

### Refs

Refs can be passed to any Carousel component with the `ref` prop.

<Showcase name="ref" />

## Configurations

### Pagination
Expand Down Expand Up @@ -327,67 +337,4 @@ In the above example, we also use the headless progress component to show the pr

## API

### Carousel.Root

<APITable
propDescriptors={[
{
name: 'gap',
type: 'number',
description: 'The gap between slides.',
},
{
name: 'slidesPerView',
type: 'number',
description: 'Number of slides to show at once.',
},
{
name: 'draggable',
type: 'boolean',
description: 'Whether the carousel is draggable.',
},
{
name: 'align',
type: 'union',
description: 'Alignment of slides within the viewport.',
info: '"start" | "center" | "end"',
},
{
name: 'rewind',
type: 'boolean',
description: 'Whether the carousel should rewind.',
},
{
name: 'bind:selectedIndex',
type: 'Signal<number>',
description: 'Bind the selected index to a signal.',
},
{
name: 'startIndex',
type: 'number',
description: 'Change the initial index of the carousel on render.',
},
{
name: 'bind:autoplay',
type: 'Signal<boolean>',
description: 'Whether the carousel should autoplay.',
},
{
name: 'autoPlayIntervalMs',
type: 'number',
description: 'Time in milliseconds before the next slide plays during autoplay.',
},
{
name: 'direction',
type: 'union',
description:
'Change the direction of the carousel, for it to be veritical define the maxSlideHeight prop as well.',
info: '"row" | "column"',
},
{
name: 'maxSlideHeight',
type: 'number',
description: 'Write the height of the longest slide.',
},
]}
/>
<AutoAPI api={api} />
1 change: 0 additions & 1 deletion apps/website/src/routes/docs/headless/select/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
title: Qwik UI | Select
---

import { api } from './auto-api/api';
import { FeatureList } from '~/components/feature-list/feature-list';
import { statusByComponent } from '~/_state/component-statuses';

Expand Down
4 changes: 3 additions & 1 deletion packages/kit-headless/src/components/carousel/bullet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
useSignal,
sync$,
useComputed$,
Signal,
} from '@builder.io/qwik';
import { carouselContextId } from './context';
import { useCarousel } from './use-carousel';
Expand All @@ -18,7 +19,8 @@ type BulletProps = PropsOf<'button'> & {

export const CarouselBullet = component$(({ _index, ...props }: BulletProps) => {
const context = useContext(carouselContextId);
const bulletRef = useSignal<HTMLButtonElement>();
const internalBulletRef = useSignal<HTMLButtonElement>();
const bulletRef = (props.ref as Signal<HTMLButtonElement>) ?? internalBulletRef;
const slideId = `${context.localId}-${_index ?? -1}`;
const isRenderedSig = useSignal(true);

Expand Down
6 changes: 3 additions & 3 deletions packages/kit-headless/src/components/carousel/inline.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component } from '@builder.io/qwik';
import { CarouselBase, CarouselRootProps } from './root';
import { CarouselBase, PublicCarouselRootProps } from './root';
import { Carousel } from '@qwik-ui/headless';
import { findComponent, processChildren } from '../../utils/inline-component';

Expand All @@ -20,8 +20,8 @@ type InternalProps = {
titleComponent?: typeof Carousel.Title;
};

export const CarouselRoot: Component<CarouselRootProps & InternalProps> = (
props: CarouselRootProps & InternalProps,
export const CarouselRoot: Component<PublicCarouselRootProps & InternalProps> = (
props: PublicCarouselRootProps & InternalProps,
) => {
const {
children,
Expand Down
6 changes: 6 additions & 0 deletions packages/kit-headless/src/components/carousel/next.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ import {
useSignal,
$,
useComputed$,
Signal,
} from '@builder.io/qwik';
import { carouselContextId } from './context';
import { useCarousel } from './use-carousel';

export const CarouselNext = component$((props: PropsOf<'button'>) => {
const context = useContext(carouselContextId);

if (props.ref) {
context.nextButtonRef = props.ref as Signal<HTMLButtonElement>;
}

const isLastSlideInViewSig = useSignal(false);
const initialLoadSig = useSignal(true);
const isKeyboardFocusSig = useSignal(false);
Expand Down
5 changes: 5 additions & 0 deletions packages/kit-headless/src/components/carousel/previous.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useSignal,
$,
useComputed$,
Signal,
} from '@builder.io/qwik';
import { carouselContextId } from './context';
import { useCarousel } from './use-carousel';
Expand All @@ -14,6 +15,10 @@ export const CarouselPrevious = component$((props: PropsOf<'button'>) => {
const context = useContext(carouselContextId);
const isKeyboardFocusSig = useSignal(false);

if (props.ref) {
context.prevButtonRef = props.ref as Signal<HTMLButtonElement>;
}

const { validIndexesSig } = useCarousel(context);

const isFirstScrollableIndexSig = useComputed$(() => {
Expand Down
Loading
Loading