@podoba/react 0.0.32 → 0.0.35
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/package.json +3 -3
- package/src/components/asset-masonry-grid.examples.tsx +9 -0
- package/src/components/asset-masonry-grid.tsx +62 -0
- package/src/components/asset-selection-surface.examples.tsx +32 -0
- package/src/components/asset-selection-surface.tsx +184 -0
- package/src/components/brand-page-header.tsx +297 -67
- package/src/components/button.tsx +27 -4
- package/src/components/combobox.tsx +1 -1
- package/src/components/compact-action-button.examples.tsx +7 -0
- package/src/components/compact-action-button.tsx +21 -0
- package/src/components/compact-settings-dialog.examples.tsx +15 -0
- package/src/components/compact-settings-dialog.tsx +48 -0
- package/src/components/context-action-glyph.examples.tsx +9 -0
- package/src/components/context-action-glyph.tsx +61 -0
- package/src/components/context-menu.tsx +191 -107
- package/src/components/context-search-panel.examples.tsx +9 -0
- package/src/components/context-search-panel.tsx +50 -0
- package/src/components/csv-binding-presentation.examples.tsx +14 -0
- package/src/components/csv-binding-presentation.tsx +40 -0
- package/src/components/cta-pill.tsx +19 -4
- package/src/components/dashboard-grid.tsx +1 -1
- package/src/components/date-picker.tsx +5 -3
- package/src/components/date-selection-calendar.examples.tsx +8 -0
- package/src/components/date-selection-calendar.tsx +47 -0
- package/src/components/delivery/send-to-print-modal.tsx +351 -84
- package/src/components/dialog-action-button.examples.tsx +7 -0
- package/src/components/dialog-action-button.tsx +22 -0
- package/src/components/dialog.tsx +32 -12
- package/src/components/document-upload-panel.examples.tsx +12 -0
- package/src/components/document-upload-panel.tsx +48 -0
- package/src/components/dropdown-menu.tsx +5 -3
- package/src/components/empty-panel-action.examples.tsx +7 -0
- package/src/components/empty-panel-action.tsx +8 -0
- package/src/components/field-appearance.ts +14 -0
- package/src/components/focus-field.tsx +3 -3
- package/src/components/icons.tsx +30 -0
- package/src/components/input.examples.tsx +8 -0
- package/src/components/input.tsx +13 -5
- package/src/components/media-asset-panel.examples.tsx +11 -0
- package/src/components/media-asset-panel.tsx +58 -0
- package/src/components/media-gallery.examples.tsx +12 -0
- package/src/components/media-gallery.tsx +53 -0
- package/src/components/media-settings-dialog.examples.tsx +21 -0
- package/src/components/media-settings-dialog.tsx +83 -0
- package/src/components/preview-info-card.examples.tsx +18 -0
- package/src/components/preview-info-card.tsx +27 -0
- package/src/components/reload-icon.examples.tsx +7 -0
- package/src/components/reload-icon.tsx +6 -0
- package/src/components/request-changes-modal.examples.tsx +26 -0
- package/src/components/request-changes-modal.tsx +36 -19
- package/src/components/rich-text-editor.tsx +1 -1
- package/src/components/select.examples.tsx +17 -0
- package/src/components/select.tsx +78 -22
- package/src/components/settings-dialog-surface.examples.tsx +44 -0
- package/src/components/settings-dialog-surface.tsx +240 -0
- package/src/components/side-panel.examples.tsx +80 -0
- package/src/components/side-panel.tsx +140 -0
- package/src/components/subtle.tsx +64 -8
- package/src/components/table.examples.tsx +9 -0
- package/src/components/table.tsx +29 -12
- package/src/components/template-catalog-card.examples.tsx +25 -0
- package/src/components/template-catalog-card.tsx +178 -0
- package/src/components/text.tsx +14 -1
- package/src/components/textarea.examples.tsx +8 -0
- package/src/components/textarea.tsx +16 -6
- package/src/components/tile.tsx +16 -17
- package/src/editor/block-editor.tsx +56 -14
- package/src/index.ts +20 -0
- package/src/layout/app-shell.tsx +13 -9
- package/src/layout/topbar.tsx +9 -12
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useEffect, useState, type ReactNode } from 'react'
|
|
2
|
+
import { uic } from '../utils/uic'
|
|
3
|
+
import { Table } from './table'
|
|
4
|
+
import { Select, type SelectProps } from './select'
|
|
5
|
+
import { DialogActionButton } from './dialog-action-button'
|
|
6
|
+
|
|
7
|
+
const PreviewScroll = uic('div', { displayName: 'CsvPreviewScroll', baseClass: 'w-full min-w-0 overflow-x-auto' })
|
|
8
|
+
|
|
9
|
+
/** CSV preview has one header per original column; a mapping row precedes data. */
|
|
10
|
+
export function CsvPreviewTable({ headers, rows, mapping, label, emptyMessage, ...rest }: {
|
|
11
|
+
headers: string[]
|
|
12
|
+
rows: Readonly<Record<string, string>>[]
|
|
13
|
+
mapping?: ReactNode[]
|
|
14
|
+
label: string
|
|
15
|
+
emptyMessage: string
|
|
16
|
+
'data-testid'?: string
|
|
17
|
+
}) {
|
|
18
|
+
const [compact, setCompact] = useState(() => typeof window !== 'undefined' && window.matchMedia('(max-width: 900px)').matches)
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const media = window.matchMedia('(max-width: 900px)')
|
|
21
|
+
const update = () => setCompact(media.matches)
|
|
22
|
+
update()
|
|
23
|
+
media.addEventListener('change', update)
|
|
24
|
+
return () => media.removeEventListener('change', update)
|
|
25
|
+
}, [])
|
|
26
|
+
const data: ReactNode[][] = rows.map(row => headers.map(header => row[header] ?? ''))
|
|
27
|
+
if (mapping) data.unshift(mapping)
|
|
28
|
+
return <PreviewScroll {...rest}>
|
|
29
|
+
<Table<ReactNode[]> appearance="worksheet" className={compact ? 'min-w-168' : 'min-w-256'}
|
|
30
|
+
aria-label={label} emptyMessage={emptyMessage} data={data}
|
|
31
|
+
columns={headers.map((header, index) => ({ key: String(index), header, render: row => row[index] }))} />
|
|
32
|
+
</PreviewScroll>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function CsvMappingSelect<T extends object>(props: SelectProps<T>) {
|
|
36
|
+
return <Select {...props} appearance="filled" isLabelHidden rootClassName="w-full min-w-48" />
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Original UI Button md (16/20 medium, 12x24 padding), without global button changes. */
|
|
40
|
+
export const CsvActionButton = DialogActionButton
|
|
@@ -5,8 +5,11 @@ import type { ReactNode } from 'react'
|
|
|
5
5
|
* something" (the middle word emphasised) with an action control on the right.
|
|
6
6
|
*
|
|
7
7
|
* The copy is three fragments (`lead` · `emphasis` · `tail`) so a consumer can
|
|
8
|
-
* translate each per-locale while keeping the middle-word highlight.
|
|
9
|
-
*
|
|
8
|
+
* translate each per-locale while keeping the middle-word highlight. With
|
|
9
|
+
* `mobileHeader`, it adopts the source ExpandableCTA's 99px mobile touch surface;
|
|
10
|
+
* `BrandPageHeader` owns the safe-bottom positioning and returns it to the one-third
|
|
11
|
+
* desktop grid at the `md` breakpoint. Presentational only (hard rule #1): every
|
|
12
|
+
* string arrives via props — no i18n, no domain data.
|
|
10
13
|
*
|
|
11
14
|
* a11y NOTE: the background is the FIXED light brand-secondary (#6eddb1) — it does
|
|
12
15
|
* not flip with the theme, so every sentence fragment sits on `fg-on-brand` (stable
|
|
@@ -23,11 +26,23 @@ export interface CtaPillProps {
|
|
|
23
26
|
tail: ReactNode
|
|
24
27
|
/** Right-side action control (e.g. a Create button). */
|
|
25
28
|
children: ReactNode
|
|
29
|
+
/**
|
|
30
|
+
* Use the source mobile header density (99px minimum height and doubled
|
|
31
|
+
* horizontal content padding). Positioning stays with `BrandPageHeader`.
|
|
32
|
+
*/
|
|
33
|
+
mobileHeader?: boolean
|
|
26
34
|
}
|
|
27
35
|
|
|
28
|
-
export function CtaPill({ lead, emphasis, tail, children }: CtaPillProps) {
|
|
36
|
+
export function CtaPill({ lead, emphasis, tail, children, mobileHeader = false }: CtaPillProps) {
|
|
29
37
|
return (
|
|
30
|
-
<div
|
|
38
|
+
<div
|
|
39
|
+
className={[
|
|
40
|
+
'flex h-full w-full items-center justify-between gap-nav-x rounded-lg bg-brand-green',
|
|
41
|
+
mobileHeader
|
|
42
|
+
? 'min-h-mobile-cta px-12 py-5 shadow-mobile-cta md:min-h-16 md:py-2.5 md:pr-3 md:pl-4.5 md:shadow-none'
|
|
43
|
+
: 'min-h-16 py-2.5 pr-3 pl-4.5',
|
|
44
|
+
].join(' ')}
|
|
45
|
+
>
|
|
31
46
|
<p className="min-w-0 text-heading4 font-medium leading-5 tracking-tight text-fg-on-brand">
|
|
32
47
|
{lead} <span className="font-semibold">{emphasis}</span> {tail}
|
|
33
48
|
</p>
|
|
@@ -34,7 +34,7 @@ export type DashboardGridProps = {
|
|
|
34
34
|
|
|
35
35
|
export function DashboardGrid({ children, className }: DashboardGridProps) {
|
|
36
36
|
return (
|
|
37
|
-
<div className={['grid grid-cols-12 gap-
|
|
37
|
+
<div className={['grid grid-cols-12 gap-3', className].filter(Boolean).join(' ')}>{children}</div>
|
|
38
38
|
)
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -42,9 +42,11 @@ const Chevron = ({ dir }: { dir: 'left' | 'right' }) => (
|
|
|
42
42
|
|
|
43
43
|
const calendarCellClass =
|
|
44
44
|
'flex h-9 w-9 cursor-pointer items-center justify-center rounded-md text-small text-fg outline-none transition-colors ' +
|
|
45
|
-
|
|
45
|
+
// #25: out-of-month and unavailable days stay de-emphasised, but they are dates a
|
|
46
|
+
// user reads and (outside-month) can click — `fg-muted`, not the 2.10:1 decorative grey.
|
|
47
|
+
'data-[outside-month]:text-fg-muted data-[hovered]:bg-surface-muted ' +
|
|
46
48
|
'data-[selected]:bg-fg data-[selected]:text-fg-inverted data-[selected]:font-medium ' +
|
|
47
|
-
'data-[unavailable]:text-fg-
|
|
49
|
+
'data-[unavailable]:text-fg-muted data-[unavailable]:line-through ' +
|
|
48
50
|
'data-[disabled]:opacity-40 data-[focus-visible]:ring-2 data-[focus-visible]:ring-ring'
|
|
49
51
|
|
|
50
52
|
function CalendarBody() {
|
|
@@ -62,7 +64,7 @@ function CalendarBody() {
|
|
|
62
64
|
<CalendarGrid className="w-full border-collapse">
|
|
63
65
|
<CalendarGridHeader>
|
|
64
66
|
{(day) => (
|
|
65
|
-
<CalendarHeaderCell className="pb-1 text-micro font-medium uppercase tracking-wide text-fg-
|
|
67
|
+
<CalendarHeaderCell className="pb-1 text-micro font-medium uppercase tracking-wide text-fg-muted">
|
|
66
68
|
{day}
|
|
67
69
|
</CalendarHeaderCell>
|
|
68
70
|
)}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DateSelectionCalendar } from './date-selection-calendar'
|
|
2
|
+
const props = { monthLabel: 'September 2026', weekdays: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], previousLabel: 'Previous month', nextLabel: 'Next month', onPrevious: () => {}, onNext: () => {}, onSelect: () => {}, weeks: [[null, ...Array.from({ length: 6 }, (_, i) => ({ id: String(i + 1), label: String(i + 1), accessibleLabel: 'September ' + (i + 1) + ', 2026', selected: i === 2, today: i === 4 }))]] }
|
|
3
|
+
export const examples = {
|
|
4
|
+
default: () => <DateSelectionCalendar {...props} />,
|
|
5
|
+
emptySelection: () => <DateSelectionCalendar {...props} weeks={props.weeks.map(week => week.map(day => day ? { ...day, selected: false } : null))} />,
|
|
6
|
+
states: () => <DateSelectionCalendar {...props} isDisabled />,
|
|
7
|
+
}
|
|
8
|
+
export const meta = { category: 'Form', description: 'Month navigation and date cells with selected, today, disabled and keyboard focus states.' }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Button as AriaButton } from 'react-aria-components'
|
|
2
|
+
import { uic } from '../utils/uic'
|
|
3
|
+
|
|
4
|
+
export const CalendarDialogContent = uic('div', { displayName: 'CalendarDialogContent', baseClass: 'flex flex-col gap-8' })
|
|
5
|
+
export const CalendarDateHint = uic('p', { displayName: 'CalendarDateHint', baseClass: 'm-0 min-h-9 text-small font-normal text-fg-workflow-muted', style: { maxWidth: '48ch' } })
|
|
6
|
+
const Frame = uic('div', { displayName: 'DateSelectionCalendar', baseClass: 'mt-6 overflow-hidden border border-border bg-surface' })
|
|
7
|
+
const Header = uic('div', { displayName: 'DateSelectionCalendarHeader', baseClass: 'flex min-h-15 items-center border-b border-border px-5' })
|
|
8
|
+
const Navigation = uic(AriaButton, { displayName: 'DateSelectionCalendarNavigation', baseClass: 'mx-1.5 inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-sm bg-transparent text-2xl font-normal leading-none text-fg outline-none data-[hovered]:bg-surface-card data-[focus-visible]:ring-2 data-[focus-visible]:ring-ring' })
|
|
9
|
+
const Month = uic('div', { displayName: 'DateSelectionCalendarMonth', baseClass: 'flex-1 text-center text-small font-normal text-fg' })
|
|
10
|
+
const Week = uic('div', { displayName: 'DateSelectionCalendarWeek', baseClass: 'grid grid-cols-7 border-b border-border last:border-b-0' })
|
|
11
|
+
const Weekday = uic('div', { displayName: 'DateSelectionCalendarWeekday', baseClass: 'border-e border-border px-3 pb-2 pt-4 text-center font-mono text-small font-normal tracking-normal text-fg-workflow-muted last:border-e-0' })
|
|
12
|
+
const Day = uic(AriaButton, { displayName: 'DateSelectionCalendarDay', baseClass: 'inline-flex min-h-16 w-full items-center justify-center rounded-none border-e border-border bg-transparent text-small font-normal text-fg outline-none last:border-e-0 data-[hovered]:bg-surface-card data-[focus-visible]:ring-2 data-[focus-visible]:ring-inset data-[focus-visible]:ring-ring data-[disabled]:cursor-not-allowed' })
|
|
13
|
+
const Placeholder = uic('div', { displayName: 'DateSelectionCalendarPlaceholder', baseClass: 'min-h-16 border-e border-border last:border-e-0' })
|
|
14
|
+
|
|
15
|
+
export interface DateSelectionCell {
|
|
16
|
+
id: string
|
|
17
|
+
label: string
|
|
18
|
+
accessibleLabel: string
|
|
19
|
+
selected: boolean
|
|
20
|
+
today: boolean
|
|
21
|
+
}
|
|
22
|
+
export function DateSelectionCalendar({ monthLabel, weekdays, weeks, previousLabel, nextLabel, onPrevious, onNext, onSelect, isDisabled = false, autoFocus = false }: {
|
|
23
|
+
monthLabel: string
|
|
24
|
+
weekdays: string[]
|
|
25
|
+
weeks: Array<Array<DateSelectionCell | null>>
|
|
26
|
+
previousLabel: string
|
|
27
|
+
nextLabel: string
|
|
28
|
+
onPrevious: () => void
|
|
29
|
+
onNext: () => void
|
|
30
|
+
onSelect: (id: string) => void
|
|
31
|
+
isDisabled?: boolean
|
|
32
|
+
autoFocus?: boolean
|
|
33
|
+
}) {
|
|
34
|
+
return <Frame>
|
|
35
|
+
<Header>
|
|
36
|
+
<Navigation aria-label={previousLabel} onPress={onPrevious} isDisabled={isDisabled} autoFocus={autoFocus}>‹</Navigation>
|
|
37
|
+
<Month aria-live="polite">{monthLabel}</Month>
|
|
38
|
+
<Navigation aria-label={nextLabel} onPress={onNext} isDisabled={isDisabled}>›</Navigation>
|
|
39
|
+
</Header>
|
|
40
|
+
<Week>{weekdays.map((label, index) => <Weekday key={index}>{label}</Weekday>)}</Week>
|
|
41
|
+
{weeks.map((week, index) => <Week key={index}>{week.map((day, column) => day
|
|
42
|
+
? <Day key={day.id} aria-label={day.accessibleLabel} aria-pressed={day.selected} aria-current={day.today ? 'date' : undefined} isDisabled={isDisabled} onPress={() => onSelect(day.id)}
|
|
43
|
+
style={day.selected ? { boxShadow: 'inset 0 calc(var(--spacing) * -0.5) 0 var(--color-brand-green)' } : undefined}
|
|
44
|
+
className={day.selected ? 'bg-surface-card' : day.today ? 'bg-brand-green/14 ring-1 ring-inset ring-brand-green data-[hovered]:bg-brand-green/18' : undefined}>{day.label}</Day>
|
|
45
|
+
: <Placeholder key={`blank-${column}`} />)}</Week>)}
|
|
46
|
+
</Frame>
|
|
47
|
+
}
|