@g4rcez/components 3.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/SKILL.md +266 -0
- package/dist/ai/docs/Alert.md +167 -0
- package/dist/ai/docs/AnimatedList.md +205 -0
- package/dist/ai/docs/Autocomplete.md +225 -0
- package/dist/ai/docs/Button.md +182 -0
- package/dist/ai/docs/Calendar.md +219 -0
- package/dist/ai/docs/Card.md +174 -0
- package/dist/ai/docs/Checkbox.md +199 -0
- package/dist/ai/docs/CommandPalette.md +293 -0
- package/dist/ai/docs/DatePicker.md +171 -0
- package/dist/ai/docs/Dropdown.md +223 -0
- package/dist/ai/docs/Empty.md +163 -0
- package/dist/ai/docs/Expand.md +143 -0
- package/dist/ai/docs/FileUpload.md +225 -0
- package/dist/ai/docs/Form.md +107 -0
- package/dist/ai/docs/FormReset.md +117 -0
- package/dist/ai/docs/Heading.md +88 -0
- package/dist/ai/docs/Input.md +237 -0
- package/dist/ai/docs/InputField.md +170 -0
- package/dist/ai/docs/List.md +205 -0
- package/dist/ai/docs/Menu.md +166 -0
- package/dist/ai/docs/Modal.md +280 -0
- package/dist/ai/docs/MultiSelect.md +196 -0
- package/dist/ai/docs/Notifications.md +231 -0
- package/dist/ai/docs/PageCalendar.md +271 -0
- package/dist/ai/docs/Polymorph.md +159 -0
- package/dist/ai/docs/Progress.md +145 -0
- package/dist/ai/docs/Radiobox.md +128 -0
- package/dist/ai/docs/RenderOnView.md +138 -0
- package/dist/ai/docs/Resizable.md +159 -0
- package/dist/ai/docs/Select.md +284 -0
- package/dist/ai/docs/Shortcut.md +105 -0
- package/dist/ai/docs/Skeleton.md +166 -0
- package/dist/ai/docs/Slider.md +144 -0
- package/dist/ai/docs/Slot.md +173 -0
- package/dist/ai/docs/Spinner.md +118 -0
- package/dist/ai/docs/Stats.md +137 -0
- package/dist/ai/docs/Step.md +159 -0
- package/dist/ai/docs/Switch.md +167 -0
- package/dist/ai/docs/Table.md +298 -0
- package/dist/ai/docs/Tabs.md +191 -0
- package/dist/ai/docs/Tag.md +224 -0
- package/dist/ai/docs/TaskList.md +144 -0
- package/dist/ai/docs/Textarea.md +167 -0
- package/dist/ai/docs/Timeline.md +210 -0
- package/dist/ai/docs/Toolbar.md +132 -0
- package/dist/ai/docs/Tooltip.md +231 -0
- package/dist/ai/docs/TransferList.md +142 -0
- package/dist/ai/docs/Typography.md +187 -0
- package/dist/ai/docs/Wizard.md +213 -0
- package/dist/ai/docs/index.md +183 -0
- package/dist/components/core/tag.d.ts +1 -1
- package/dist/components/core/tag.d.ts.map +1 -1
- package/dist/components/display/list.d.ts.map +1 -1
- package/dist/components/floating/dropdown.d.ts +1 -0
- package/dist/components/floating/dropdown.d.ts.map +1 -1
- package/dist/components/floating/menu.d.ts.map +1 -1
- package/dist/config/default-translations.d.ts +4 -4
- package/dist/hooks/use-translations.d.ts +4 -4
- package/dist/hooks/use-translations.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +28 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2463 -2458
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +12 -12
- package/dist/index.umd.js.map +1 -1
- package/package.json +4 -4
- package/dist/components/core/button.jsx +0 -79
- package/dist/components/core/heading.jsx +0 -4
- package/dist/components/core/polymorph.jsx +0 -5
- package/dist/components/core/render-on-view.jsx +0 -31
- package/dist/components/core/resizable.jsx +0 -51
- package/dist/components/core/slot.jsx +0 -156
- package/dist/components/core/tag.jsx +0 -51
- package/dist/components/core/typography.jsx +0 -22
- package/dist/components/display/alert.jsx +0 -58
- package/dist/components/display/calendar.jsx +0 -299
- package/dist/components/display/card.jsx +0 -43
- package/dist/components/display/empty.jsx +0 -11
- package/dist/components/display/list.jsx +0 -81
- package/dist/components/display/notifications.jsx +0 -126
- package/dist/components/display/progress.jsx +0 -11
- package/dist/components/display/shortcut.jsx +0 -23
- package/dist/components/display/skeleton.jsx +0 -12
- package/dist/components/display/spinner.jsx +0 -7
- package/dist/components/display/stats.jsx +0 -20
- package/dist/components/display/step.jsx +0 -131
- package/dist/components/display/tabs.jsx +0 -98
- package/dist/components/display/timeline.jsx +0 -25
- package/dist/components/floating/command-palette.jsx +0 -194
- package/dist/components/floating/dropdown.jsx +0 -53
- package/dist/components/floating/expand.jsx +0 -44
- package/dist/components/floating/menu.jsx +0 -147
- package/dist/components/floating/modal.jsx +0 -299
- package/dist/components/floating/toolbar.jsx +0 -5
- package/dist/components/floating/tooltip.jsx +0 -58
- package/dist/components/floating/wizard.jsx +0 -161
- package/dist/components/form/autocomplete.jsx +0 -279
- package/dist/components/form/checkbox.jsx +0 -12
- package/dist/components/form/date-picker.jsx +0 -115
- package/dist/components/form/file-upload.jsx +0 -133
- package/dist/components/form/form.jsx +0 -10
- package/dist/components/form/formReset.jsx +0 -17
- package/dist/components/form/free-text.jsx +0 -41
- package/dist/components/form/input-field.jsx +0 -56
- package/dist/components/form/input.jsx +0 -36
- package/dist/components/form/multi-select.jsx +0 -328
- package/dist/components/form/radiobox.jsx +0 -6
- package/dist/components/form/select.jsx +0 -42
- package/dist/components/form/slider.jsx +0 -45
- package/dist/components/form/switch.jsx +0 -46
- package/dist/components/form/task-list.jsx +0 -26
- package/dist/components/form/textarea.jsx +0 -12
- package/dist/components/form/transfer-list.jsx +0 -39
- package/dist/components/index.js +0 -45
- package/dist/components/page-calendar/calendar-header.jsx +0 -81
- package/dist/components/page-calendar/day-view.jsx +0 -87
- package/dist/components/page-calendar/event-pill.jsx +0 -25
- package/dist/components/page-calendar/index.js +0 -2
- package/dist/components/page-calendar/month-view.jsx +0 -47
- package/dist/components/page-calendar/page-calendar.jsx +0 -41
- package/dist/components/page-calendar/page-calendar.types.js +0 -1
- package/dist/components/page-calendar/page-calendar.utils.js +0 -71
- package/dist/components/page-calendar/week-view.jsx +0 -64
- package/dist/components/table/filter.jsx +0 -141
- package/dist/components/table/group.jsx +0 -68
- package/dist/components/table/index.jsx +0 -60
- package/dist/components/table/inner-table.jsx +0 -104
- package/dist/components/table/metadata.jsx +0 -36
- package/dist/components/table/pagination.jsx +0 -73
- package/dist/components/table/row.jsx +0 -58
- package/dist/components/table/sort.jsx +0 -105
- package/dist/components/table/table-lib.js +0 -83
- package/dist/components/table/table.context.jsx +0 -4
- package/dist/components/table/thead.jsx +0 -103
- package/dist/config/context.js +0 -12
- package/dist/config/default-translations.jsx +0 -83
- package/dist/config/default-tweaks.js +0 -4
- package/dist/constants.js +0 -2
- package/dist/hooks/use-click-outside.js +0 -17
- package/dist/hooks/use-color-parser.js +0 -9
- package/dist/hooks/use-components-provider.jsx +0 -19
- package/dist/hooks/use-debounce.js +0 -12
- package/dist/hooks/use-floating-ref.js +0 -6
- package/dist/hooks/use-form.js +0 -550
- package/dist/hooks/use-hover.js +0 -18
- package/dist/hooks/use-input-id.js +0 -5
- package/dist/hooks/use-is-coarse-device.js +0 -12
- package/dist/hooks/use-locale.js +0 -10
- package/dist/hooks/use-media-query.js +0 -25
- package/dist/hooks/use-on-event.js +0 -7
- package/dist/hooks/use-parent.js +0 -21
- package/dist/hooks/use-preferences.js +0 -23
- package/dist/hooks/use-previous.js +0 -9
- package/dist/hooks/use-reactive.js +0 -9
- package/dist/hooks/use-remove-scroll.js +0 -61
- package/dist/hooks/use-resize-observer.js +0 -17
- package/dist/hooks/use-stable-ref.js +0 -9
- package/dist/hooks/use-swipe.js +0 -17
- package/dist/hooks/use-translations.js +0 -9
- package/dist/hooks/use-tweaks.js +0 -9
- package/dist/hooks/use-window-size.js +0 -14
- package/dist/lib/combi-keys.js +0 -60
- package/dist/lib/dict.js +0 -39
- package/dist/lib/dom.js +0 -62
- package/dist/lib/fns.js +0 -46
- package/dist/lib/fzf.js +0 -117
- package/dist/lib/keyboard-area.js +0 -14
- package/dist/styles/common.js +0 -29
- package/dist/styles/dark.js +0 -214
- package/dist/styles/design-tokens.js +0 -69
- package/dist/styles/light.js +0 -214
- package/dist/styles/theme.js +0 -4
- package/dist/styles/theme.types.js +0 -1
- package/dist/types.js +0 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Autocomplete
|
|
3
|
+
description: Searchable select with fuzzy matching, virtualized options, keyboard navigation, and dynamic option creation.
|
|
4
|
+
package: "@g4rcez/components"
|
|
5
|
+
export: "{ Autocomplete }"
|
|
6
|
+
import: "import { Autocomplete } from '@g4rcez/components/autocomplete'"
|
|
7
|
+
category: form
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Autocomplete
|
|
11
|
+
|
|
12
|
+
Searchable select with fuzzy matching, virtualized options, keyboard navigation, and dynamic option creation.
|
|
13
|
+
|
|
14
|
+
## Import
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { Autocomplete } from "@g4rcez/components/autocomplete";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Description |
|
|
23
|
+
|------|------|---------|-------------|
|
|
24
|
+
| `title` | `string` | - | Field label |
|
|
25
|
+
| `value` | `string` | - | Controlled selected value |
|
|
26
|
+
| `options` | `AutocompleteItemProps[]` | - | List of selectable options |
|
|
27
|
+
| `emptyMessage` | `Label` | - | Message displayed when no options match |
|
|
28
|
+
| `dynamicOption` | `boolean` | `false` | Allow creating new options from typed text |
|
|
29
|
+
| `onChange` | `(e: React.ChangeEvent<HTMLInputElement>) => void` | - | Change handler |
|
|
30
|
+
| `error` | `string` | - | Error message displayed below the field |
|
|
31
|
+
| `feedback` | `Label` | - | Success/neutral feedback text below the field |
|
|
32
|
+
| `left` | `Label` | - | Content rendered on the left inside the field border |
|
|
33
|
+
| `right` | `Label` | - | Content rendered on the right inside the field border |
|
|
34
|
+
| `required` | `boolean` | `false` | Marks field as required; hides "Optional" text |
|
|
35
|
+
| `disabled` | `boolean` | `false` | Disables the field |
|
|
36
|
+
| `loading` | `boolean` | `false` | Shows loading state |
|
|
37
|
+
| `container` | `string` | - | Extra CSS classes for the outer `fieldset` |
|
|
38
|
+
| `labelClassName` | `string` | - | Extra CSS classes for the label/border wrapper |
|
|
39
|
+
|
|
40
|
+
### AutocompleteItemProps
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
type AutocompleteItemProps = {
|
|
44
|
+
value: string;
|
|
45
|
+
label?: string;
|
|
46
|
+
hidden?: boolean;
|
|
47
|
+
disabled?: boolean;
|
|
48
|
+
"data-dynamic"?: string;
|
|
49
|
+
Render?: React.FC<OptionProps>;
|
|
50
|
+
} & Record<`data-${string}`, string>;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Design Tokens
|
|
54
|
+
|
|
55
|
+
Tokens this component reads. Customize by overriding these CSS variables in your theme.
|
|
56
|
+
|
|
57
|
+
| Token | CSS Variable | Purpose |
|
|
58
|
+
|-------|-------------|---------|
|
|
59
|
+
| `h-input-height` | `--input-height` | Input height |
|
|
60
|
+
| `px-input-x` | `--input-x` | Horizontal input padding |
|
|
61
|
+
| `py-input-y` | `--input-y` | Vertical input padding |
|
|
62
|
+
| `border-input-border` | `--input-border` | Default field border color |
|
|
63
|
+
| `placeholder-input-mask` | `--input-mask` | Placeholder text color |
|
|
64
|
+
| `placeholder-input-mask-error` | `--input-mask-error` | Placeholder color in error state |
|
|
65
|
+
| `text-foreground` | `--foreground` | Input text color |
|
|
66
|
+
| `text-danger` | `--danger` | Text color in error state |
|
|
67
|
+
| `text-primary` | `--primary` | Focus/hover border and ring color |
|
|
68
|
+
| `bg-floating-background` | `--floating-background` | Dropdown background |
|
|
69
|
+
| `border-floating-border` | `--floating-border` | Dropdown border color |
|
|
70
|
+
| `bg-floating-hover` | `--floating-hover` | Option background on hover/keyboard focus |
|
|
71
|
+
| `text-disabled` | `--disabled` | Empty message text color |
|
|
72
|
+
| `border-tooltip-border` | `--tooltip-border` | Separator inside dropdown |
|
|
73
|
+
| `z-floating` | `--z-floating` | Z-index for the floating panel |
|
|
74
|
+
| `shadow-floating` | `--shadow-floating` | Drop shadow for the floating panel |
|
|
75
|
+
|
|
76
|
+
## Examples
|
|
77
|
+
|
|
78
|
+
### Basic usage
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { Autocomplete } from "@g4rcez/components/autocomplete";
|
|
82
|
+
|
|
83
|
+
const options = [
|
|
84
|
+
{ value: "react", label: "React" },
|
|
85
|
+
{ value: "vue", label: "Vue" },
|
|
86
|
+
{ value: "svelte", label: "Svelte" },
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
function FrameworkPicker() {
|
|
90
|
+
const [value, setValue] = useState("");
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Autocomplete
|
|
94
|
+
title="Framework"
|
|
95
|
+
placeholder="Search frameworks..."
|
|
96
|
+
options={options}
|
|
97
|
+
value={value}
|
|
98
|
+
onChange={(e) => setValue(e.target.value)}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Dynamic option creation
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
function TagPicker() {
|
|
108
|
+
const [value, setValue] = useState("");
|
|
109
|
+
const [tags, setTags] = useState([
|
|
110
|
+
{ value: "typescript", label: "TypeScript" },
|
|
111
|
+
{ value: "javascript", label: "JavaScript" },
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
115
|
+
const next = e.target.value;
|
|
116
|
+
setValue(next);
|
|
117
|
+
if (next && !tags.find((t) => t.value === next)) {
|
|
118
|
+
setTags((prev) => [...prev, { value: next, label: next }]);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Autocomplete
|
|
124
|
+
title="Tag"
|
|
125
|
+
placeholder="Search or create a tag..."
|
|
126
|
+
options={tags}
|
|
127
|
+
value={value}
|
|
128
|
+
onChange={handleChange}
|
|
129
|
+
dynamicOption
|
|
130
|
+
emptyMessage="Type to create a new tag"
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Custom option renderer
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
type UserOption = AutocompleteItemProps & { email: string; role: string };
|
|
140
|
+
|
|
141
|
+
const UserRow = ({ value, label, ...props }: UserOption) => (
|
|
142
|
+
<div className="flex flex-col gap-0.5 py-1">
|
|
143
|
+
<span className="font-medium text-sm text-foreground">{label}</span>
|
|
144
|
+
<span className="text-xs text-muted-foreground">{props.email}</span>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const users: UserOption[] = [
|
|
149
|
+
{ value: "alice", label: "Alice", email: "alice@example.com", role: "admin", Render: UserRow },
|
|
150
|
+
{ value: "bob", label: "Bob", email: "bob@example.com", role: "member", Render: UserRow },
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
<Autocomplete title="Assign to" options={users} value="" onChange={() => {}} />;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Form integration
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
import { Form } from "@g4rcez/components/form";
|
|
160
|
+
import { Button } from "@g4rcez/components/button";
|
|
161
|
+
|
|
162
|
+
const countries = [
|
|
163
|
+
{ value: "br", label: "Brazil" },
|
|
164
|
+
{ value: "us", label: "United States" },
|
|
165
|
+
{ value: "de", label: "Germany" },
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
function ContactForm() {
|
|
169
|
+
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
170
|
+
const data = new FormData(e.currentTarget);
|
|
171
|
+
console.log(Object.fromEntries(data));
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<Form onSubmit={handleSubmit} className="flex flex-col gap-base">
|
|
176
|
+
<Autocomplete
|
|
177
|
+
name="country"
|
|
178
|
+
title="Country"
|
|
179
|
+
options={countries}
|
|
180
|
+
required
|
|
181
|
+
emptyMessage="No countries found"
|
|
182
|
+
/>
|
|
183
|
+
<Button theme="primary" type="submit">Submit</Button>
|
|
184
|
+
</Form>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Do
|
|
190
|
+
|
|
191
|
+
- Provide a meaningful `emptyMessage` so users understand why no results appear.
|
|
192
|
+
- Use `dynamicOption` when users should be able to add values not in the list.
|
|
193
|
+
- Supply a custom `Render` component when each option needs to show more than its label (e.g., avatars, secondary text).
|
|
194
|
+
- Prefer `Autocomplete` over `Select` for lists of more than ~15 items.
|
|
195
|
+
|
|
196
|
+
## Don't
|
|
197
|
+
|
|
198
|
+
- Don't pass raw Tailwind color classes (`bg-blue-500`, `text-white`, `border-gray-300`) — use theme props or design tokens instead.
|
|
199
|
+
- Don't use arbitrary Tailwind values (`bg-[#abc]`, `bg-[--my-var]`) — override CSS variables in your `@theme` block instead.
|
|
200
|
+
- Don't use `Autocomplete` for small lists where `Select` is simpler and faster.
|
|
201
|
+
- Don't forget to handle the unselected case when `dynamicOption` is off and the user types but never picks an option.
|
|
202
|
+
|
|
203
|
+
## Accessibility
|
|
204
|
+
|
|
205
|
+
- Renders a `role="listbox"` dropdown managed by `@floating-ui/react`.
|
|
206
|
+
- The visible input carries `aria-autocomplete="list"`.
|
|
207
|
+
- Each option button has `aria-selected`, `aria-current`, and `aria-checked` set to reflect the active state.
|
|
208
|
+
- Full keyboard navigation: Arrow Up/Down to move, Enter to select, Escape to close.
|
|
209
|
+
- The caret button includes an `sr-only` label from the translation system.
|
|
210
|
+
- Focus is returned to the trigger input when the dropdown closes.
|
|
211
|
+
|
|
212
|
+
## Data Attributes
|
|
213
|
+
|
|
214
|
+
- `data-value` — on the hidden `<input>`: the committed option value.
|
|
215
|
+
- `data-shadow="true"` — on the visible text input to distinguish it from the real field.
|
|
216
|
+
- `data-dynamic="true"` — on options injected by `dynamicOption`.
|
|
217
|
+
- `data-initialized` — managed internally; used by `formReset` to track interaction state.
|
|
218
|
+
- `data-floating="true"` — on the floating panel element.
|
|
219
|
+
|
|
220
|
+
## Notes
|
|
221
|
+
|
|
222
|
+
- The component renders two `<input>` elements: a visible shadow input for display/search and a hidden input with `name` that participates in form submission.
|
|
223
|
+
- Option filtering uses a fuzzy-search (`fzf`) algorithm that matches partial text, abbreviations, and out-of-order characters against both `value` and `label`.
|
|
224
|
+
- Large option lists are virtualized via `react-virtuoso` — rendering performance is maintained even with thousands of options.
|
|
225
|
+
- The dropdown width always matches the width of the triggering fieldset.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Button
|
|
3
|
+
description: A versatile button component with multiple variants, sizes, and states.
|
|
4
|
+
package: "@g4rcez/components"
|
|
5
|
+
export: "{ Button }"
|
|
6
|
+
import: "import { Button } from '@g4rcez/components/button'"
|
|
7
|
+
category: core
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Button
|
|
11
|
+
|
|
12
|
+
A versatile button component with multiple variants, sizes, and states. Built with polymorphic capabilities to render as different HTML elements.
|
|
13
|
+
|
|
14
|
+
## Import
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { Button } from "@g4rcez/components/button";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Description |
|
|
23
|
+
|------|------|---------|-------------|
|
|
24
|
+
| `theme` | `"main" \| "primary" \| "secondary" \| "info" \| "warn" \| "danger" \| "success" \| "muted" \| "neutral" \| "ghost-primary" \| "ghost-secondary" \| "ghost-info" \| "ghost-warn" \| "ghost-danger" \| "ghost-success" \| "ghost-muted" \| "ghost-neutral" \| "raw" \| "disabled" \| "loading"` | `"main"` | Visual theme/variant of the button |
|
|
25
|
+
| `size` | `"icon" \| "min" \| "small" \| "default" \| "big" \| "tiny"` | `"default"` | Size of the button |
|
|
26
|
+
| `rounded` | `"rough" \| "squared" \| "default" \| "circle"` | `"default"` | Border radius style |
|
|
27
|
+
| `icon` | `React.ReactNode` | - | Icon to display before button content |
|
|
28
|
+
| `loading` | `boolean` | `false` | Shows loading state with pulse animation and disables interaction |
|
|
29
|
+
| `disabled` | `boolean` | `false` | Disables the button |
|
|
30
|
+
| `as` | `React.ElementType` | `"button"` | HTML element to render as |
|
|
31
|
+
| `type` | `string` | `"button"` | Button type attribute |
|
|
32
|
+
| `className` | `string` | - | Additional CSS classes |
|
|
33
|
+
| `children` | `React.ReactNode` | - | Button content |
|
|
34
|
+
|
|
35
|
+
## Design Tokens
|
|
36
|
+
|
|
37
|
+
Tokens this component reads. Customize by overriding these CSS variables in your theme.
|
|
38
|
+
|
|
39
|
+
| Token | CSS Variable | Purpose |
|
|
40
|
+
|-------|-------------|---------|
|
|
41
|
+
| `bg-button-primary-bg` | `--button-primary-bg` | Background for primary/main theme |
|
|
42
|
+
| `text-button-primary-text` | `--button-primary-text` | Text color for primary/main theme |
|
|
43
|
+
| `bg-button-secondary-bg` | `--button-secondary-bg` | Background for secondary theme |
|
|
44
|
+
| `text-button-secondary-text` | `--button-secondary-text` | Text color for secondary theme |
|
|
45
|
+
| `bg-button-info-bg` | `--button-info-bg` | Background for info theme |
|
|
46
|
+
| `text-button-info-text` | `--button-info-text` | Text color for info theme |
|
|
47
|
+
| `bg-button-warn-bg` | `--button-warn-bg` | Background for warn theme |
|
|
48
|
+
| `text-button-warn-text` | `--button-warn-text` | Text color for warn theme |
|
|
49
|
+
| `bg-button-danger-bg` | `--button-danger-bg` | Background for danger theme |
|
|
50
|
+
| `text-button-danger-text` | `--button-danger-text` | Text color for danger theme |
|
|
51
|
+
| `bg-button-success-bg` | `--button-success-bg` | Background for success theme |
|
|
52
|
+
| `text-button-success-text` | `--button-success-text` | Text color for success theme |
|
|
53
|
+
| `bg-button-muted-bg` | `--button-muted-bg` | Background for muted theme |
|
|
54
|
+
| `text-button-muted-text` | `--button-muted-text` | Text color for muted theme |
|
|
55
|
+
| `bg-disabled` | `--disabled` | Background for disabled/loading states |
|
|
56
|
+
| `border-card-border` | `--card-border` | Border color for neutral theme |
|
|
57
|
+
| `rounded-button` | `--radius-button` | Default border radius |
|
|
58
|
+
| `focus-visible:ring-ring` | `--ring` | Focus ring color |
|
|
59
|
+
|
|
60
|
+
## Theme Variants
|
|
61
|
+
|
|
62
|
+
### Solid Variants
|
|
63
|
+
|
|
64
|
+
- `main` / `primary`: Primary brand action
|
|
65
|
+
- `secondary`: Secondary action
|
|
66
|
+
- `info`: Informational action
|
|
67
|
+
- `warn`: Warning action
|
|
68
|
+
- `danger`: Destructive action
|
|
69
|
+
- `success`: Confirmation/success action
|
|
70
|
+
- `muted`: Subtle/subdued appearance
|
|
71
|
+
- `neutral`: Transparent with card border
|
|
72
|
+
|
|
73
|
+
### Ghost Variants
|
|
74
|
+
|
|
75
|
+
Transparent background with colored text; shows a tinted background on hover.
|
|
76
|
+
|
|
77
|
+
- `ghost-primary`, `ghost-secondary`, `ghost-info`, `ghost-warn`, `ghost-danger`, `ghost-success`, `ghost-muted`, `ghost-neutral`
|
|
78
|
+
|
|
79
|
+
### Special Variants
|
|
80
|
+
|
|
81
|
+
- `raw`: No default styling — supply all classes via `className`
|
|
82
|
+
- `disabled`: Disabled visual appearance (use the `disabled` prop for full behavior)
|
|
83
|
+
- `loading`: Pulse animation with muted background
|
|
84
|
+
|
|
85
|
+
## Examples
|
|
86
|
+
|
|
87
|
+
### Basic Variants
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<Button theme="primary">Primary</Button>
|
|
91
|
+
<Button theme="secondary">Secondary</Button>
|
|
92
|
+
<Button theme="danger">Delete</Button>
|
|
93
|
+
<Button theme="success">Save</Button>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Sizes
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
<Button size="min">Mini</Button>
|
|
100
|
+
<Button size="small">Small</Button>
|
|
101
|
+
<Button size="default">Default</Button>
|
|
102
|
+
<Button size="big">Big</Button>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### With Icons
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { PlusIcon } from "lucide-react";
|
|
109
|
+
|
|
110
|
+
<Button icon={<PlusIcon size={16} />}>Add Item</Button>
|
|
111
|
+
|
|
112
|
+
<Button size="icon" theme="ghost-primary">
|
|
113
|
+
<PlusIcon size={16} />
|
|
114
|
+
</Button>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Loading State
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<Button loading>Saving...</Button>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Ghost Variants
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
<Button theme="ghost-primary">Ghost Primary</Button>
|
|
127
|
+
<Button theme="ghost-danger">Ghost Danger</Button>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Polymorphic Usage
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
<Button as="a" href="/dashboard" theme="primary">
|
|
134
|
+
Go to Dashboard
|
|
135
|
+
</Button>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Rounded Variants
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
<Button rounded="squared">Squared</Button>
|
|
142
|
+
<Button rounded="circle" size="icon">
|
|
143
|
+
<PlusIcon size={16} />
|
|
144
|
+
</Button>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Do
|
|
148
|
+
|
|
149
|
+
- Use `theme="primary"` for the single main action on a page
|
|
150
|
+
- Use `theme="danger"` for destructive actions (delete, remove)
|
|
151
|
+
- Use the `loading` prop to give immediate feedback for async operations
|
|
152
|
+
- Use `as="a"` with `href` when the button navigates to a URL
|
|
153
|
+
- Use design-token classes for any wrapper elements (`bg-primary`, `text-foreground`)
|
|
154
|
+
|
|
155
|
+
## Don't
|
|
156
|
+
|
|
157
|
+
- Don't pass raw Tailwind color classes (`bg-blue-500`, `text-white`) — use `theme` prop instead
|
|
158
|
+
- Don't use arbitrary Tailwind values (`bg-[#abc]`, `bg-[--my-var]`) — override CSS variables in your `@theme` block
|
|
159
|
+
- Don't place multiple `theme="primary"` buttons in the same view
|
|
160
|
+
- Don't use `theme="primary"` for destructive actions — use `theme="danger"` instead
|
|
161
|
+
|
|
162
|
+
## Accessibility
|
|
163
|
+
|
|
164
|
+
- Renders as a semantic `<button>` element by default
|
|
165
|
+
- Sets `aria-disabled` when `disabled` or `loading` is true
|
|
166
|
+
- Sets `aria-busy` during loading state
|
|
167
|
+
- Click handlers are automatically removed while loading or disabled
|
|
168
|
+
- Supports visible focus rings via `focus-visible:ring-4 focus-visible:ring-ring`
|
|
169
|
+
- Keyboard navigation is fully supported
|
|
170
|
+
|
|
171
|
+
## Data Attributes
|
|
172
|
+
|
|
173
|
+
- `data-component="button"`: Identifies the component for styling/testing
|
|
174
|
+
- `data-theme`: Current theme variant value
|
|
175
|
+
- `data-loading`: `"true"` when in loading state, `"false"` otherwise
|
|
176
|
+
|
|
177
|
+
## Notes
|
|
178
|
+
|
|
179
|
+
- When `loading` is `true`, `disabled` is also set to `true` automatically
|
|
180
|
+
- The component forwards refs correctly to the underlying element
|
|
181
|
+
- All standard HTML button (or target element) attributes are forwarded
|
|
182
|
+
- The `as` prop enables rendering as any HTML element or React component while preserving full TypeScript type safety
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Calendar
|
|
3
|
+
description: Interactive month-view calendar with single-date and range selection, keyboard navigation, and locale support.
|
|
4
|
+
package: "@g4rcez/components"
|
|
5
|
+
export: "{ Calendar }"
|
|
6
|
+
import: "import { Calendar } from '@g4rcez/components/calendar'"
|
|
7
|
+
category: display
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Calendar
|
|
11
|
+
|
|
12
|
+
Interactive month-view calendar with single-date and range selection, keyboard navigation, and locale support.
|
|
13
|
+
|
|
14
|
+
## Import
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { Calendar } from "@g4rcez/components/calendar";
|
|
18
|
+
import type { Range, Locales } from "@g4rcez/components/calendar";
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Props
|
|
22
|
+
|
|
23
|
+
| Prop | Type | Default | Description |
|
|
24
|
+
|------|------|---------|-------------|
|
|
25
|
+
| `date` | `Date` | — | Selected date (single-date mode) |
|
|
26
|
+
| `range` | `Range` | — | Selected range `{ from?: Date; to?: Date }` |
|
|
27
|
+
| `rangeMode` | `boolean` | `false` | Enable range selection mode |
|
|
28
|
+
| `markRange` | `boolean` | `true` | Visually highlight dates inside a range |
|
|
29
|
+
| `markToday` | `boolean` | `true` | Emphasize today's date |
|
|
30
|
+
| `type` | `"date" \| "datetime"` | `"date"` | Show an additional time input when `"datetime"` |
|
|
31
|
+
| `datetimeTitle` | `string` | — | Label for the time input in `"datetime"` mode |
|
|
32
|
+
| `onChange` | `OnChangeDate \| OnChangeRange` | — | Called when a date or range changes |
|
|
33
|
+
| `changeOnlyOnClick` | `boolean` | `false` | Suppress onChange on keyboard navigation; fire only on explicit click |
|
|
34
|
+
| `onChangeYear` | `(date: Date) => void` | — | Called when the year changes |
|
|
35
|
+
| `onChangeMonth` | `(date: Date) => void` | — | Called when the month changes |
|
|
36
|
+
| `disabledDate` | `(date: Date) => boolean` | — | Return `true` to disable a specific date |
|
|
37
|
+
| `RenderOnDay` | `React.FC<{ date: Date }>` | — | Custom renderer overlaid on each day cell |
|
|
38
|
+
| `locale` | `Locales` | — | BCP 47 locale string for month/weekday labels |
|
|
39
|
+
| `labelRange` | `{ from: string; to: string }` | — | Labels shown on the selected range endpoints |
|
|
40
|
+
| `styles` | `CalendarStyles` | — | Fine-grained class overrides per calendar section |
|
|
41
|
+
|
|
42
|
+
## Design Tokens
|
|
43
|
+
|
|
44
|
+
Tokens this component reads. Customize by overriding these CSS variables in your theme.
|
|
45
|
+
|
|
46
|
+
| Token | CSS Variable | Purpose |
|
|
47
|
+
|-------|-------------|---------|
|
|
48
|
+
| `bg-primary` | `--primary` | Selected day background |
|
|
49
|
+
| `text-primary-foreground` | `--primary-foreground` | Selected day text |
|
|
50
|
+
| `hover:bg-primary` | `--primary` | Navigation button hover background |
|
|
51
|
+
| `hover:text-primary-foreground` | `--primary-foreground` | Navigation button hover text |
|
|
52
|
+
| `text-primary` | `--primary` | "Today" button and year/month hover color |
|
|
53
|
+
| `text-disabled` | `--disabled` | Days outside the current month |
|
|
54
|
+
| `border-card-border` | `--card-border` | Range highlight border |
|
|
55
|
+
| `text-foreground` | `--foreground` | Range endpoint label |
|
|
56
|
+
|
|
57
|
+
## Examples
|
|
58
|
+
|
|
59
|
+
### Single Date Selection
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { useState } from "react";
|
|
63
|
+
|
|
64
|
+
function DatePicker() {
|
|
65
|
+
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Calendar
|
|
69
|
+
date={selectedDate}
|
|
70
|
+
onChange={setSelectedDate}
|
|
71
|
+
markToday
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Date Range Selection
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { useState } from "react";
|
|
81
|
+
import { Calendar, type Range } from "@g4rcez/components/calendar";
|
|
82
|
+
|
|
83
|
+
function DateRangePicker() {
|
|
84
|
+
const [range, setRange] = useState<Range>({ from: undefined, to: undefined });
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<Calendar
|
|
88
|
+
range={range}
|
|
89
|
+
rangeMode
|
|
90
|
+
markRange
|
|
91
|
+
onChange={setRange}
|
|
92
|
+
labelRange={{ from: "Start Date", to: "End Date" }}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Disabled Dates
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
const isPastDate = (date: Date): boolean => {
|
|
102
|
+
const today = new Date();
|
|
103
|
+
today.setHours(0, 0, 0, 0);
|
|
104
|
+
return date < today;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
<Calendar
|
|
108
|
+
date={selectedDate}
|
|
109
|
+
onChange={setSelectedDate}
|
|
110
|
+
disabledDate={isPastDate}
|
|
111
|
+
/>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Booking Calendar with Range Restrictions
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
function BookingCalendar() {
|
|
118
|
+
const [bookingRange, setBookingRange] = useState<Range>({});
|
|
119
|
+
|
|
120
|
+
const isDateDisabled = (date: Date): boolean => {
|
|
121
|
+
const today = new Date();
|
|
122
|
+
const minDate = new Date(today);
|
|
123
|
+
minDate.setDate(today.getDate() + 2);
|
|
124
|
+
const maxDate = new Date(today);
|
|
125
|
+
maxDate.setDate(today.getDate() + 90);
|
|
126
|
+
return date < minDate || date > maxDate;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<Calendar
|
|
131
|
+
range={bookingRange}
|
|
132
|
+
rangeMode
|
|
133
|
+
markRange
|
|
134
|
+
markToday
|
|
135
|
+
onChange={setBookingRange}
|
|
136
|
+
disabledDate={isDateDisabled}
|
|
137
|
+
labelRange={{ from: "Check-in", to: "Check-out" }}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Internationalized Calendar
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import { Calendar, type Locales } from "@g4rcez/components/calendar";
|
|
147
|
+
|
|
148
|
+
function InternationalCalendar() {
|
|
149
|
+
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
|
|
150
|
+
const [locale, setLocale] = useState<Locales>("en");
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<Calendar
|
|
154
|
+
date={selectedDate}
|
|
155
|
+
onChange={setSelectedDate}
|
|
156
|
+
locale={locale}
|
|
157
|
+
markToday
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Custom Day Renderer
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
function EventDot({ date }: { date: Date }) {
|
|
167
|
+
const hasEvent = myEvents.some(
|
|
168
|
+
(e) => e.date.toDateString() === date.toDateString()
|
|
169
|
+
);
|
|
170
|
+
return hasEvent ? (
|
|
171
|
+
<span className="absolute bottom-1 left-1/2 -translate-x-1/2 size-1 rounded-full bg-primary" />
|
|
172
|
+
) : null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
<Calendar
|
|
176
|
+
date={selectedDate}
|
|
177
|
+
onChange={setSelectedDate}
|
|
178
|
+
RenderOnDay={EventDot}
|
|
179
|
+
markToday
|
|
180
|
+
/>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Do
|
|
184
|
+
|
|
185
|
+
- Use `markToday` to help users orient themselves.
|
|
186
|
+
- Use `disabledDate` to restrict selection to valid periods.
|
|
187
|
+
- Provide `labelRange` labels when using `rangeMode` so users know which endpoint they are selecting.
|
|
188
|
+
- Pass the correct `locale` to match user locale preferences.
|
|
189
|
+
- Use design-token classes for any wrapper elements (`bg-background`, `border-border`).
|
|
190
|
+
|
|
191
|
+
## Don't
|
|
192
|
+
|
|
193
|
+
- Don't pass raw Tailwind color classes (`bg-blue-500`, `text-white`) in `styles` or `RenderOnDay` — use design tokens instead.
|
|
194
|
+
- Don't use arbitrary Tailwind values (`bg-[#abc]`) — override CSS variables in your `@theme` block.
|
|
195
|
+
- Don't use `Calendar` when only year selection is needed — a `Select` is more efficient.
|
|
196
|
+
- Don't place too many visual markers on each day; keep day-level indicators minimal.
|
|
197
|
+
|
|
198
|
+
## Accessibility
|
|
199
|
+
|
|
200
|
+
- Full keyboard navigation: Arrow keys move days, Shift+Arrow moves months/years, Enter/Space selects.
|
|
201
|
+
- Month and year controls are accessible `<select>` and masked text inputs with `aria-label`.
|
|
202
|
+
- Navigation buttons include `title` attributes for screen reader description.
|
|
203
|
+
- Disabled dates use the native `disabled` attribute on `<button>`.
|
|
204
|
+
|
|
205
|
+
## Data Attributes
|
|
206
|
+
|
|
207
|
+
- `data-component="calendar"` — root container.
|
|
208
|
+
- `data-date` — ISO date string on each day button, used for focus management.
|
|
209
|
+
- `data-samemonth` — `"true"` / `"false"` on each day button.
|
|
210
|
+
- `data-range` — `"true"` / `"false"` on each day button.
|
|
211
|
+
- `data-focustrap` — `"prev"` / `"next"` on navigation buttons.
|
|
212
|
+
|
|
213
|
+
## Notes
|
|
214
|
+
|
|
215
|
+
- Uses `date-fns` for date arithmetic and Framer Motion (`motion/react`) for slide animations.
|
|
216
|
+
- Touch devices get swipe-left/right support for month navigation automatically.
|
|
217
|
+
- When `changeOnlyOnClick` is `false` (default), `onChange` fires on every keyboard navigation move.
|
|
218
|
+
- The calendar always renders 6 weeks (42 cells) to avoid layout shifts when switching months.
|
|
219
|
+
- `type="datetime"` appends a masked time input below the calendar grid.
|