Skip to content

Commit 89c5736

Browse files
authored
Merge pull request #1 from bizarre/inlay
BREAKING CHANGE: - feat: new component "Inlay", structured text input primitive - tests: playwright ct testing - refactor: timeslice -> chrono - chore: landing page rework
2 parents 3db0d0f + 1f6c9c9 commit 89c5736

File tree

94 files changed

+13949
-1101
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+13949
-1101
lines changed

.github/workflows/ci-tests.yml

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ on:
99
- master
1010

1111
jobs:
12-
test:
12+
unit-tests:
1313
runs-on: ubuntu-latest
1414
steps:
1515
- name: Checkout code
@@ -20,9 +20,31 @@ jobs:
2020
with:
2121
bun-version: latest
2222

23+
- name: Cache Bun dependencies
24+
uses: actions/cache@v4
25+
with:
26+
path: ~/.bun/install/cache
27+
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
28+
restore-keys: |
29+
${{ runner.os }}-bun-
30+
2331
- name: Install dependencies
2432
run: bun install
2533

34+
- name: Run unit tests
35+
run: bun run test:run
36+
37+
component-tests:
38+
runs-on: ubuntu-latest
39+
steps:
40+
- name: Checkout code
41+
uses: actions/checkout@v4
42+
43+
- name: Set up Bun
44+
uses: oven-sh/setup-bun@v2
45+
with:
46+
bun-version: latest
47+
2648
- name: Cache Bun dependencies
2749
uses: actions/cache@v4
2850
with:
@@ -31,5 +53,30 @@ jobs:
3153
restore-keys: |
3254
${{ runner.os }}-bun-
3355
34-
- name: Run tests
35-
run: bun run test:run
56+
- name: Install dependencies
57+
run: bun install
58+
59+
- name: Cache Playwright browsers
60+
uses: actions/cache@v4
61+
id: playwright-cache
62+
with:
63+
path: ~/.cache/ms-playwright
64+
key: ${{ runner.os }}-playwright-${{ hashFiles('**/bun.lock') }}
65+
66+
- name: Install Playwright browsers
67+
if: steps.playwright-cache.outputs.cache-hit != 'true'
68+
run: bun run playwright:install
69+
70+
- name: Install Playwright system dependencies
71+
run: bunx playwright install-deps
72+
73+
- name: Run component tests
74+
run: bun run test:ct
75+
76+
- name: Upload test results
77+
uses: actions/upload-artifact@v4
78+
if: ${{ !cancelled() }}
79+
with:
80+
name: playwright-report
81+
path: playwright-report/
82+
retention-days: 7

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,10 @@ yarn-error.log*
2525
*storybook.log
2626

2727
/dist
28+
29+
# Playwright
30+
node_modules/
31+
/test-results/
32+
/playwright-report/
33+
/blob-report/
34+
/playwright/.cache/

README.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Or use `npm`, `yarn`, or `pnpm`. Whatever you like.
2121

2222
## 🧩 Components
2323

24-
### `TimeSlice`
24+
### `Chrono`
2525

2626
A smart, headless time range picker that speaks human.
2727

@@ -38,27 +38,27 @@ A smart, headless time range picker that speaks human.
3838
#### 🛠 Basic Usage
3939

4040
```tsx
41-
import { TimeSlice } from '@bizarre/ui'
41+
import { Chrono } from '@bizarre/ui'
4242

4343
function MyComponent() {
4444
const handleConfirm = (range) => {
4545
console.log('Selected range:', range)
4646
}
4747

4848
return (
49-
<TimeSlice.Root onDateRangeConfirm={handleConfirm}>
50-
<TimeSlice.Input />
51-
<TimeSlice.Portal>
52-
<TimeSlice.Shortcut duration={{ minutes: 15 }}>
49+
<Chrono.Root onDateRangeConfirm={handleConfirm}>
50+
<Chrono.Input />
51+
<Chrono.Portal>
52+
<Chrono.Shortcut duration={{ minutes: 15 }}>
5353
15 minutes
54-
</TimeSlice.Shortcut>
55-
<TimeSlice.Shortcut duration={{ hours: 1 }}>1 hour</TimeSlice.Shortcut>
56-
<TimeSlice.Shortcut duration={{ days: 1 }}>1 day</TimeSlice.Shortcut>
57-
<TimeSlice.Shortcut duration={{ months: 1 }}>
54+
</Chrono.Shortcut>
55+
<Chrono.Shortcut duration={{ hours: 1 }}>1 hour</Chrono.Shortcut>
56+
<Chrono.Shortcut duration={{ days: 1 }}>1 day</Chrono.Shortcut>
57+
<Chrono.Shortcut duration={{ months: 1 }}>
5858
1 month
59-
</TimeSlice.Shortcut>
60-
</TimeSlice.Portal>
61-
</TimeSlice.Root>
59+
</Chrono.Shortcut>
60+
</Chrono.Portal>
61+
</Chrono.Root>
6262
)
6363
}
6464
```

bun.lock

Lines changed: 272 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default [
2020
rules: {
2121
'prettier/prettier': 'error',
2222
'react/prop-types': 'off',
23-
'@typescript-eslint/no-explicit-any': 'warn',
23+
'@typescript-eslint/no-explicit-any': 'error',
2424
'@typescript-eslint/explicit-module-boundary-types': 'off',
2525
'unused-imports/no-unused-imports': 'error',
2626
'unused-imports/no-unused-vars': [

landing/bun.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import React from 'react'
2+
import { Chrono } from '@lib'
3+
import type { DateRange } from '@lib/chrono'
4+
import { ChevronDown } from 'lucide-react'
5+
import {
6+
differenceInYears,
7+
differenceInMonths,
8+
differenceInWeeks,
9+
differenceInDays,
10+
differenceInHours,
11+
differenceInMinutes,
12+
differenceInSeconds
13+
} from 'date-fns'
14+
15+
const colors = {
16+
bg: '#0A0A0A',
17+
surface: '#111111',
18+
border: '#222222',
19+
text: '#FFFFFF',
20+
textMuted: '#888888',
21+
cyan: '#00F0FF'
22+
}
23+
24+
export default function ChronoExample() {
25+
const [dateRange, setDateRange] = React.useState<DateRange>({
26+
startDate: new Date(Date.now() - 1000 * 60 * 15), // 15 minutes ago
27+
endDate: new Date()
28+
})
29+
const [isOpen, setIsOpen] = React.useState(false)
30+
31+
const onDateRangeChange = (range: DateRange) => {
32+
setDateRange(range)
33+
}
34+
35+
const getDurationLabel = (start: Date, end: Date) => {
36+
const diffSeconds = differenceInSeconds(end, start)
37+
const diffMinutes = differenceInMinutes(end, start)
38+
const diffHours = differenceInHours(end, start)
39+
const diffDays = differenceInDays(end, start)
40+
const diffWeeks = differenceInWeeks(end, start)
41+
const diffMonths = differenceInMonths(end, start)
42+
const diffYears = differenceInYears(end, start)
43+
44+
if (diffYears > 0) return `${diffYears}y`
45+
if (diffMonths > 0) return `${diffMonths}mo`
46+
if (diffWeeks > 0) return `${diffWeeks}w`
47+
if (diffDays > 0) return `${diffDays}d`
48+
if (diffHours > 0) return `${diffHours}h`
49+
if (diffMinutes > 0) return `${diffMinutes}m`
50+
51+
return `${diffSeconds}s`
52+
}
53+
54+
const activeDurationLabel = React.useMemo(() => {
55+
if (!dateRange.startDate || !dateRange.endDate) return '-'
56+
return getDurationLabel(dateRange.startDate, dateRange.endDate)
57+
}, [dateRange])
58+
59+
return (
60+
<Chrono.Root
61+
onDateRangeChange={onDateRangeChange}
62+
defaultDateRange={dateRange}
63+
onOpenChange={setIsOpen}
64+
>
65+
<Chrono.Trigger asChild>
66+
<div className="flex items-center space-x-2 w-full">
67+
<div
68+
className="flex-1 flex items-center rounded-lg px-4 py-3 transition-all duration-200 cursor-pointer group"
69+
style={{
70+
border: `1px solid ${colors.border}`,
71+
backgroundColor: colors.surface
72+
}}
73+
>
74+
<div className="flex items-center space-x-3 w-full">
75+
<div
76+
className="h-6 min-w-[48px] rounded text-xs text-center leading-6 font-mono font-medium"
77+
style={{
78+
backgroundColor: `${colors.cyan}20`,
79+
color: colors.cyan
80+
}}
81+
>
82+
{activeDurationLabel}
83+
</div>
84+
<Chrono.Input
85+
className="bg-transparent text-sm w-full outline-none border-none cursor-pointer overflow-hidden truncate"
86+
style={{ color: colors.textMuted }}
87+
/>
88+
<div
89+
className={`transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
90+
style={{ color: colors.textMuted }}
91+
>
92+
<ChevronDown className="h-4 w-4" />
93+
</div>
94+
</div>
95+
</div>
96+
</div>
97+
</Chrono.Trigger>
98+
99+
<Chrono.Portal
100+
className="relative rounded-lg mt-2 p-1.5 flex flex-col gap-0.5 text-sm shadow-2xl z-50 w-full transition-all duration-100 animate-in fade-in-0 zoom-in-95"
101+
style={{
102+
backgroundColor: colors.surface,
103+
border: `1px solid ${colors.border}`
104+
}}
105+
>
106+
<div
107+
className="pb-2 px-2 mb-1"
108+
style={{ borderBottom: `1px solid ${colors.border}` }}
109+
>
110+
<div
111+
className="text-[10px] font-mono font-medium uppercase tracking-widest"
112+
style={{ color: colors.textMuted }}
113+
>
114+
Quick select
115+
</div>
116+
</div>
117+
<Chrono.Shortcut duration={{ minutes: 15 }} asChild>
118+
<div
119+
className="p-2.5 rounded-md cursor-pointer transition-colors duration-100 flex items-center justify-between"
120+
style={{ color: colors.text }}
121+
onMouseEnter={(e) =>
122+
(e.currentTarget.style.backgroundColor = `${colors.cyan}10`)
123+
}
124+
onMouseLeave={(e) =>
125+
(e.currentTarget.style.backgroundColor = 'transparent')
126+
}
127+
>
128+
<span className="text-sm">15 minutes</span>
129+
<span className="text-xs font-mono" style={{ color: colors.cyan }}>
130+
15m
131+
</span>
132+
</div>
133+
</Chrono.Shortcut>
134+
<Chrono.Shortcut duration={{ hours: 1 }} asChild>
135+
<div
136+
className="p-2.5 rounded-md cursor-pointer transition-colors duration-100 flex items-center justify-between"
137+
style={{ color: colors.text }}
138+
onMouseEnter={(e) =>
139+
(e.currentTarget.style.backgroundColor = `${colors.cyan}10`)
140+
}
141+
onMouseLeave={(e) =>
142+
(e.currentTarget.style.backgroundColor = 'transparent')
143+
}
144+
>
145+
<span className="text-sm">1 hour</span>
146+
<span className="text-xs font-mono" style={{ color: colors.cyan }}>
147+
1h
148+
</span>
149+
</div>
150+
</Chrono.Shortcut>
151+
<Chrono.Shortcut duration={{ days: 1 }} asChild>
152+
<div
153+
className="p-2.5 rounded-md cursor-pointer transition-colors duration-100 flex items-center justify-between"
154+
style={{ color: colors.text }}
155+
onMouseEnter={(e) =>
156+
(e.currentTarget.style.backgroundColor = `${colors.cyan}10`)
157+
}
158+
onMouseLeave={(e) =>
159+
(e.currentTarget.style.backgroundColor = 'transparent')
160+
}
161+
>
162+
<span className="text-sm">1 day</span>
163+
<span className="text-xs font-mono" style={{ color: colors.cyan }}>
164+
1d
165+
</span>
166+
</div>
167+
</Chrono.Shortcut>
168+
<Chrono.Shortcut duration={{ months: 1 }} asChild>
169+
<div
170+
className="p-2.5 rounded-md cursor-pointer transition-colors duration-100 flex items-center justify-between"
171+
style={{ color: colors.text }}
172+
onMouseEnter={(e) =>
173+
(e.currentTarget.style.backgroundColor = `${colors.cyan}10`)
174+
}
175+
onMouseLeave={(e) =>
176+
(e.currentTarget.style.backgroundColor = 'transparent')
177+
}
178+
>
179+
<span className="text-sm">1 month</span>
180+
<span className="text-xs font-mono" style={{ color: colors.cyan }}>
181+
1mo
182+
</span>
183+
</div>
184+
</Chrono.Shortcut>
185+
</Chrono.Portal>
186+
</Chrono.Root>
187+
)
188+
}

0 commit comments

Comments
 (0)