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

Radio group #85

Merged
merged 3 commits into from
Jan 8, 2025
Merged
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: 1 addition & 0 deletions apps/docs/src/routes/index.mdx
Original file line number Diff line number Diff line change
@@ -11,3 +11,4 @@ Here is some MDX
- [Pagination](pagination)
- [OTP](otp)
- [Scroll Area](scroll-area)
- [Radio Group](radio-group)
3 changes: 2 additions & 1 deletion apps/docs/src/routes/menu.md
Original file line number Diff line number Diff line change
@@ -7,8 +7,9 @@
- [Pagination](/pagination)
- [OTP](/otp)
- [Scroll Area](/scroll-area)
- [Radio Group](/radio-group)

## Styled

- [Feed](/feed)
- [Avatar](/avatar)
- [Avatar](/avatar)
68 changes: 68 additions & 0 deletions apps/docs/src/routes/pagination/auto-api/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
export const api = {
"pagination": [
{
"Pagination Ellipsis": {
"types": [],
"inheritsFrom": "div"
}
},
{
"Pagination Next": {
"types": [],
"inheritsFrom": "button"
}
},
{
"Pagination Page": {
"types": [],
"dataAttributes": [
{
"name": "data-index",
"type": "string"
},
{
"name": "data-current",
"type": "string"
}
]
}
},
{
"Pagination Previous": {
"types": [],
"inheritsFrom": "button"
}
},
{
"Pagination Root": {
"types": [],
"inheritsFrom": "div",
"dataAttributes": [
{
"name": "data-disabled",
"type": "string | undefined"
}
]
}
}
],
"anatomy": [
{
"name": "Pagination.Root"
},
{
"name": "Pagination.Page"
},
{
"name": "Pagination.Next"
},
{
"name": "Pagination.Previous"
},
{
"name": "Pagination.Ellipsis"
}
],
"keyboardInteractions": [],
"features": []
};
20 changes: 20 additions & 0 deletions apps/docs/src/routes/radio-group/examples/hero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { component$, useStyles$ } from '@builder.io/qwik';
import { RadioGroup } from '@kunai-consulting/qwik-components';

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

return (
<RadioGroup.Root>
<RadioGroup.Trigger class="radio-group-trigger">
<RadioGroup.Indicator class="radio-group-indicator">
<LuCheck />
</RadioGroup.Indicator>
</RadioGroup.Trigger>
</RadioGroup.Root>
);
});

// example styles
import styles from './radio-group.css?inline';
import { LuCheck } from '@qwikest/icons/lucide';
24 changes: 24 additions & 0 deletions apps/docs/src/routes/radio-group/examples/radio-group.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.radio-group-root {
display: flex;
align-items: center;
gap: 8px;
}

.radrio-group-trigger {
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
border-radius: 4px;
border: 1px solid #ccc;
}

.radio-group-indicator {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #ccc;
}
9 changes: 9 additions & 0 deletions apps/docs/src/routes/radio-group/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: Qwik Design System | Radio Group
---

# Radio Group

A button that can be toggled between two states.

<Showcase name="hero" />
11 changes: 6 additions & 5 deletions libs/components/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * as Otp from "./otp";
export * as Checkbox from "./checkbox";
export * as Checklist from "./checklist";
export * as Pagination from "./pagination";
export * as ScrollArea from "./scroll-area";
export * as Otp from './otp';
export * as Checkbox from './checkbox';
export * as Checklist from './checklist';
export * as Pagination from './pagination';
export * as ScrollArea from './scroll-area';
export * as RadioGroup from './radio-group';
35 changes: 19 additions & 16 deletions libs/components/src/pagination/pagination-root.tsx
Original file line number Diff line number Diff line change
@@ -3,29 +3,32 @@ import {
type QRL,
Slot,
component$,
JSXNode,
JSXChildren,
type JSXNode,
type JSXChildren,
useContextProvider,
useSignal,
useTask$,
type Signal,
useComputed$,
useId,
$
} from "@builder.io/qwik";
import { findComponent, processChildren } from "../../utils/inline-component";
import { PaginationPage } from "./pagination-page";
import { type PaginationContext, paginationContextId } from "./pagination-context";
import { useBoundSignal } from "../../utils/bound-signal";
import { getPaginationItems } from "./utils";
$,
} from '@builder.io/qwik';
import { findComponent, processChildren } from '../../utils/inline-component';
import { PaginationPage } from './pagination-page';
import {
type PaginationContext,
paginationContextId,
} from './pagination-context';
import { useBoundSignal } from '../../utils/bound-signal';
import { getPaginationItems } from './utils';

export type PaginationRootProps = PropsOf<"div"> & {
export type PaginationRootProps = PropsOf<'div'> & {
totalPages: number;
currentPage?: number;
"bind:page"?: Signal<number | 1>;
'bind:page'?: Signal<number | 1>;
onPageChange$?: QRL<(page: number) => void>;
disabled?: boolean;
pages: any[];
pages: number[];
ellipsis?: JSXChildren;
siblingCount?: number;
};
@@ -45,7 +48,7 @@ export const PaginationRoot = (props: PaginationRootProps) => {

export const PaginationBase = component$((props: PaginationRootProps) => {
const {
"bind:page": givenPageSig,
'bind:page': givenPageSig,
totalPages,
onPageChange$,
currentPage,
@@ -73,7 +76,7 @@ export const PaginationBase = component$((props: PaginationRootProps) => {
selectedPageSig,
ellipsisSig,
ellipsis,
focusedIndexSig
focusedIndexSig,
};

useContextProvider(paginationContextId, context);
@@ -97,8 +100,8 @@ export const PaginationBase = component$((props: PaginationRootProps) => {
<div
{...rest}
data-qds-pagination-root
data-disabled={context.isDisabledSig.value ? "" : undefined}
aria-disabled={context.isDisabledSig.value ? "true" : "false"}
data-disabled={context.isDisabledSig.value ? '' : undefined}
aria-disabled={context.isDisabledSig.value ? 'true' : 'false'}
>
<Slot />
</div>
7 changes: 7 additions & 0 deletions libs/components/src/radio-group/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { RadioGroupRoot as Root } from './radio-group-root';
export { RadioGroupIndicator as Indicator } from './radio-group-indicator';
export { RadioGroupTrigger as Trigger } from './radio-group-trigger';
export { RadioGroupLabel as Label } from './radio-group-label';
export { RadioGroupDescription as Description } from './radio-group-description';
export { RadioGroupHiddenNativeInput as HiddenNativeInput } from './radio-group-hidden-input';
export { RadioGroupErrorMessage as ErrorMessage } from './radio-group-error-message';
17 changes: 17 additions & 0 deletions libs/components/src/radio-group/radio-group-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createContextId, type Signal } from '@builder.io/qwik';

export const radioGroupContextId = createContextId<RadioGroupContext>(
'qds-radio-group-context'
);

export type RadioGroupContext = {
isCheckedSig: Signal<boolean>;
isDisabledSig: Signal<boolean | undefined>;
isErrorSig: Signal<boolean | undefined>;
localId: string;
isDescription: boolean | undefined;
name: string | undefined;
required: boolean | undefined;
value: string | undefined;
triggerRef: Signal<HTMLButtonElement | undefined>;
};
33 changes: 33 additions & 0 deletions libs/components/src/radio-group/radio-group-description.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
component$,
type PropsOf,
Slot,
sync$,
useContext,
useOnWindow,
useTask$,
} from '@builder.io/qwik';
import { radioGroupContextId } from './radio-group-context';

type RadioGroupDescriptionProps = PropsOf<'div'>;

export const RadioGroupDescription = component$(
(props: RadioGroupDescriptionProps) => {
const context = useContext(radioGroupContextId);
const descriptionId = `${context.localId}-description`;

useTask$(() => {
if (!context.isDescription) {
console.warn(
'Qwik Design System Warning: No description prop provided to the Radio Group Root component.'
);
}
});

return (
<div id={descriptionId} data-qds-checkbox-description {...props}>
<Slot />
</div>
);
}
);
31 changes: 31 additions & 0 deletions libs/components/src/radio-group/radio-group-error-message.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
component$,
Slot,
useContext,
useTask$,
type PropsOf,
} from '@builder.io/qwik';
import { radioGroupContextId } from './radio-group-context';

type RadioGroupErrorMessageProps = PropsOf<'div'>;

export const RadioGroupErrorMessage = component$(
(props: RadioGroupErrorMessageProps) => {
const context = useContext(radioGroupContextId);
const errorId = `${context.localId}-error`;

useTask$(({ cleanup }) => {
context.isErrorSig.value = true;

cleanup(() => {
context.isErrorSig.value = false;
});
});

return (
<div id={errorId} data-qds-radio-group-error-message {...props}>
<Slot />
</div>
);
}
);
36 changes: 36 additions & 0 deletions libs/components/src/radio-group/radio-group-hidden-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { $, component$, useContext, type PropsOf } from '@builder.io/qwik';
import { VisuallyHidden } from '../visually-hidden/visually-hidden';
import { radioGroupContextId } from './radio-group-context';

type RadioGroupHiddenNativeInputProps = PropsOf<'input'>;

export const RadioGroupHiddenNativeInput = component$(
(props: RadioGroupHiddenNativeInputProps) => {
const context = useContext(radioGroupContextId);

const handleChange$ = $((e: InputEvent) => {
const target = e.target as HTMLInputElement;
if (target.checked === context.isCheckedSig.value) {
return;
}

context.isCheckedSig.value = target.checked;
});

return (
<VisuallyHidden>
<input
type="radio"
tabIndex={-1}
checked={context.isCheckedSig.value === true}
data-qds-checkbox-hidden-input
name={context.name ?? props.name ?? undefined}
required={context.required ?? props.required ?? undefined}
value={context.value ?? props.value ?? undefined}
onChange$={[handleChange$, props.onChange$]}
{...props}
/>
</VisuallyHidden>
);
}
);
32 changes: 32 additions & 0 deletions libs/components/src/radio-group/radio-group-indicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
component$,
useContext,
type PropsOf,
Slot,
useTask$,
useStyles$,
} from '@builder.io/qwik';
import { RadioGroupContext, radioGroupContextId } from './radio-group-context';
import './radio-group.css';
import styles from './radio-group.css?inline';

export type RadioGroupIndicatorProps = PropsOf<'span'>;

export const RadioGroupIndicator = component$<RadioGroupIndicatorProps>(
(props) => {
useStyles$(styles);
const context = useContext(radioGroupContextId);

return (
<span
{...props}
data-hidden={!context.isCheckedSig.value}
data-checked={context.isCheckedSig.value ? '' : undefined}
data-qds-indicator
aria-hidden="true"
>
<Slot />
</span>
);
}
);
Loading