Skip to content
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
2 changes: 1 addition & 1 deletion apps/docs/code-notate.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @type {import('@kunai-consulting/code-notate').CodeNotateConfig} */
export default {
aiModel: "claude-3-7-sonnet-20250219",
aiModel: "claude-sonnet-4-20250514",
aiProvider: "anthropic",
documentationFolder: "./src/routes/base",
formatCommand: "pnpm format",
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"serve": "wrangler pages dev ./dist --compatibility-flags=nodejs_als",
"start": "vite --open --mode ssr",
"qwik": "qwik",
"cn": "cn"
"code-notate": "code-notate"
},
"devDependencies": {
"@builder.io/qwik": "^1.14.1",
Expand Down
14 changes: 14 additions & 0 deletions apps/docs/src/docs-widgets/example-container/example-container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Slot, component$ } from "@builder.io/qwik";

export const ExampleContainer = component$(() => {
return (
<div class="flex gap-4 justify-items-center w-full">
<div class="flex justify-center grow">
<Slot name="example" />
</div>
<div>
<Slot name="props" />
</div>
</div>
);
});
Empty file.
20 changes: 20 additions & 0 deletions apps/docs/src/docs-widgets/switch/switch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { type Signal, component$, useStyles$ } from "@builder.io/qwik";
import { Switch } from "@kunai-consulting/qwik";
import styles from "../../routes/base/switch/examples/switch-custom.css?inline";
//import styles from "./switch.css?inline";
type Props = {
value: Signal<boolean>;
label: string;
};
export const DocsSwitch = component$((props: Props) => {
useStyles$(styles);

return (
<Switch.Root class="switch-root" bind:checked={props.value}>
<Switch.Trigger class="switch-trigger">
<Switch.Thumb class="switch-thumb" />
</Switch.Trigger>
<Switch.Label>{props.label}</Switch.Label>
</Switch.Root>
);
});
65 changes: 65 additions & 0 deletions apps/docs/src/docs-widgets/toggle-group/toggle-group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { type Signal, component$ } from "@builder.io/qwik";

export type ToggleItem = {
value: string;
label?: string;
};

export type DocsToggleGroupProps = {
// Current selected value
value: Signal<string | undefined>;
// Items to render in the toggle group
items: ToggleItem[];
// Optional accessible label for the group
ariaLabel?: string;
// Optional extra classes for the container
class?: string;
};

export const DocsToggleGroup = component$<DocsToggleGroupProps>((props) => {
const { value, items, ariaLabel = "Toggle group", class: className } = props;

return (
<div
role="group"
aria-label={ariaLabel}
class={[
"inline-flex overflow-hidden rounded-md border shadow-sm",
"border-neutral-300 bg-white dark:border-neutral-700 dark:bg-neutral-900",
className
]
.filter(Boolean)
.join(" ")}
style={{ width: "fit-content" }}
>
{items.map((item, idx) => {
const selected = value.value === item.value;
return (
<button
key={item.value}
type="button"
aria-pressed={selected}
onClick$={() => (value.value = item.value)}
class={[
// base
"px-3 py-1.5 text-sm font-medium transition-colors outline-none",
// focus ring
"focus-visible:ring-2 focus-visible:ring-sky-500 ring-offset-2",
"ring-offset-white dark:ring-offset-neutral-900",
// borders between items
idx !== 0 ? "border-l border-neutral-300 dark:border-neutral-700" : "",
// selected vs idle
selected
? "bg-neutral-200 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-50"
: "text-neutral-700 hover:bg-neutral-100 dark:text-neutral-200 dark:hover:bg-neutral-800"
]
.filter(Boolean)
.join(" ")}
>
{item.label ?? item.value}
</button>
);
})}
</div>
);
});
86 changes: 86 additions & 0 deletions apps/docs/src/routes/base/calendar/examples/interactive.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { component$, useSignal, useStyles$ } from "@builder.io/qwik";
import { Calendar } from "@kunai-consulting/qwik";

import { ExampleContainer } from "../../../../docs-widgets/example-container/example-container";
import { DocsSwitch } from "../../../../docs-widgets/switch/switch";
import { DocsToggleGroup } from "../../../../docs-widgets/toggle-group/toggle-group";
import { CalendarIcon } from "../shared/calendar-icon";
import { NextIcon } from "../shared/next-icon";
import { PreviousIcon } from "../shared/previous-icon";
import styles from "./calendar.css?inline";

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

// Primary data
const selectedDate = useSignal<Calendar.ISODate | null>(null);

// Props
const mode = useSignal<"inline" | "popover">("inline");
const disabled = useSignal(false);
const fullWeeks = useSignal(false);
const showDaysOfWeek = useSignal(false);
const showWeekNumbers = useSignal(false);
const open = useSignal(false);

return (
<ExampleContainer>
<div class="flex flex-col gap-4" q:slot="example">
<Calendar.Root
class="calendar-root"
bind:date={selectedDate}
// props
bind:mode={mode}
bind:disabled={disabled}
bind:fullWeeks={fullWeeks}
bind:showDaysOfWeek={showDaysOfWeek}
bind:showWeekNumber={showWeekNumbers}
bind:open={open}
>
{mode.value === "popover" && (
<div class="calendar-field-and-trigger">
<Calendar.Field separator="/">
<Calendar.Month />
<Calendar.Day />
<Calendar.Year />
</Calendar.Field>
<Calendar.Trigger class="calendar-icon-trigger-button">
<CalendarIcon />
</Calendar.Trigger>
</div>
)}
<Calendar.Content>
<Calendar.Header class="calendar-header">
<Calendar.Previous class="calendar-header-button">
<PreviousIcon />
</Calendar.Previous>
<Calendar.Title />
<Calendar.Next class="calendar-header-button">
<NextIcon />
</Calendar.Next>
</Calendar.Header>
<Calendar.Grid class="calendar-grid">
<Calendar.GridDay class="calendar-grid-day" />
</Calendar.Grid>
</Calendar.Content>
</Calendar.Root>

<div>
<p>Selected date: {selectedDate.value}</p>
</div>
</div>

<div class="flex flex-col gap-2" q:slot="props">
<DocsToggleGroup
value={mode}
items={[{ value: "inline" }, { value: "popover" }]}
/>
{mode.value === "popover" && <DocsSwitch value={open} label="Open" />}
<DocsSwitch value={disabled} label="Disabled" />
<DocsSwitch value={fullWeeks} label="Full Weeks" />
<DocsSwitch value={showDaysOfWeek} label="Show Days of Week" />
<DocsSwitch value={showWeekNumbers} label="Show Week Numbers" />
</div>
</ExampleContainer>
);
});
2 changes: 1 addition & 1 deletion apps/docs/src/routes/base/calendar/examples/reactive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default component$(() => {

return (
<div class="calendar-example-container">
<Calendar.Root class="calendar-roott" bind:date={selectedDate}>
<Calendar.Root class="calendar-root" bind:date={selectedDate}>
<Calendar.Field>
<Calendar.Month />
<span>/</span>
Expand Down
161 changes: 6 additions & 155 deletions apps/docs/src/routes/base/calendar/index.mdx
Original file line number Diff line number Diff line change
@@ -1,165 +1,16 @@
import api from "./code-notate/api.json";

# Calendar
A customizable date picker that helps users select dates through an intuitive grid interface.
<Showcase name="hero" />
## Features
<Features api={api} />
## Anatomy
<AnatomyTable api={api} />
## Examples
### Basic Usage
The calendar component provides a simple way to implement date selection functionality. The example below shows the basic setup with core components and date change handling.
<Showcase name="hero" />
This example demonstrates:
- Using `Calendar.Root` as the main container with `locale` set to "en" by default
- `Calendar.Header` containing navigation buttons and month/year display
- `Calendar.Grid` for the calendar layout with `Calendar.GridDay` for individual date cells
- Date selection handling through `onDateChange$` prop
- Visual feedback for selected dates using `data-selected` and current date with `data-current`
Key props used:
- `onDateChange$`: Callback fired when a date is selected
- `data-selected`: Indicates the currently selected date
- `data-current`: Highlights the current date
- `class`: Custom styling applied to various components
The calendar supports full keyboard navigation and follows WAI-ARIA calendar design patterns for accessibility.

### Date picker
The Calendar component can be used to create a
[date picker](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/) by including
the `Calendar.Field` component and setting the `mode` prop to "popover".
The `Calendar.Field` component is a wrapper around the `DateInput` component providing a text input that works in
conjunction with the calendar.
<Showcase name="date-picker" />

### Date picker with readonly date field
You can prevent users from manually entering a date by setting the `Calendar.Field` component's `openCalendarOnClick` prop.
In this mode, the date field is read-only and the calendar is opened when the user clicks on the field.
<Showcase name="date-picker-no-manual-entry" />
A date picker that shows a monthly grid view for selecting dates

### Showing leading zeros
You can control the display of leading zeros in the calendar grid by setting the `showLeadingZeros` prop on the `Calendar.GridDay` component.
<Showcase name="leading-zeros" />
<Showcase name="interactive" />

### Week numbers
You can control the display of week numbers in the calendar grid by setting the `showWeekNumber` prop on the `Calendar.Root` component.
<Showcase name="week-numbers" />

### Full weeks
You can control the display of full weeks in the calendar grid by setting the `fullWeeks` prop on the `Calendar.Root` component.
<Showcase name="full-weeks" />

## Component State
### Using Component State
The Calendar component's state can be controlled through several props on the `Calendar.Root` component:
1. **Selected Date**
Track the currently selected date using the `date` prop:
```tsx
<Calendar.Root date={selectedDate}>
{/* Calendar content */}
</Calendar.Root>
```
2. **Display Options**
Control what information is shown in the calendar:
```tsx
<Calendar.Root
showWeekNumber={true} // Show week numbers
showDaysOfWeek={true} // Show day names header
fullWeeks={true} // Show complete weeks including adjacent month days
>
{/* Calendar content */}
</Calendar.Root>
```
### State Interactions
The Calendar provides ways to respond to date selection through event handlers:
1. **Date Selection**
Listen for date changes using the `onDateChange$` prop on either the `Calendar.Root` or `Calendar.GridDay` components:
```tsx
<Calendar.Root
onDateChange$={(date) => {
console.log('Selected date:', date);
}}
>
{/* Calendar content */}
</Calendar.Root>
```
As shown in the hero example, you can track the selected date in your application state:
```tsx
const selectedDate = useSignal<LocalDate>();
<Calendar.GridDay
onDateChange$={$((date) => {
selectedDate.value = date;
})}
/>
```
2. **Popover State**
Control the calendar popover's open state using the `bind:open` prop:
```tsx
const isOpen = useSignal(false);
<Calendar.Root bind:open={isOpen}>
{/* Calendar content */}
</Calendar.Root>
```
The calendar component maintains its own internal state for:
- Current month/year being displayed
- Date currently in focus
- Navigation between months
- Keyboard navigation within the calendar grid
These aspects are handled automatically by the component and don't require manual management.
## Features

## Core Configuration
### Locale and Date Format
The Calendar component supports localization through the `locale` prop on `Calendar.Root`. Currently, it supports:
- Default locale: `"en"`
- Date format: `YYYY-MM-DD`
As shown in the hero example above, the calendar uses the default English locale for month names, weekday labels, and date formatting.
### Initial Date Selection
The calendar can be configured with:
- `defaultDate`: Sets the initial focused date (defaults to current date)
- `date`: Controls the currently selected date
- `showDaysOfWeek`: Controls visibility of weekday headers (defaults to `true`)
- `showWeekNumber`: Enables week number display (defaults to `false`)
### Week Display
The calendar provides two week display modes controlled by the `fullWeeks` prop:
```typescript
type WeekDisplayProps = {
// When true, shows complete weeks including days from adjacent months
fullWeeks?: boolean; // default: false
}
```
## Advanced Configuration
### Date Change Handling
The calendar supports date selection handling through:
```typescript
type DateChangeProps = {
onDateChange$?: (date: LocalDate) => void;
}
```
Where `LocalDate` is typed as:
```typescript
type LocalDate = `${number}-${number}-${number}`;
```
### Grid Customization
The calendar grid can be customized through:
- `buttonProps`: Props to be spread onto each date button
- Custom day cell rendering through `Calendar.GridDay`
### Technical Constraints
1. Date Format Validation
- Dates must follow the `YYYY-MM-DD` format
- Invalid formats will throw an error during initialization
2. Month Navigation
- Month navigation is handled internally
- Year boundaries are automatically managed when navigating between December and January
3. Focus Management
- Focus is automatically managed within the calendar grid
- Only one date cell is focusable at a time (tabIndex management)
- Focus is preserved during month navigation
The calendar maintains internal state for:
- Current month/year being displayed
- Selected date
- Focused date
- Week number calculations (when enabled)
<Features api={api} />

## Anatomy

<AnatomyTable api={api} />

<APITable api={api} />
4 changes: 3 additions & 1 deletion apps/docs/src/routes/base/checkbox/code-notate/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -208,5 +208,7 @@
"Focus management with ref tracking",
"Disabled state handling",
"Enter key prevention for form submissions"
]
],
"name": "Checkbox",
"summary": "A clickable box that can be checked or unchecked to select options."
}
Loading
Loading