@streamoid/ui 0.6.17 → 0.6.18
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/README.md +35 -18
- package/dist/docs/AGENTS.md +321 -0
- package/dist/docs/CreditWarningBanner.md +305 -0
- package/dist/docs/InvoiceHistoryMobile.md +222 -0
- package/dist/docs/ScAccess.md +259 -0
- package/dist/docs/ScAppCard.md +244 -0
- package/dist/docs/ScAppCardForCopilot.md +230 -0
- package/dist/docs/ScAppCardV3.md +273 -0
- package/dist/docs/ScAppField.md +308 -0
- package/dist/docs/ScAppListingCard.md +271 -0
- package/dist/docs/ScAppSwitchPanel.md +286 -0
- package/dist/docs/ScAppcardLogos.md +226 -0
- package/dist/docs/ScArtifaxInvite.md +262 -0
- package/dist/docs/ScArtifaxSidebar.md +330 -0
- package/dist/docs/ScAskAgentButton.md +307 -0
- package/dist/docs/ScBadges.md +261 -0
- package/dist/docs/ScBeacon.md +244 -0
- package/dist/docs/ScBillingHistoryHeader.md +210 -0
- package/dist/docs/ScBillingHistoryTableList.md +243 -0
- package/dist/docs/ScBillingLogsTableHeader.md +212 -0
- package/dist/docs/ScBillingLogsTableList.md +251 -0
- package/dist/docs/ScBriefCard.md +255 -0
- package/dist/docs/ScButton.md +251 -0
- package/dist/docs/ScCalendar.md +268 -0
- package/dist/docs/ScCalendarDateComps.md +264 -0
- package/dist/docs/ScCatalogixInvite.md +345 -0
- package/dist/docs/ScCatalogixSidebar.md +337 -0
- package/dist/docs/ScCatalogixStoreHeader.md +246 -0
- package/dist/docs/ScCatalogixStoreTableList.md +316 -0
- package/dist/docs/ScCheckField.md +233 -0
- package/dist/docs/ScCheckbox.md +272 -0
- package/dist/docs/ScCounter.md +235 -0
- package/dist/docs/ScCreditsUsageCard.md +247 -0
- package/dist/docs/ScCreditsUsageCardMobile.md +224 -0
- package/dist/docs/ScDefaultCard.md +269 -0
- package/dist/docs/ScDp.md +245 -0
- package/dist/docs/ScDrawer.md +318 -0
- package/dist/docs/ScFieldButton.md +255 -0
- package/dist/docs/ScFileField.md +268 -0
- package/dist/docs/ScGoogleSignIn.md +250 -0
- package/dist/docs/ScGuide.md +278 -0
- package/dist/docs/ScHDivider.md +213 -0
- package/dist/docs/ScHeader.md +222 -0
- package/dist/docs/ScImageField.md +253 -0
- package/dist/docs/ScInChatList.md +277 -0
- package/dist/docs/ScInChatMessage.md +205 -0
- package/dist/docs/ScInfoPopup.md +248 -0
- package/dist/docs/ScIntialProfileCover.md +233 -0
- package/dist/docs/ScInvoiceHistoryMobile.md +187 -0
- package/dist/docs/ScLogoUnit.md +232 -0
- package/dist/docs/ScMappingCard.md +241 -0
- package/dist/docs/ScMediaApproval.md +301 -0
- package/dist/docs/ScMediaSelect.md +310 -0
- package/dist/docs/ScMenuOptions.md +308 -0
- package/dist/docs/ScMobileBottomAction.md +252 -0
- package/dist/docs/ScMobileTopNav.md +279 -0
- package/dist/docs/ScModal.md +291 -0
- package/dist/docs/ScOnlyField.md +302 -0
- package/dist/docs/ScOnlyIcon.md +213 -0
- package/dist/docs/ScPagination.md +284 -0
- package/dist/docs/ScPairtext.md +287 -0
- package/dist/docs/ScPendingAction.md +238 -0
- package/dist/docs/ScPhtogenixInvite.md +275 -0
- package/dist/docs/ScPlanCard.md +302 -0
- package/dist/docs/ScPlanComparison.md +264 -0
- package/dist/docs/ScPlanDetailsCard.md +246 -0
- package/dist/docs/ScPlanDetailsCardMobile.md +240 -0
- package/dist/docs/ScPopUpMenu.md +224 -0
- package/dist/docs/ScProfile.md +234 -0
- package/dist/docs/ScProfileImageUpdate.md +261 -0
- package/dist/docs/ScProfileOptions.md +245 -0
- package/dist/docs/ScProfilePopup.md +396 -0
- package/dist/docs/ScProfileSettingsComp.md +250 -0
- package/dist/docs/ScProfileV2Mobile.md +216 -0
- package/dist/docs/ScProgressBar.md +267 -0
- package/dist/docs/ScQuickPrompt.md +277 -0
- package/dist/docs/ScRadio.md +228 -0
- package/dist/docs/ScReferralCardMobile.md +226 -0
- package/dist/docs/ScReferralTableHeader.md +260 -0
- package/dist/docs/ScReferralTableList.md +293 -0
- package/dist/docs/ScRole.md +226 -0
- package/dist/docs/ScRoleMobile.md +199 -0
- package/dist/docs/ScSelect.md +270 -0
- package/dist/docs/ScSelection.md +256 -0
- package/dist/docs/ScSelectionList.md +272 -0
- package/dist/docs/ScSelectionPill.md +240 -0
- package/dist/docs/ScSelectionPillGroup.md +302 -0
- package/dist/docs/ScSettingsNav.md +212 -0
- package/dist/docs/ScSettingsTabComp.md +260 -0
- package/dist/docs/ScSideBarLogoUnit.md +340 -0
- package/dist/docs/ScSidebar.md +243 -0
- package/dist/docs/ScSidebarIcons.md +232 -0
- package/dist/docs/ScSidebarMenu.md +283 -0
- package/dist/docs/ScSidebarProfile.md +231 -0
- package/dist/docs/ScSidebarSwitchMenu.md +258 -0
- package/dist/docs/ScSlider.md +194 -0
- package/dist/docs/ScStoreCard.md +252 -0
- package/dist/docs/ScStrLogo.md +253 -0
- package/dist/docs/ScStreamoidWordmark.md +302 -0
- package/dist/docs/ScSubAgent.md +226 -0
- package/dist/docs/ScTabComp.md +308 -0
- package/dist/docs/ScTabField.md +258 -0
- package/dist/docs/ScTabSwitcher.md +307 -0
- package/dist/docs/ScTableHeader.md +261 -0
- package/dist/docs/ScTableList.md +301 -0
- package/dist/docs/ScTableListMobile.md +282 -0
- package/dist/docs/ScTabs.md +268 -0
- package/dist/docs/ScTaxonomyPill.md +263 -0
- package/dist/docs/ScTextArea.md +259 -0
- package/dist/docs/ScTextField.md +324 -0
- package/dist/docs/ScThinkingStepIcon.md +249 -0
- package/dist/docs/ScTodoList.md +288 -0
- package/dist/docs/ScToggleSwitch.md +229 -0
- package/dist/docs/ScUsageHistoryMobile.md +194 -0
- package/dist/docs/ScVDivider.md +215 -0
- package/dist/docs/ScValueMappingL1.md +256 -0
- package/dist/docs/ScVersion.md +251 -0
- package/dist/docs/ScWorkspace.md +233 -0
- package/dist/docs/ScWorkspaceCard.md +234 -0
- package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
- package/dist/docs/ScWorkspaceSwitchCard.md +314 -0
- package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
- package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
- package/dist/docs/StreamoidSidebar.md +403 -0
- package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
- package/dist/docs/UsageHistoryMobile.md +235 -0
- package/dist/docs/components.json +4849 -0
- package/dist/index.css +36 -36
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/package.json +3 -2
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScCalendar
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: forms
|
|
5
|
+
status: legacy
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [calendar, date, datepicker, month, day, range, date-range]
|
|
8
|
+
related: [ScCalendarDateComps, ScHDivider, ScModal]
|
|
9
|
+
do_not_confuse_with: [ScCalendarDateComps, ScCounter, ScPagination]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# ScCalendar
|
|
13
|
+
|
|
14
|
+
**A single-month day grid — and an unfinished one.** 20rem card with
|
|
15
|
+
"`{Month} {Year}` ‹ ›" in the header, a divider, then 28–42 `ScCalendarDateComps`
|
|
16
|
+
cells (whole weeks: `ceil((firstWeekday + daysInMonth) / 7) * 7`). **Its own grid CSS pins every cell to row 1 / column 1, so all the dates
|
|
17
|
+
render stacked on top of each other.** It has no host consumer, and the date-range
|
|
18
|
+
picker that actually shipped (`ExportDateModal` in `@streamoid/settings`) hand-rolls
|
|
19
|
+
this same chrome instead of using it.
|
|
20
|
+
|
|
21
|
+
> **status: legacy.** Do not reach for this in new work until Gotcha 1 is fixed in
|
|
22
|
+
> the DS. If you need a date picker today, copy the pattern from
|
|
23
|
+
> `packages/settings/src/export-date-modal.tsx`, or use `ScCalendarDateComps`
|
|
24
|
+
> directly with your own grid.
|
|
25
|
+
|
|
26
|
+
## TL;DR for agents
|
|
27
|
+
|
|
28
|
+
- **Reach for it when:** you're fixing or finishing this component. Otherwise, don't.
|
|
29
|
+
- **Don't reach for it when:** you need a working date or date-range picker →
|
|
30
|
+
`ExportDateModal` from `@streamoid/settings` (a modal with prev/next-month fill,
|
|
31
|
+
real range selection and future-date blocking), or build your own grid out of
|
|
32
|
+
`ScCalendarDateComps`.
|
|
33
|
+
- **Five things that will bite you:**
|
|
34
|
+
1. **All cells collapse into one grid cell.** Every child gets
|
|
35
|
+
`grid-column: 1 / span 1 !important; grid-row: 1 / span 1 !important`.
|
|
36
|
+
2. **There is no weekday header row.** No S M T W T F S — day 1 lands under
|
|
37
|
+
nothing.
|
|
38
|
+
3. **`month`/`year` are controlled-only.** `onMonthChange` just notifies; if you
|
|
39
|
+
don't store it, the arrows are dead.
|
|
40
|
+
4. **No range support in practice.** It only ever emits
|
|
41
|
+
`empty` / `today` / `range-start` / `default`, so `range` and `range-end`
|
|
42
|
+
never appear no matter what you pass.
|
|
43
|
+
5. **`today` beats `selectedDate`.** Selecting today shows the today underline,
|
|
44
|
+
not the selected fill.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 1. How to use it
|
|
49
|
+
|
|
50
|
+
### Import
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { ScCalendar } from "@streamoid/ui";
|
|
54
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Minimal usage
|
|
58
|
+
|
|
59
|
+
It must be driven from state or the arrows do nothing:
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
const [view, setView] = useState({ m: new Date().getMonth(), y: new Date().getFullYear() });
|
|
63
|
+
const [picked, setPicked] = useState<Date | undefined>();
|
|
64
|
+
|
|
65
|
+
<ScCalendar
|
|
66
|
+
month={view.m}
|
|
67
|
+
year={view.y}
|
|
68
|
+
selectedDate={picked}
|
|
69
|
+
onMonthChange={(m, y) => setView({ m, y })}
|
|
70
|
+
onDateSelect={setPicked}
|
|
71
|
+
/>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Props
|
|
75
|
+
|
|
76
|
+
| Prop | Type | Default | Notes |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| `month` | `number` | current month | **0-indexed** (0 = January). Passing `1` renders February. Falls back to `new Date().getMonth()` when `undefined`. |
|
|
79
|
+
| `year` | `number` | current year | Full year, e.g. `2026`. Falls back to `new Date().getFullYear()`. |
|
|
80
|
+
| `selectedDate` | `Date` | – | A single date. Rendered as `range-start` (left-rounded fill) — **not** as a range, and **not** when it is today. Ignored when it falls outside `month`/`year`. |
|
|
81
|
+
| `onDateSelect` | `(date: Date) => void` | – | Fires with a real `Date` for in-month cells only. Empty cells get no handler and no pointer cursor. |
|
|
82
|
+
| `onMonthChange` | `(month: number, year: number) => void` | – | Fires from the ‹ › icons with the **next** month/year, already wrapped across the year boundary. Does not change what the component renders. |
|
|
83
|
+
| `className` | `string` | – | Concatenated after the root class. |
|
|
84
|
+
| `...props` | `HTMLAttributes<HTMLDivElement>` | – | Spread onto the root `div`. |
|
|
85
|
+
|
|
86
|
+
No `minDate`, no `maxDate`, no `disabledDates`, no `rangeStart`/`rangeEnd`, no
|
|
87
|
+
`weekStartsOn`, no locale, no `size`.
|
|
88
|
+
|
|
89
|
+
### The cell states it can actually produce
|
|
90
|
+
|
|
91
|
+
| Condition | State passed to `ScCalendarDateComps` |
|
|
92
|
+
|---|---|
|
|
93
|
+
| index before the 1st, or after the last day | `"empty"` (blank text) |
|
|
94
|
+
| the cell is today, in the displayed month | `"today"` (1px bottom border) |
|
|
95
|
+
| the cell equals `selectedDate`, and isn't today | `"range-start"` (overlay fill, left-rounded) |
|
|
96
|
+
| everything else | `"default"` |
|
|
97
|
+
|
|
98
|
+
`"range"`, `"range-end"` and `"hover"` are declared on `ScCalendarDateComps` but
|
|
99
|
+
`ScCalendar` never emits them.
|
|
100
|
+
|
|
101
|
+
### Recipes
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
// Inside a popover/modal, wired as a single-date picker
|
|
105
|
+
<ScModal /* … */>
|
|
106
|
+
<ScCalendar
|
|
107
|
+
month={view.m}
|
|
108
|
+
year={view.y}
|
|
109
|
+
selectedDate={value}
|
|
110
|
+
onMonthChange={(m, y) => setView({ m, y })}
|
|
111
|
+
onDateSelect={(d) => { setValue(d); close(); }}
|
|
112
|
+
/>
|
|
113
|
+
</ScModal>
|
|
114
|
+
|
|
115
|
+
// Blocking future dates — the component can't; filter in your handler
|
|
116
|
+
<ScCalendar
|
|
117
|
+
month={view.m}
|
|
118
|
+
year={view.y}
|
|
119
|
+
onMonthChange={(m, y) => setView({ m, y })}
|
|
120
|
+
onDateSelect={(d) => { if (d <= new Date()) setValue(d); }}
|
|
121
|
+
/>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 2. Where to use it
|
|
127
|
+
|
|
128
|
+
Nowhere, currently. The surfaces it was drawn for are:
|
|
129
|
+
|
|
130
|
+
- **A date-range export modal** in billing / usage history — that surface exists and
|
|
131
|
+
is served by `ExportDateModal` (`@streamoid/settings`, rendered from
|
|
132
|
+
`billing-content.tsx:2123`), which reimplements this card's exact chrome
|
|
133
|
+
(same `MONTH_NAMES`, same 72px arrow row, same `surface-canvasbase` /
|
|
134
|
+
`radius-3xl` / `spacing-6xl` shell) with the features `ScCalendar` lacks.
|
|
135
|
+
- **A `ScTextField`-adjacent date popover.** Note there is **no** `ScDateField` in
|
|
136
|
+
this library — nothing wraps a calendar in a field, so the trigger, the popover
|
|
137
|
+
and the formatting are all yours.
|
|
138
|
+
|
|
139
|
+
It composes `ScHDivider` and `ScCalendarDateComps` internally.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 3. When to use it
|
|
144
|
+
|
|
145
|
+
### Use it when
|
|
146
|
+
|
|
147
|
+
- You are repairing the DS. Otherwise: not yet.
|
|
148
|
+
|
|
149
|
+
### Don't use it — reach for this instead
|
|
150
|
+
|
|
151
|
+
| Situation | Use instead |
|
|
152
|
+
|---|---|
|
|
153
|
+
| A working **date-range** picker in a modal (billing export, usage history) | `ExportDateModal` from `@streamoid/settings` |
|
|
154
|
+
| A custom grid where you need real range/hover states, or 6-row months, or a weekday header | `ScCalendarDateComps` + your own CSS grid |
|
|
155
|
+
| A text input the user types a date into | `ScTextField` (there is no `ScDateField`) |
|
|
156
|
+
| Stepping a number up/down | `ScCounter` |
|
|
157
|
+
| Paging through a list | `ScPagination` |
|
|
158
|
+
|
|
159
|
+
### Don't confuse with
|
|
160
|
+
|
|
161
|
+
| You may actually want | Not this |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `ScCalendarDateComps` — one day cell; the only part of this pair that behaves | `ScCalendar` is the (broken) grid around it |
|
|
164
|
+
| `ExportDateModal` (`@streamoid/settings`) — the shipped range picker | Different package, not a `@streamoid/ui` export |
|
|
165
|
+
| `ScCounter` / `ScPagination` — also have ‹ › arrow pairs | Nothing to do with dates |
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 4. Why to use it
|
|
170
|
+
|
|
171
|
+
Honestly: for the maths, once the CSS is fixed. What it gets right today —
|
|
172
|
+
|
|
173
|
+
- **Correct month arithmetic.** Leading-blank count from `getDay()`, day count from
|
|
174
|
+
the `new Date(y, m + 1, 0)` trick, total cells rounded up to whole weeks, and
|
|
175
|
+
year-boundary wrapping in both arrows. That's the part people get wrong.
|
|
176
|
+
- **Real `Date` objects out.** `onDateSelect` hands you a constructed
|
|
177
|
+
`new Date(year, month, day)`, not a string you have to parse.
|
|
178
|
+
- **Tokenised shell** — `surface-canvasbase`, `radius-3xl`, `spacing-6xl`, and a
|
|
179
|
+
`ScHDivider`, so the card matches every other panel in both themes.
|
|
180
|
+
|
|
181
|
+
What you'd have to add to ship it: the grid fix, a weekday header, a 6th row, real
|
|
182
|
+
range state, min/max dates, and keyboard support.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Gotchas
|
|
187
|
+
|
|
188
|
+
**1. Every date renders in the same grid cell.** `ScCalendar.module.css` defines 35
|
|
189
|
+
positioning classes (`.scCalendarDateCompsInstance` … `Instance35`), but the render
|
|
190
|
+
loop passes **only the first one** to every child:
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
// ScCalendar.tsx:154 — the same class on all 28–42 cells
|
|
194
|
+
<ScCalendarDateComps key={i} date={dayNumber || ""} state={state}
|
|
195
|
+
className={styles.scCalendarDateCompsInstance} … />
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
and that class is `grid-column: 1 / span 1 !important; grid-row: 1 / span 1 !important`.
|
|
199
|
+
Result: the whole month stacks in the top-left square. The fix is to drop the
|
|
200
|
+
per-instance classes and let the grid auto-place (`.calendarGrid` is already
|
|
201
|
+
`grid-template-columns: repeat(7, minmax(0,1fr))`) — but that is a DS change, not
|
|
202
|
+
something a call site can work around, because the rules are `!important`.
|
|
203
|
+
|
|
204
|
+
**2. No weekday header.** The grid starts at the first date cell. Columns are Sunday
|
|
205
|
+
-first (`getDay()` where 0 = Sunday) and there is no `weekStartsOn` prop, so a
|
|
206
|
+
Monday-first locale needs a different component.
|
|
207
|
+
|
|
208
|
+
**3. `grid-template-rows: repeat(5, …)` only declares 5 rows,** but a 31-day month
|
|
209
|
+
starting on a Saturday needs 6. Those cells fall into an implicit auto row.
|
|
210
|
+
|
|
211
|
+
**4. It is controlled-only.**
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
// WRONG — arrows fire onMonthChange but the calendar never moves
|
|
215
|
+
<ScCalendar onMonthChange={(m, y) => console.log(m, y)} />
|
|
216
|
+
|
|
217
|
+
// RIGHT — you own the view
|
|
218
|
+
<ScCalendar month={view.m} year={view.y} onMonthChange={(m, y) => setView({ m, y })} />
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**5. `month` is 0-indexed.** `month={1}` is February. `month={12}` is not December —
|
|
222
|
+
`MONTH_NAMES[12]` is `undefined` and the header renders "undefined 2026" while
|
|
223
|
+
`new Date(y, 12, 1)` silently rolls into next January.
|
|
224
|
+
|
|
225
|
+
**6. `today` outranks `selectedDate`.** The today check runs first and returns early,
|
|
226
|
+
so selecting today shows the underline, never the fill. Two different dates can
|
|
227
|
+
never both be highlighted.
|
|
228
|
+
|
|
229
|
+
**7. `selectedDate` gives you a left-rounded "range-start" pill, not a symmetric
|
|
230
|
+
selection.** It looks like the start of a range that isn't there.
|
|
231
|
+
|
|
232
|
+
**8. The ‹ › arrows are raw SVGs with `onClick`.** No `<button>`, no `tabIndex`, no
|
|
233
|
+
`aria-label`, and no way to disable the next arrow at the current month (compare
|
|
234
|
+
`ExportDateModal`, which greys it out). Cells are `div`s too — the whole calendar is
|
|
235
|
+
mouse-only and unreachable by keyboard.
|
|
236
|
+
|
|
237
|
+
**9. Nothing can be disabled.** No `minDate`/`maxDate`/`disabledDates`. Every
|
|
238
|
+
in-month cell is clickable; filter inside `onDateSelect`.
|
|
239
|
+
|
|
240
|
+
**10. `className` is concatenated unguarded,** so omitting it puts the literal
|
|
241
|
+
`undefined` in the root class list. Don't assert on exact class strings.
|
|
242
|
+
|
|
243
|
+
**11. Fixed `width: 20rem`.** Not responsive, no `size` prop; override via
|
|
244
|
+
`className` if you must.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## In the wild
|
|
249
|
+
|
|
250
|
+
_No host render site found — used by the agent runtime / composed internally._
|
|
251
|
+
|
|
252
|
+
Neither is true here, so to be precise: **no consumer anywhere.** No host dashboard
|
|
253
|
+
renders it, and it is not an agent-runtime component. Its only render is the DS
|
|
254
|
+
gallery, which resolves it through a name→component map
|
|
255
|
+
(`apps/docs/utils/componentMap.tsx:10`, with its prop metadata at
|
|
256
|
+
`apps/docs/utils/componentData.ts:307`). The surface it belongs to —
|
|
257
|
+
the billing/usage **date-range export modal** — is served by a hand-rolled clone at
|
|
258
|
+
`packages/settings/src/export-date-modal.tsx`, which is the strongest available
|
|
259
|
+
signal that this component was never usable as shipped.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Related
|
|
264
|
+
|
|
265
|
+
- `ScCalendarDateComps` — the single day cell; the usable half of this pair.
|
|
266
|
+
- `ExportDateModal` (`@streamoid/settings`) — the date-range picker that actually ships.
|
|
267
|
+
- `ScHDivider` — the header rule it composes.
|
|
268
|
+
- `ScModal` / `ScPopUpMenu` — what you'd put a calendar inside; neither knows about dates.
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScCalendarDateComps
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: forms
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [calendar, date, day, cell, date-cell, range, today]
|
|
8
|
+
related: [ScCalendar, ScTaxonomyPill, ScBadges]
|
|
9
|
+
do_not_confuse_with: [ScCalendar, ScBadges, ScCounter]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# ScCalendarDateComps
|
|
13
|
+
|
|
14
|
+
**One day cell in a calendar grid.** A 2.5rem-wide box with a centred day number and
|
|
15
|
+
seven skins: plain, hovered, today (thin bottom rule), range-start (left-rounded
|
|
16
|
+
fill), range-end (right-rounded fill), mid-range (flat fill), and empty. It is a
|
|
17
|
+
pure leaf — no date logic, no `Date`, just a string and a state.
|
|
18
|
+
|
|
19
|
+
The awkward plural in the name is a Figma export artefact; it renders exactly **one**
|
|
20
|
+
cell.
|
|
21
|
+
|
|
22
|
+
## TL;DR for agents
|
|
23
|
+
|
|
24
|
+
- **Reach for it when:** you are building your own calendar grid and want the DS's
|
|
25
|
+
day-cell skins — including the half-rounded range ends.
|
|
26
|
+
- **Don't reach for it when:** you want a whole month laid out for you. `ScCalendar`
|
|
27
|
+
is the wrapper, but its grid CSS is broken (see its README), so building the grid
|
|
28
|
+
yourself is currently the *recommended* path.
|
|
29
|
+
- **Four things that will bite you:**
|
|
30
|
+
1. `date` defaults to **`"31"`**. ⚠️ Forget it and every cell says 31.
|
|
31
|
+
2. `state="empty"` **does not blank the number** — it only un-tokenises the text
|
|
32
|
+
colour. Pass `date=""` as well.
|
|
33
|
+
3. **`:hover` fires on every cell, empty ones included.** There's no `disabled`.
|
|
34
|
+
4. It's a bare `div`. No `role`, no `tabIndex`, no keyboard. `onClick` works only
|
|
35
|
+
because DOM props are spread.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 1. How to use it
|
|
40
|
+
|
|
41
|
+
### Import
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { ScCalendarDateComps } from "@streamoid/ui";
|
|
45
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Minimal usage
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
<ScCalendarDateComps date="14" state="today" />
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Props
|
|
55
|
+
|
|
56
|
+
| Prop | Type | Default | Notes |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| `date` | `string` | `"31"` | ⚠️ Has a real default. A **string**, not a number — `String(day)`. Rendered with a trailing space in the text node. |
|
|
59
|
+
| `state` | `"default"` \| `"hover"` \| `"today"` \| `"range-start"` \| `"range-end"` \| `"range"` \| `"empty"` | `"default"` | Skin only. See the table below. |
|
|
60
|
+
| `className` | `string` | – | Concatenated between the root class and the state class. |
|
|
61
|
+
| `...props` | `HTMLAttributes<HTMLDivElement>` | – | Spread onto the root `div` — `onClick`, `style`, `role`, `tabIndex`, `aria-*`, `data-*`. This is how you make it interactive. |
|
|
62
|
+
|
|
63
|
+
### What each state paints
|
|
64
|
+
|
|
65
|
+
| `state` | Visual | Note |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `default` | day number in `text-and-icons-primary`, no fill | **No CSS class is emitted for this state** — there is no `.state-default` rule, so you can't target it |
|
|
68
|
+
| `hover` | `fill-neutral-neutralplus` background | Force-renders the hover skin (Figma parity/screenshots) — real `:hover` already does this |
|
|
69
|
+
| `today` | 1px bottom border in `text-and-icons-tertiary` | Underline only, no fill |
|
|
70
|
+
| `range-start` | `surface-overlay` fill, radius on the **left** corners only | Designed to butt up against the next cell |
|
|
71
|
+
| `range-end` | `surface-overlay` fill, radius on the **right** corners only | |
|
|
72
|
+
| `range` | `fill-neutral-neutraltohover` fill, square corners | The days between |
|
|
73
|
+
| `empty` | `.date { color: unset }` — the number inherits whatever colour the ancestor has | Does **not** hide or dim the number |
|
|
74
|
+
|
|
75
|
+
### Recipes
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// Your own month grid — 7 columns, no gap, so range fills join up
|
|
79
|
+
<div style={{ display: "grid", gridTemplateColumns: "repeat(7, minmax(0,1fr))", gap: 0 }}>
|
|
80
|
+
{["S","M","T","W","T","F","S"].map((d, i) => (
|
|
81
|
+
<div key={i} style={{ textAlign: "center", fontSize: 12,
|
|
82
|
+
color: "var(--alias-text-and-icons-tertiary)" }}>{d}</div>
|
|
83
|
+
))}
|
|
84
|
+
{cells.map((c) => (
|
|
85
|
+
<ScCalendarDateComps
|
|
86
|
+
key={c.key}
|
|
87
|
+
date={c.inMonth ? String(c.day) : ""}
|
|
88
|
+
state={stateFor(c)}
|
|
89
|
+
onClick={c.inMonth ? () => pick(c.date) : undefined}
|
|
90
|
+
style={{ cursor: c.inMonth ? "pointer" : "default", width: "unset" }}
|
|
91
|
+
/>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
// A real range: start / middles / end
|
|
96
|
+
const stateFor = (c) => {
|
|
97
|
+
if (!c.inMonth) return "empty";
|
|
98
|
+
if (same(c.date, start)) return "range-start";
|
|
99
|
+
if (same(c.date, end)) return "range-end";
|
|
100
|
+
if (start && end && c.date > start && c.date < end) return "range";
|
|
101
|
+
if (isToday(c.date)) return "today";
|
|
102
|
+
return "default";
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Make it accessible — the component won't do it for you
|
|
106
|
+
<ScCalendarDateComps
|
|
107
|
+
date="14"
|
|
108
|
+
state="range-start"
|
|
109
|
+
role="button"
|
|
110
|
+
tabIndex={0}
|
|
111
|
+
aria-label="14 March 2026"
|
|
112
|
+
aria-pressed
|
|
113
|
+
onClick={pick}
|
|
114
|
+
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") pick(); }}
|
|
115
|
+
/>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 2. Where to use it
|
|
121
|
+
|
|
122
|
+
- **Inside a calendar grid you control** — 7 columns, `gap: 0`, `width: unset` on the
|
|
123
|
+
cell so it stretches to the column.
|
|
124
|
+
- It is what `ScCalendar` renders internally (28–42 of them — whole weeks only).
|
|
125
|
+
`ScCalendar` also passes `onClick`/`style` through this component's DOM spread,
|
|
126
|
+
which is the only reason its cells are clickable at all.
|
|
127
|
+
|
|
128
|
+
No host dashboard renders it directly.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 3. When to use it
|
|
133
|
+
|
|
134
|
+
### Use it when
|
|
135
|
+
|
|
136
|
+
- You are assembling a month grid and want the DS day-cell tokens (fills, the today
|
|
137
|
+
rule, the half-rounded range caps) rather than inventing them.
|
|
138
|
+
- You need states `ScCalendar` can't produce — a genuine `range` / `range-end`, or a
|
|
139
|
+
forced `hover` for a screenshot.
|
|
140
|
+
|
|
141
|
+
### Don't use it — reach for this instead
|
|
142
|
+
|
|
143
|
+
| Situation | Use instead |
|
|
144
|
+
|---|---|
|
|
145
|
+
| A ready-made month with month arithmetic and ‹ › navigation | `ScCalendar` — but read its Gotcha 1 first; its grid CSS stacks all cells |
|
|
146
|
+
| A working date-**range** picker, today | `ExportDateModal` from `@streamoid/settings` |
|
|
147
|
+
| A small non-date chip / count | `ScBadges` |
|
|
148
|
+
| A tree node chip with expand/collapse | `ScTaxonomyPill` |
|
|
149
|
+
| Numeric stepping | `ScCounter` |
|
|
150
|
+
|
|
151
|
+
### Don't confuse with
|
|
152
|
+
|
|
153
|
+
| You may actually want | Not this |
|
|
154
|
+
|---|---|
|
|
155
|
+
| `ScCalendar` — the month card (header, divider, grid) | `ScCalendarDateComps` is one square inside it |
|
|
156
|
+
| `ScBadges` — status/count chip | Similar footprint, different vocabulary and tokens |
|
|
157
|
+
| The `range`/`range-end` states as produced by `ScCalendar` | `ScCalendar` never emits them; only you can |
|
|
158
|
+
|
|
159
|
+
Naming: it's `ScCalendarDateComps` — plural "Comps", singular cell, and it lives in
|
|
160
|
+
the folder `SC-Calendar date comps` (spaces in the path).
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 4. Why to use it
|
|
165
|
+
|
|
166
|
+
- **The range caps are the hard part and they're already right.** `range-start` is
|
|
167
|
+
rounded left-only, `range-end` right-only, `range` square — so a selected span
|
|
168
|
+
reads as one continuous pill *provided your grid has `gap: 0`*. Getting that with
|
|
169
|
+
hand-rolled CSS usually produces gaps or double-rounded ends.
|
|
170
|
+
- **Token-correct in both themes.** `surface-overlay` for the range caps,
|
|
171
|
+
`fill-neutral-neutraltohover` for the middle, `text-and-icons-tertiary` for the
|
|
172
|
+
today rule — no hardcoded greys to fix in light mode.
|
|
173
|
+
- **Fully transparent.** It spreads every DOM prop, so you keep complete control of
|
|
174
|
+
semantics, keyboard handling and layout while inheriting only the skin.
|
|
175
|
+
- **One place to restyle.** Change the day-cell look once and every calendar in the
|
|
176
|
+
product follows.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Gotchas
|
|
181
|
+
|
|
182
|
+
**1. `date` defaults to `"31"`.**
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
// WRONG — a grid full of 31s
|
|
186
|
+
{cells.map((c) => <ScCalendarDateComps key={c.key} state="default" />)}
|
|
187
|
+
|
|
188
|
+
// RIGHT
|
|
189
|
+
{cells.map((c) => <ScCalendarDateComps key={c.key} date={String(c.day)} />)}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**2. `state="empty"` still shows the number.** All the `empty` rule does is
|
|
193
|
+
`.date { color: unset }`, which makes the text *inherit* the ancestor colour instead
|
|
194
|
+
of the primary token — it does not hide, dim or disable anything. Blank the text
|
|
195
|
+
yourself:
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
// WRONG — the padding-day number is still visible, just in the wrong colour
|
|
199
|
+
<ScCalendarDateComps date="29" state="empty" />
|
|
200
|
+
|
|
201
|
+
// RIGHT
|
|
202
|
+
<ScCalendarDateComps date="" state="empty" />
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**3. Hover paints on empty cells too.** `.scCalendarDateComps:hover` is unqualified,
|
|
206
|
+
so blank padding cells highlight under the cursor. Suppress it with your own rule if
|
|
207
|
+
that matters — there is no `disabled` state.
|
|
208
|
+
|
|
209
|
+
**4. No interactivity of its own.** Root is a plain `div`: no `role`, no `tabIndex`,
|
|
210
|
+
no `onKeyDown`, no `aria-selected`. Everything you'd need for an accessible date grid
|
|
211
|
+
you pass in yourself through the spread.
|
|
212
|
+
|
|
213
|
+
**5. `state="default"` emits `undefined` into the class list.** `styles["state-default"]`
|
|
214
|
+
doesn't exist (there is no `.state-default` rule), and `className` is concatenated
|
|
215
|
+
unguarded, so a bare cell renders
|
|
216
|
+
`class="…scCalendarDateComps undefined undefined"`. Cosmetic — but never assert on
|
|
217
|
+
exact class strings, and don't try to style default cells via a state class.
|
|
218
|
+
|
|
219
|
+
**6. Fixed `width: 2.5rem`.** In a `1fr` grid column you almost always want
|
|
220
|
+
`width: unset` (which is exactly what `ScCalendar`'s per-cell classes force). Pass it
|
|
221
|
+
via `style` or `className`.
|
|
222
|
+
|
|
223
|
+
**7. `date` is a string, so you own formatting.** `date={5}` is a type error;
|
|
224
|
+
`String(day)` — and note the component renders `{date} ` with a trailing space, so
|
|
225
|
+
`textContent` in tests is `"5 "`, not `"5"`.
|
|
226
|
+
|
|
227
|
+
**8. `state="hover"` is for design parity, not interaction.** Never wire it to your
|
|
228
|
+
own mouse handlers; `:hover` already works.
|
|
229
|
+
|
|
230
|
+
**9. Range fills break if your grid has a gap.** The half-rounded caps assume
|
|
231
|
+
adjacent cells touch. `ScCalendar`'s own grid uses `gap: 0rem` for this reason.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## In the wild
|
|
236
|
+
|
|
237
|
+
_No host render site found — used by the agent runtime / composed internally._
|
|
238
|
+
|
|
239
|
+
Composed internally: `ScCalendar.tsx:150` renders one per grid cell —
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
// npm-components packages/ui/src/SC-Calendar/ScCalendar.tsx:150
|
|
243
|
+
<ScCalendarDateComps
|
|
244
|
+
key={i}
|
|
245
|
+
date={dayNumber || ""}
|
|
246
|
+
state={state}
|
|
247
|
+
className={styles.scCalendarDateCompsInstance}
|
|
248
|
+
onClick={cellDate && state !== "empty" ? () => onDateSelect?.(cellDate) : undefined}
|
|
249
|
+
style={cellDate && state !== "empty" ? { cursor: "pointer" } : undefined}
|
|
250
|
+
/>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
If it gets a direct consumer it will most likely be a **billing/usage date-range
|
|
254
|
+
export** popover — the surface currently served by the hand-rolled grid in
|
|
255
|
+
`packages/settings/src/export-date-modal.tsx`, which would be the obvious first
|
|
256
|
+
place to swap this cell in.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Related
|
|
261
|
+
|
|
262
|
+
- `ScCalendar` — the month wrapper; read its Gotcha 1 before using it.
|
|
263
|
+
- `ExportDateModal` (`@streamoid/settings`) — the date-range picker that ships today.
|
|
264
|
+
- `ScBadges` / `ScTaxonomyPill` — other small chips, unrelated vocabulary.
|