Skip to content

Commit

Permalink
Merge pull request #85 from kunai-consulting/radioGroup
Browse files Browse the repository at this point in the history
Radio group
  • Loading branch information
thejackshelton-kunaico authored Jan 8, 2025
2 parents b8a4fe1 + 14a0c3f commit e79b875
Show file tree
Hide file tree
Showing 18 changed files with 493 additions and 22 deletions.
1 change: 1 addition & 0 deletions apps/docs/src/routes/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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
Expand Up @@ -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;
};
Expand All @@ -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,
Expand Down Expand Up @@ -73,7 +76,7 @@ export const PaginationBase = component$((props: PaginationRootProps) => {
selectedPageSig,
ellipsisSig,
ellipsis,
focusedIndexSig
focusedIndexSig,
};

useContextProvider(paginationContextId, context);
Expand All @@ -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>
Expand Down
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

0 comments on commit e79b875

Please sign in to comment.