@atlure/ui 0.4.0 → 0.6.0

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.
Files changed (72) hide show
  1. package/README.md +39 -6
  2. package/package.json +5 -6
  3. package/src/components/alert-dialog/alert-dialog.tsx +59 -0
  4. package/src/components/alert-dialog/utils.ts +5 -0
  5. package/src/components/avatar/avatar-group.tsx +52 -0
  6. package/src/components/avatar/avatar.tsx +54 -11
  7. package/src/components/badge/urgency-badge.tsx +21 -0
  8. package/src/components/calendar/calendar.tsx +189 -0
  9. package/src/components/checkbox/checkbox.tsx +26 -9
  10. package/src/components/chip/chip.tsx +66 -0
  11. package/src/components/date-range-picker/date-range-picker.tsx +104 -0
  12. package/src/components/dialog/dialog.tsx +137 -0
  13. package/src/components/format/date-label.tsx +41 -0
  14. package/src/components/format/distance-label.tsx +13 -0
  15. package/src/components/format/duration-label.tsx +13 -0
  16. package/src/components/format/money-label.tsx +20 -0
  17. package/src/components/picker/picker.tsx +104 -0
  18. package/src/components/progress/progress.tsx +95 -0
  19. package/src/components/progress/utils.ts +10 -0
  20. package/src/components/radio-group/radio-group-context.tsx +27 -0
  21. package/src/components/radio-group/radio-group.tsx +150 -0
  22. package/src/components/screen-header/screen-header.tsx +62 -0
  23. package/src/components/segmented-control/segmented-control.tsx +67 -0
  24. package/src/components/select/select.tsx +134 -0
  25. package/src/components/settings-row/settings-row.tsx +52 -0
  26. package/src/components/sheet/sheet.tsx +139 -0
  27. package/src/components/sheet/utils.ts +55 -0
  28. package/src/components/slider/format-label.ts +3 -0
  29. package/src/components/slider/hooks/use-slider-drag.ts +47 -0
  30. package/src/components/slider/range-slider.tsx +203 -0
  31. package/src/components/slider/slider.tsx +148 -0
  32. package/src/components/slider/utils.ts +57 -0
  33. package/src/components/star-rating/star-rating.tsx +98 -0
  34. package/src/components/switch/switch.tsx +34 -6
  35. package/src/components/tabs/tabs-context.tsx +32 -0
  36. package/src/components/tabs/tabs.tsx +239 -0
  37. package/src/components/time-picker/time-picker.tsx +131 -0
  38. package/src/components/toast/toast-context.tsx +26 -0
  39. package/src/components/toast/toast-provider.tsx +70 -0
  40. package/src/components/toast/toast.tsx +41 -0
  41. package/src/components/toast/utils.ts +2 -0
  42. package/src/index.ts +54 -0
  43. package/src/lib/calendar.ts +204 -0
  44. package/src/lib/format/date.ts +46 -0
  45. package/src/lib/format/distance.ts +36 -0
  46. package/src/lib/format/duration.ts +28 -0
  47. package/src/lib/format/money.ts +20 -0
  48. package/src/lib/locale.tsx +36 -0
  49. package/src/lib/portal.tsx +54 -0
  50. package/src/lib/touch-target.ts +5 -1
  51. package/src/lib/use-sheet.ts +18 -0
  52. package/src/lib/use-toast.ts +17 -0
  53. package/src/variants/avatar-variants.ts +68 -15
  54. package/src/variants/badge-variants.ts +8 -0
  55. package/src/variants/calendar-variants.ts +26 -0
  56. package/src/variants/checkbox-variants.ts +5 -14
  57. package/src/variants/chip-variants.ts +38 -0
  58. package/src/variants/dialog-variants.ts +11 -0
  59. package/src/variants/index.ts +9 -0
  60. package/src/variants/overlay-variants.ts +1 -0
  61. package/src/variants/progress-variants.ts +18 -0
  62. package/src/variants/radio-group-variants.ts +48 -0
  63. package/src/variants/screen-header-variants.ts +33 -0
  64. package/src/variants/segmented-control-variants.ts +41 -0
  65. package/src/variants/select-variants.ts +48 -0
  66. package/src/variants/settings-row-variants.ts +18 -0
  67. package/src/variants/sheet-variants.ts +10 -0
  68. package/src/variants/slider-variants.ts +8 -0
  69. package/src/variants/star-rating-variants.ts +28 -0
  70. package/src/variants/switch-variants.ts +26 -4
  71. package/src/variants/tabs-variants.ts +46 -0
  72. package/src/variants/toast-variants.ts +31 -0
@@ -0,0 +1,204 @@
1
+ export type IsoDate = string;
2
+ export type YearMonth = string;
3
+
4
+ const ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
5
+ const YEAR_MONTH_PATTERN = /^\d{4}-\d{2}$/;
6
+ const MS_PER_DAY = 86_400_000;
7
+
8
+ const SUNDAY_FIRST_REGIONS: ReadonlySet<string> = new Set([
9
+ "US",
10
+ "CA",
11
+ "MX",
12
+ "JP",
13
+ "KR",
14
+ "PH",
15
+ "BR",
16
+ "IL",
17
+ "SA",
18
+ "AE",
19
+ "EG",
20
+ "AU",
21
+ ]);
22
+
23
+ export function parseIsoDate(iso: IsoDate): { year: number; month: number; day: number } {
24
+ if (!ISO_DATE_PATTERN.test(iso)) {
25
+ throw new Error(`Invalid ISO date: ${iso}`);
26
+ }
27
+ const parts = iso.split("-");
28
+ return {
29
+ year: Number(parts[0]),
30
+ month: Number(parts[1]),
31
+ day: Number(parts[2]),
32
+ };
33
+ }
34
+
35
+ export function parseYearMonth(yearMonth: YearMonth): { year: number; month: number } {
36
+ if (!YEAR_MONTH_PATTERN.test(yearMonth)) {
37
+ throw new Error(`Invalid year-month: ${yearMonth}`);
38
+ }
39
+ const parts = yearMonth.split("-");
40
+ return {
41
+ year: Number(parts[0]),
42
+ month: Number(parts[1]),
43
+ };
44
+ }
45
+
46
+ export function toIsoDate(year: number, month: number, day: number): IsoDate {
47
+ return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
48
+ }
49
+
50
+ export function toYearMonth(year: number, month: number): YearMonth {
51
+ return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}`;
52
+ }
53
+
54
+ export function yearMonthOf(iso: IsoDate): YearMonth {
55
+ const { year, month } = parseIsoDate(iso);
56
+ return toYearMonth(year, month);
57
+ }
58
+
59
+ export function daysInMonth(year: number, month: number): number {
60
+ return new Date(Date.UTC(year, month, 0)).getUTCDate();
61
+ }
62
+
63
+ export function dayOfWeek(iso: IsoDate): number {
64
+ const { year, month, day } = parseIsoDate(iso);
65
+ return new Date(Date.UTC(year, month - 1, day)).getUTCDay();
66
+ }
67
+
68
+ export function addMonths(yearMonth: YearMonth, delta: number): YearMonth {
69
+ const { year, month } = parseYearMonth(yearMonth);
70
+ const totalMonths = year * 12 + (month - 1) + delta;
71
+ const nextYear = Math.floor(totalMonths / 12);
72
+ const nextMonth = ((totalMonths % 12) + 12) % 12 + 1;
73
+ return toYearMonth(nextYear, nextMonth);
74
+ }
75
+
76
+ export function daysBetween(startIso: IsoDate, endIso: IsoDate): number {
77
+ const start = parseIsoDate(startIso);
78
+ const end = parseIsoDate(endIso);
79
+ const startUtc = Date.UTC(start.year, start.month - 1, start.day);
80
+ const endUtc = Date.UTC(end.year, end.month - 1, end.day);
81
+ return Math.round((endUtc - startUtc) / MS_PER_DAY);
82
+ }
83
+
84
+ export function firstDayOfWeek(locale: string): number {
85
+ try {
86
+ const loc = new Intl.Locale(locale);
87
+ const info =
88
+ (loc as unknown as { getWeekInfo?: () => { firstDay?: number } }).getWeekInfo?.() ??
89
+ (loc as unknown as { weekInfo?: { firstDay?: number } }).weekInfo;
90
+ if (info && typeof info.firstDay === "number") {
91
+ return info.firstDay === 7 ? 0 : info.firstDay;
92
+ }
93
+ const region = loc.maximize().region;
94
+ return region !== undefined && SUNDAY_FIRST_REGIONS.has(region) ? 0 : 1;
95
+ } catch {
96
+ return 1;
97
+ }
98
+ }
99
+
100
+ export interface MonthGridCell {
101
+ iso: IsoDate;
102
+ dayNumber: number;
103
+ isCurrentMonth: boolean;
104
+ }
105
+
106
+ export function buildMonthGrid(yearMonth: YearMonth, firstDow: number): MonthGridCell[][] {
107
+ const { year, month } = parseYearMonth(yearMonth);
108
+ const firstOfMonthDow = dayOfWeek(toIsoDate(year, month, 1));
109
+ const leading = (firstOfMonthDow - firstDow + 7) % 7;
110
+ const inThisMonth = daysInMonth(year, month);
111
+ const totalCells = Math.ceil((leading + inThisMonth) / 7) * 7;
112
+
113
+ const previousYearMonth = addMonths(yearMonth, -1);
114
+ const { year: prevY, month: prevM } = parseYearMonth(previousYearMonth);
115
+ const previousMonthDays = daysInMonth(prevY, prevM);
116
+ const nextYearMonth = addMonths(yearMonth, 1);
117
+ const { year: nextY, month: nextM } = parseYearMonth(nextYearMonth);
118
+
119
+ const cells: MonthGridCell[] = [];
120
+ for (let cellIndex = 0; cellIndex < totalCells; cellIndex++) {
121
+ const offset = cellIndex - leading;
122
+ if (offset < 0) {
123
+ const day = previousMonthDays + offset + 1;
124
+ cells.push({ iso: toIsoDate(prevY, prevM, day), dayNumber: day, isCurrentMonth: false });
125
+ } else if (offset < inThisMonth) {
126
+ const day = offset + 1;
127
+ cells.push({ iso: toIsoDate(year, month, day), dayNumber: day, isCurrentMonth: true });
128
+ } else {
129
+ const day = offset - inThisMonth + 1;
130
+ cells.push({ iso: toIsoDate(nextY, nextM, day), dayNumber: day, isCurrentMonth: false });
131
+ }
132
+ }
133
+
134
+ const weeks: MonthGridCell[][] = [];
135
+ for (let start = 0; start < cells.length; start += 7) {
136
+ weeks.push(cells.slice(start, start + 7));
137
+ }
138
+ return weeks;
139
+ }
140
+
141
+ export function isDateInRange(iso: IsoDate, minDate?: IsoDate, maxDate?: IsoDate): boolean {
142
+ if (minDate !== undefined && iso < minDate) return false;
143
+ if (maxDate !== undefined && iso > maxDate) return false;
144
+ return true;
145
+ }
146
+
147
+ export function formatMonthLabel(yearMonth: YearMonth, locale: string): string {
148
+ const { year, month } = parseYearMonth(yearMonth);
149
+ return new Intl.DateTimeFormat(locale, {
150
+ year: "numeric",
151
+ month: "long",
152
+ timeZone: "UTC",
153
+ }).format(new Date(Date.UTC(year, month - 1, 1, 12, 0, 0)));
154
+ }
155
+
156
+ const SUNDAY_ANCHOR_UTC = Date.UTC(2024, 0, 7);
157
+
158
+ export function formatWeekdayShort(dayOfWeekIndex: number, locale: string): string {
159
+ const date = new Date(SUNDAY_ANCHOR_UTC + dayOfWeekIndex * MS_PER_DAY);
160
+ return new Intl.DateTimeFormat(locale, { weekday: "short", timeZone: "UTC" }).format(date);
161
+ }
162
+
163
+ export function formatFullDate(iso: IsoDate, locale: string): string {
164
+ const { year, month, day } = parseIsoDate(iso);
165
+ return new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone: "UTC" }).format(
166
+ new Date(Date.UTC(year, month - 1, day, 12, 0, 0)),
167
+ );
168
+ }
169
+
170
+ export function isLocaleHour12(locale: string): boolean {
171
+ const parts = new Intl.DateTimeFormat(locale, {
172
+ hour: "numeric",
173
+ minute: "numeric",
174
+ timeZone: "UTC",
175
+ }).formatToParts(new Date(Date.UTC(2024, 0, 1, 13, 0, 0)));
176
+ return parts.some((part) => part.type === "dayPeriod");
177
+ }
178
+
179
+ export function formatHourLabel(hour24: number, hour12Mode: boolean, locale: string): string {
180
+ const date = new Date(Date.UTC(2024, 0, 1, hour24, 0, 0));
181
+ return new Intl.DateTimeFormat(locale, {
182
+ hour: "numeric",
183
+ hour12: hour12Mode,
184
+ timeZone: "UTC",
185
+ }).format(date);
186
+ }
187
+
188
+ export function padTwoDigits(value: number): string {
189
+ return String(value).padStart(2, "0");
190
+ }
191
+
192
+ export function formatTimeValue(hour24: number, minute: number): string {
193
+ return `${padTwoDigits(hour24)}:${padTwoDigits(minute)}`;
194
+ }
195
+
196
+ export function parseTimeValue(value: string): { hour: number; minute: number } | undefined {
197
+ const match = /^(\d{2}):(\d{2})$/.exec(value);
198
+ if (match === null) return undefined;
199
+ const hour = Number(match[1]);
200
+ const minute = Number(match[2]);
201
+ if (Number.isNaN(hour) || Number.isNaN(minute)) return undefined;
202
+ if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return undefined;
203
+ return { hour, minute };
204
+ }
@@ -0,0 +1,46 @@
1
+ export type DateStyle = "short" | "medium" | "long";
2
+
3
+ const millisecondsPerSecond = 1000;
4
+
5
+ const relativeThresholds: ReadonlyArray<{ unit: Intl.RelativeTimeFormatUnit; seconds: number }> = [
6
+ { unit: "year", seconds: 60 * 60 * 24 * 365 },
7
+ { unit: "month", seconds: 60 * 60 * 24 * 30 },
8
+ { unit: "day", seconds: 60 * 60 * 24 },
9
+ { unit: "hour", seconds: 60 * 60 },
10
+ { unit: "minute", seconds: 60 },
11
+ ];
12
+
13
+ export function formatDate(isoDateTime: string, locale: string, dateStyle: DateStyle = "medium") {
14
+ return new Intl.DateTimeFormat(locale, { dateStyle }).format(new Date(isoDateTime));
15
+ }
16
+
17
+ export function formatDateRange(
18
+ startIsoDateTime: string,
19
+ endIsoDateTime: string,
20
+ locale: string,
21
+ dateStyle: DateStyle = "medium",
22
+ ): string {
23
+ return new Intl.DateTimeFormat(locale, { dateStyle }).formatRange(
24
+ new Date(startIsoDateTime),
25
+ new Date(endIsoDateTime),
26
+ );
27
+ }
28
+
29
+ export function formatRelativeDate(
30
+ isoDateTime: string,
31
+ locale: string,
32
+ now: Date = new Date(),
33
+ ): string {
34
+ const deltaSeconds =
35
+ (new Date(isoDateTime).getTime() - now.getTime()) / millisecondsPerSecond;
36
+ const formatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
37
+ const magnitude = Math.abs(deltaSeconds);
38
+
39
+ const threshold = relativeThresholds.find(({ seconds }) => magnitude >= seconds);
40
+
41
+ if (threshold === undefined) {
42
+ return formatter.format(Math.trunc(deltaSeconds), "second");
43
+ }
44
+
45
+ return formatter.format(Math.trunc(deltaSeconds / threshold.seconds), threshold.unit);
46
+ }
@@ -0,0 +1,36 @@
1
+ export type MeasurementSystem = "metric" | "imperial";
2
+
3
+ const metersPerKilometer = 1000;
4
+ const metersPerMile = 1609.344;
5
+
6
+ function unitFormatter(
7
+ locale: string,
8
+ unit: string,
9
+ fractionDigits: number,
10
+ ): Intl.NumberFormat {
11
+ return new Intl.NumberFormat(locale, {
12
+ style: "unit",
13
+ unit,
14
+ unitDisplay: "short",
15
+ minimumFractionDigits: fractionDigits,
16
+ maximumFractionDigits: fractionDigits,
17
+ });
18
+ }
19
+
20
+ export function formatDistance(
21
+ meters: number,
22
+ locale: string,
23
+ measurementSystem: MeasurementSystem,
24
+ ): string {
25
+ const distance = Math.max(0, meters);
26
+
27
+ if (measurementSystem === "imperial") {
28
+ return unitFormatter(locale, "mile", 1).format(distance / metersPerMile);
29
+ }
30
+
31
+ if (distance < metersPerKilometer) {
32
+ return unitFormatter(locale, "meter", 0).format(distance);
33
+ }
34
+
35
+ return unitFormatter(locale, "kilometer", 1).format(distance / metersPerKilometer);
36
+ }
@@ -0,0 +1,28 @@
1
+ const minutesPerHour = 60;
2
+
3
+ function unitFormatter(locale: string, unit: "hour" | "minute"): Intl.NumberFormat {
4
+ return new Intl.NumberFormat(locale, {
5
+ style: "unit",
6
+ unit,
7
+ unitDisplay: "short",
8
+ maximumFractionDigits: 0,
9
+ });
10
+ }
11
+
12
+ export function formatDuration(minutes: number, locale: string): string {
13
+ const wholeMinutes = Math.max(0, Math.round(minutes));
14
+
15
+ if (wholeMinutes < minutesPerHour) {
16
+ return unitFormatter(locale, "minute").format(wholeMinutes);
17
+ }
18
+
19
+ const hours = Math.floor(wholeMinutes / minutesPerHour);
20
+ const remainingMinutes = wholeMinutes % minutesPerHour;
21
+ const formattedHours = unitFormatter(locale, "hour").format(hours);
22
+
23
+ if (remainingMinutes === 0) {
24
+ return formattedHours;
25
+ }
26
+
27
+ return `${formattedHours} ${unitFormatter(locale, "minute").format(remainingMinutes)}`;
28
+ }
@@ -0,0 +1,20 @@
1
+ import type { Money } from "@atlure/types";
2
+
3
+ export type RateUnit = "hour" | "night" | "walk";
4
+
5
+ const fallbackCurrencyFractionDigits = 2;
6
+
7
+ function currencyFormatter(locale: string, currency: Money["currency"]): Intl.NumberFormat {
8
+ return new Intl.NumberFormat(locale, { style: "currency", currency });
9
+ }
10
+
11
+ export function formatMoney(money: Money, locale: string): string {
12
+ const formatter = currencyFormatter(locale, money.currency);
13
+ const { maximumFractionDigits = fallbackCurrencyFractionDigits } = formatter.resolvedOptions();
14
+
15
+ return formatter.format(money.amountMinor / 10 ** maximumFractionDigits);
16
+ }
17
+
18
+ export function formatMoneyRate(money: Money, locale: string, per: RateUnit): string {
19
+ return `${formatMoney(money, locale)} per ${per}`;
20
+ }
@@ -0,0 +1,36 @@
1
+ import { createContext, useContext, useMemo, type ReactNode } from "react";
2
+
3
+ import type { MeasurementSystem } from "./format/distance";
4
+
5
+ export interface LocalePreferences {
6
+ locale: string;
7
+ measurementSystem: MeasurementSystem;
8
+ }
9
+
10
+ export const defaultLocalePreferences: LocalePreferences = {
11
+ locale: "en-IE",
12
+ measurementSystem: "metric",
13
+ };
14
+
15
+ const LocaleContext = createContext<LocalePreferences>(defaultLocalePreferences);
16
+
17
+ export function LocaleProvider({
18
+ locale,
19
+ measurementSystem = "metric",
20
+ children,
21
+ }: {
22
+ locale: string;
23
+ measurementSystem?: MeasurementSystem;
24
+ children: ReactNode;
25
+ }) {
26
+ const preferences = useMemo(() => ({ locale, measurementSystem }), [locale, measurementSystem]);
27
+
28
+ return <LocaleContext.Provider value={preferences}>{children}</LocaleContext.Provider>;
29
+ }
30
+
31
+ export function useLocale(): LocalePreferences {
32
+ return useContext(LocaleContext);
33
+ }
34
+
35
+ export type { MeasurementSystem };
36
+ export { LocaleContext };
@@ -0,0 +1,54 @@
1
+ import {
2
+ createContext,
3
+ type ReactNode,
4
+ useCallback,
5
+ useContext,
6
+ useMemo,
7
+ useState,
8
+ } from "react";
9
+
10
+ export interface PortalRegistry {
11
+ activeOverlayId: string | null;
12
+ requestSlot: (id: string) => void;
13
+ releaseSlot: (id: string) => void;
14
+ }
15
+
16
+ const PortalContext = createContext<PortalRegistry | null>(null);
17
+
18
+ const MISSING_HOST_MESSAGE =
19
+ "No <PortalHost> found. Render <PortalHost> once at the app root, above the navigator, so overlays can queue and paint above the tab bar.";
20
+
21
+ export function PortalHost({ children }: { children: ReactNode }) {
22
+ const [queuedIds, setQueuedIds] = useState<string[]>([]);
23
+
24
+ const requestSlot = useCallback((id: string) => {
25
+ setQueuedIds((currentIds) => (currentIds.includes(id) ? currentIds : [...currentIds, id]));
26
+ }, []);
27
+
28
+ const releaseSlot = useCallback((id: string) => {
29
+ setQueuedIds((currentIds) => currentIds.filter((queuedId) => queuedId !== id));
30
+ }, []);
31
+
32
+ const registry = useMemo(
33
+ () => ({ activeOverlayId: queuedIds[0] ?? null, requestSlot, releaseSlot }),
34
+ [queuedIds, requestSlot, releaseSlot],
35
+ );
36
+
37
+ return <PortalContext.Provider value={registry}>{children}</PortalContext.Provider>;
38
+ }
39
+
40
+ export function usePortalRegistry(): PortalRegistry {
41
+ const registry = useContext(PortalContext);
42
+
43
+ if (registry === null) {
44
+ throw new Error(MISSING_HOST_MESSAGE);
45
+ }
46
+
47
+ return registry;
48
+ }
49
+
50
+ export function Portal({ children }: { children: ReactNode }) {
51
+ usePortalRegistry();
52
+
53
+ return <>{children}</>;
54
+ }
@@ -4,6 +4,10 @@ export const MIN_TOUCH_TARGET_SIZE = 44;
4
4
 
5
5
  export type ControlSize = keyof typeof controlHeight;
6
6
 
7
+ export function touchTargetHitSlopForSize(sizePx: number): number {
8
+ return Math.max(0, Math.ceil((MIN_TOUCH_TARGET_SIZE - sizePx) / 2));
9
+ }
10
+
7
11
  export function touchTargetHitSlop(size: ControlSize): number {
8
- return Math.max(0, Math.ceil((MIN_TOUCH_TARGET_SIZE - controlHeight[size]) / 2));
12
+ return touchTargetHitSlopForSize(controlHeight[size]);
9
13
  }
@@ -0,0 +1,18 @@
1
+ import { useCallback, useMemo, useState } from "react";
2
+
3
+ export interface SheetControl {
4
+ isOpen: boolean;
5
+ open: () => void;
6
+ close: () => void;
7
+ toggle: () => void;
8
+ }
9
+
10
+ export function useSheet(isInitiallyOpen = false): SheetControl {
11
+ const [isOpen, setIsOpen] = useState(isInitiallyOpen);
12
+
13
+ const open = useCallback(() => setIsOpen(true), []);
14
+ const close = useCallback(() => setIsOpen(false), []);
15
+ const toggle = useCallback(() => setIsOpen((wasOpen) => !wasOpen), []);
16
+
17
+ return useMemo(() => ({ isOpen, open, close, toggle }), [isOpen, open, close, toggle]);
18
+ }
@@ -0,0 +1,17 @@
1
+ import { useContext } from "react";
2
+
3
+ import {
4
+ MISSING_TOAST_PROVIDER_MESSAGE,
5
+ ToastContext,
6
+ type ToastQueue,
7
+ } from "../components/toast/toast-context";
8
+
9
+ export function useToast(): ToastQueue {
10
+ const queue = useContext(ToastContext);
11
+
12
+ if (queue === null) {
13
+ throw new Error(MISSING_TOAST_PROVIDER_MESSAGE);
14
+ }
15
+
16
+ return queue;
17
+ }
@@ -1,40 +1,93 @@
1
1
  import { cva, type VariantProps } from "class-variance-authority";
2
2
 
3
- export const avatarVariants = cva(
4
- "items-center justify-center overflow-hidden rounded-full bg-muted",
3
+ export const avatarRootVariants = cva("relative self-start");
4
+
5
+ export const avatarVariants = cva("items-center justify-center overflow-hidden bg-muted", {
6
+ variants: {
7
+ size: {
8
+ xs: "h-6 w-6",
9
+ sm: "h-8 w-8",
10
+ md: "h-10 w-10",
11
+ lg: "h-12 w-12",
12
+ xl: "h-16 w-16",
13
+ },
14
+ shape: {
15
+ circle: "rounded-full",
16
+ rounded: "rounded-lg",
17
+ },
18
+ hasRing: {
19
+ true: "border-2 border-primary",
20
+ false: "",
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ size: "md",
25
+ shape: "circle",
26
+ hasRing: false,
27
+ },
28
+ });
29
+
30
+ export const avatarFallbackVariants = cva("font-semibold uppercase text-muted-foreground", {
31
+ variants: {
32
+ size: {
33
+ xs: "text-xs",
34
+ sm: "text-xs",
35
+ md: "text-sm",
36
+ lg: "text-base",
37
+ xl: "text-xl",
38
+ },
39
+ },
40
+ defaultVariants: {
41
+ size: "md",
42
+ },
43
+ });
44
+
45
+ export const avatarPresenceVariants = cva(
46
+ "absolute bottom-0 right-0 rounded-full border-2 border-background",
5
47
  {
6
48
  variants: {
7
49
  size: {
8
- sm: "h-8 w-8",
9
- md: "h-10 w-10",
10
- lg: "h-12 w-12",
11
- xl: "h-16 w-16",
50
+ xs: "h-2 w-2",
51
+ sm: "h-2.5 w-2.5",
52
+ md: "h-3 w-3",
53
+ lg: "h-3.5 w-3.5",
54
+ xl: "h-4 w-4",
12
55
  },
13
- hasRing: {
14
- true: "border-2 border-primary",
15
- false: "",
56
+ presence: {
57
+ online: "bg-primary",
58
+ offline: "bg-muted-foreground",
16
59
  },
17
60
  },
18
61
  defaultVariants: {
19
62
  size: "md",
20
- hasRing: false,
63
+ presence: "offline",
21
64
  },
22
65
  },
23
66
  );
24
67
 
25
- export const avatarFallbackVariants = cva("font-semibold uppercase text-muted-foreground", {
68
+ export const avatarGroupVariants = cva("flex-row items-center");
69
+
70
+ export const avatarGroupItemVariants = cva("", {
26
71
  variants: {
27
72
  size: {
28
- sm: "text-xs",
29
- md: "text-sm",
30
- lg: "text-base",
31
- xl: "text-xl",
73
+ xs: "-ml-2",
74
+ sm: "-ml-2.5",
75
+ md: "-ml-3",
76
+ lg: "-ml-4",
77
+ xl: "-ml-5",
78
+ },
79
+ isFirst: {
80
+ true: "ml-0",
81
+ false: "",
32
82
  },
33
83
  },
34
84
  defaultVariants: {
35
85
  size: "md",
86
+ isFirst: false,
36
87
  },
37
88
  });
38
89
 
39
90
  export type AvatarVariantProps = VariantProps<typeof avatarVariants>;
40
91
  export type AvatarFallbackVariantProps = VariantProps<typeof avatarFallbackVariants>;
92
+ export type AvatarPresenceVariantProps = VariantProps<typeof avatarPresenceVariants>;
93
+ export type AvatarGroupItemVariantProps = VariantProps<typeof avatarGroupItemVariants>;
@@ -4,14 +4,18 @@ export const badgeVariants = cva("flex-row items-center justify-center self-star
4
4
  variants: {
5
5
  variant: {
6
6
  primary: "bg-primary",
7
+ default: "bg-primary",
7
8
  secondary: "bg-secondary",
8
9
  outline: "border border-border/20 bg-transparent",
10
+ success: "bg-success",
11
+ warning: "bg-warning",
9
12
  destructive: "bg-destructive",
10
13
  muted: "bg-muted",
11
14
  },
12
15
  size: {
13
16
  sm: "px-sm py-xs",
14
17
  md: "px-md py-xs",
18
+ default: "px-md py-xs",
15
19
  },
16
20
  },
17
21
  defaultVariants: {
@@ -24,14 +28,18 @@ export const badgeLabelVariants = cva("font-medium", {
24
28
  variants: {
25
29
  variant: {
26
30
  primary: "text-primary-foreground",
31
+ default: "text-primary-foreground",
27
32
  secondary: "text-secondary-foreground",
28
33
  outline: "text-foreground",
34
+ success: "text-success-foreground",
35
+ warning: "text-warning-foreground",
29
36
  destructive: "text-destructive-foreground",
30
37
  muted: "text-muted-foreground",
31
38
  },
32
39
  size: {
33
40
  sm: "text-xs",
34
41
  md: "text-sm",
42
+ default: "text-sm",
35
43
  },
36
44
  },
37
45
  defaultVariants: {
@@ -0,0 +1,26 @@
1
+ export const calendarContainerClassName = "w-full px-md";
2
+ export const calendarHeaderClassName = "flex-row items-center justify-between py-md";
3
+ export const calendarMonthLabelClassName = "text-base font-medium text-foreground";
4
+ export const calendarNavButtonClassName = "px-md py-sm";
5
+ export const calendarWeekdayRowClassName = "flex-row";
6
+ export const calendarWeekdayCellClassName = "flex-1 items-center py-xs";
7
+ export const calendarWeekdayLabelClassName = "text-xs uppercase text-muted-foreground";
8
+ export const calendarWeekRowClassName = "flex-row";
9
+ export const calendarDayCellClassName =
10
+ "flex-1 aspect-square items-center justify-center rounded-full";
11
+ export const calendarDayCellSelectedClassName = "bg-primary";
12
+ export const calendarDayCellInRangeClassName = "rounded-none bg-primary/20";
13
+ export const calendarDayCellRangeStartClassName = "rounded-l-full rounded-r-none bg-primary";
14
+ export const calendarDayCellRangeEndClassName = "rounded-l-none rounded-r-full bg-primary";
15
+ export const calendarDayCellDisabledClassName = "opacity-40";
16
+ export const calendarDayLabelClassName = "text-sm text-foreground";
17
+ export const calendarDayLabelOutsideMonthClassName = "text-muted-foreground/50";
18
+ export const calendarDayLabelSelectedClassName = "text-primary-foreground";
19
+ export const calendarMarkerDotClassName = "absolute bottom-1 h-1 w-1 rounded-full bg-primary";
20
+ export const dateRangePickerContainerClassName = "w-full";
21
+ export const dateRangePickerSummaryClassName = "px-md pb-md text-sm text-foreground";
22
+ export const timePickerBodyClassName = "flex-row py-md";
23
+ export const timePickerColumnClassName = "flex-1";
24
+ export const timePickerOptionClassName = "items-center py-sm";
25
+ export const timePickerOptionSelectedClassName = "bg-primary/10";
26
+ export const timePickerOptionLabelClassName = "text-base text-foreground";