@proteos/ui 0.31.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.
- package/LICENSE +105 -0
- package/dist/chunk-IKAX76MB.js +113 -0
- package/dist/chunk-IKAX76MB.js.map +1 -0
- package/dist/chunk-MWAXK66H.cjs +119 -0
- package/dist/chunk-MWAXK66H.cjs.map +1 -0
- package/dist/index.cjs +6246 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2126 -0
- package/dist/index.d.ts +2126 -0
- package/dist/index.js +6069 -0
- package/dist/index.js.map +1 -0
- package/dist/pdf-view-3ZMMS6ML.cjs +95 -0
- package/dist/pdf-view-3ZMMS6ML.cjs.map +1 -0
- package/dist/pdf-view-BV5ZD6OC.js +93 -0
- package/dist/pdf-view-BV5ZD6OC.js.map +1 -0
- package/package.json +96 -0
- package/src/components/attachment-chip/attachment-chip.tsx +129 -0
- package/src/components/attachment-chip/index.ts +2 -0
- package/src/components/attribute-type-icon/attribute-type-icon.stories.tsx +70 -0
- package/src/components/attribute-type-icon/attribute-type-icon.tsx +125 -0
- package/src/components/attribute-type-icon/index.ts +8 -0
- package/src/components/avatar/avatar.stories.tsx +42 -0
- package/src/components/avatar/avatar.tsx +48 -0
- package/src/components/avatar/index.ts +2 -0
- package/src/components/avatar-group/avatar-group.test.tsx +38 -0
- package/src/components/avatar-group/avatar-group.tsx +60 -0
- package/src/components/avatar-group/index.ts +2 -0
- package/src/components/badge/badge.stories.tsx +62 -0
- package/src/components/badge/badge.test.tsx +36 -0
- package/src/components/badge/badge.tsx +54 -0
- package/src/components/badge/index.ts +2 -0
- package/src/components/badge-multi-select/badge-multi-select.stories.tsx +177 -0
- package/src/components/badge-multi-select/badge-multi-select.test.tsx +155 -0
- package/src/components/badge-multi-select/badge-multi-select.tsx +272 -0
- package/src/components/badge-multi-select/index.ts +2 -0
- package/src/components/breadcrumbs/breadcrumbs.stories.tsx +43 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +86 -0
- package/src/components/breadcrumbs/index.ts +2 -0
- package/src/components/button/button.stories.tsx +290 -0
- package/src/components/button/button.test.tsx +59 -0
- package/src/components/button/button.tsx +86 -0
- package/src/components/button/index.ts +2 -0
- package/src/components/card/card.stories.tsx +351 -0
- package/src/components/card/card.test.tsx +141 -0
- package/src/components/card/card.tsx +173 -0
- package/src/components/card/index.ts +13 -0
- package/src/components/checkbox/checkbox-group.tsx +52 -0
- package/src/components/checkbox/checkbox.stories.tsx +48 -0
- package/src/components/checkbox/checkbox.test.tsx +50 -0
- package/src/components/checkbox/checkbox.tsx +71 -0
- package/src/components/checkbox/index.ts +4 -0
- package/src/components/combobox/combobox.stories.tsx +97 -0
- package/src/components/combobox/combobox.test.tsx +123 -0
- package/src/components/combobox/combobox.tsx +239 -0
- package/src/components/combobox/index.ts +2 -0
- package/src/components/command/command-hint.tsx +46 -0
- package/src/components/command/command.stories.tsx +88 -0
- package/src/components/command/command.test.tsx +102 -0
- package/src/components/command/command.tsx +76 -0
- package/src/components/command/index.ts +16 -0
- package/src/components/currency-input/currency-input.tsx +192 -0
- package/src/components/currency-input/index.ts +6 -0
- package/src/components/data-table/cells.stories.tsx +120 -0
- package/src/components/data-table/cells.tsx +165 -0
- package/src/components/data-table/data-table.stories.tsx +371 -0
- package/src/components/data-table/data-table.tsx +556 -0
- package/src/components/data-table/index.ts +22 -0
- package/src/components/data-table/make-actions-column.ts +30 -0
- package/src/components/data-table/states/data-table-empty.tsx +40 -0
- package/src/components/data-table/states/data-table-error.tsx +30 -0
- package/src/components/data-table/states/data-table-skeleton.tsx +42 -0
- package/src/components/data-table/use-infinite-scroll-sentinel.ts +51 -0
- package/src/components/date-picker/calendar-internals.tsx +138 -0
- package/src/components/date-picker/date-picker.stories.tsx +78 -0
- package/src/components/date-picker/date-picker.test.tsx +71 -0
- package/src/components/date-picker/date-picker.tsx +140 -0
- package/src/components/date-picker/index.ts +2 -0
- package/src/components/date-time-picker/date-time-picker.stories.tsx +96 -0
- package/src/components/date-time-picker/date-time-picker.test.tsx +87 -0
- package/src/components/date-time-picker/date-time-picker.tsx +203 -0
- package/src/components/date-time-picker/index.ts +2 -0
- package/src/components/empty-state/empty-state.tsx +63 -0
- package/src/components/empty-state/index.ts +2 -0
- package/src/components/error-animation/error-animation.stories.tsx +50 -0
- package/src/components/error-animation/error-animation.test.tsx +22 -0
- package/src/components/error-animation/error-animation.tsx +26 -0
- package/src/components/error-animation/index.ts +2 -0
- package/src/components/field/field.tsx +142 -0
- package/src/components/field/index.ts +8 -0
- package/src/components/file-field/file-field.tsx +285 -0
- package/src/components/file-field/index.ts +2 -0
- package/src/components/file-viewer/fallback-card.tsx +68 -0
- package/src/components/file-viewer/file-viewer.stories.tsx +67 -0
- package/src/components/file-viewer/file-viewer.test.tsx +81 -0
- package/src/components/file-viewer/file-viewer.tsx +116 -0
- package/src/components/file-viewer/image-view.tsx +38 -0
- package/src/components/file-viewer/index.ts +7 -0
- package/src/components/file-viewer/pdf-view.tsx +122 -0
- package/src/components/file-viewer/video-view.tsx +16 -0
- package/src/components/image-attachment/image-attachment.stories.tsx +39 -0
- package/src/components/image-attachment/image-attachment.test.tsx +54 -0
- package/src/components/image-attachment/image-attachment.tsx +77 -0
- package/src/components/image-attachment/index.ts +3 -0
- package/src/components/image-attachment/is-previewable-image-name.ts +14 -0
- package/src/components/image-lightbox/image-lightbox.stories.tsx +38 -0
- package/src/components/image-lightbox/image-lightbox.test.tsx +72 -0
- package/src/components/image-lightbox/image-lightbox.tsx +92 -0
- package/src/components/image-lightbox/index.ts +2 -0
- package/src/components/input/index.ts +2 -0
- package/src/components/input/input.stories.tsx +71 -0
- package/src/components/input/input.test.tsx +36 -0
- package/src/components/input/input.tsx +76 -0
- package/src/components/kbd/index.ts +2 -0
- package/src/components/kbd/kbd.stories.tsx +34 -0
- package/src/components/kbd/kbd.tsx +27 -0
- package/src/components/menu/index.ts +2 -0
- package/src/components/menu/menu.tsx +121 -0
- package/src/components/modal/index.ts +10 -0
- package/src/components/modal/modal.stories.tsx +153 -0
- package/src/components/modal/modal.test.tsx +93 -0
- package/src/components/modal/modal.tsx +149 -0
- package/src/components/multi-select/index.ts +2 -0
- package/src/components/multi-select/multi-select.stories.tsx +211 -0
- package/src/components/multi-select/multi-select.test.tsx +151 -0
- package/src/components/multi-select/multi-select.tsx +339 -0
- package/src/components/nav/index.ts +7 -0
- package/src/components/nav/nav.stories.tsx +139 -0
- package/src/components/nav/nav.tsx +246 -0
- package/src/components/popover/index.ts +8 -0
- package/src/components/popover/popover.stories.tsx +92 -0
- package/src/components/popover/popover.tsx +135 -0
- package/src/components/progress-bar/index.ts +2 -0
- package/src/components/progress-bar/progress-bar.stories.tsx +75 -0
- package/src/components/progress-bar/progress-bar.test.tsx +29 -0
- package/src/components/progress-bar/progress-bar.tsx +57 -0
- package/src/components/radio-group/index.ts +2 -0
- package/src/components/radio-group/radio-group.stories.tsx +113 -0
- package/src/components/radio-group/radio-group.test.tsx +87 -0
- package/src/components/radio-group/radio-group.tsx +104 -0
- package/src/components/record-filter/attribute-picker.tsx +74 -0
- package/src/components/record-filter/chip-value.tsx +171 -0
- package/src/components/record-filter/filter-row.tsx +262 -0
- package/src/components/record-filter/filter-value-input.tsx +351 -0
- package/src/components/record-filter/group-add-controls.tsx +77 -0
- package/src/components/record-filter/group-body.tsx +173 -0
- package/src/components/record-filter/group-mutations.test.ts +104 -0
- package/src/components/record-filter/group-mutations.ts +116 -0
- package/src/components/record-filter/index.ts +58 -0
- package/src/components/record-filter/nested-group.tsx +138 -0
- package/src/components/record-filter/operator-picker.tsx +62 -0
- package/src/components/record-filter/operators.test.ts +105 -0
- package/src/components/record-filter/operators.ts +97 -0
- package/src/components/record-filter/record-filter-builder-panel.tsx +236 -0
- package/src/components/record-filter/record-filter-button.tsx +126 -0
- package/src/components/record-filter/record-filter-chips.tsx +527 -0
- package/src/components/record-filter/record-filter-editor.tsx +91 -0
- package/src/components/record-filter/record-filter.stories.tsx +196 -0
- package/src/components/record-filter/record-filter.test.tsx +437 -0
- package/src/components/record-filter/show-details-store.ts +72 -0
- package/src/components/record-filter/types.ts +150 -0
- package/src/components/segmented-control/index.ts +6 -0
- package/src/components/segmented-control/segmented-control.stories.tsx +67 -0
- package/src/components/segmented-control/segmented-control.test.tsx +47 -0
- package/src/components/segmented-control/segmented-control.tsx +105 -0
- package/src/components/select/index.ts +2 -0
- package/src/components/select/select.stories.tsx +76 -0
- package/src/components/select/select.test.tsx +56 -0
- package/src/components/select/select.tsx +159 -0
- package/src/components/separator/index.ts +2 -0
- package/src/components/separator/separator.stories.tsx +73 -0
- package/src/components/separator/separator.test.tsx +29 -0
- package/src/components/separator/separator.tsx +51 -0
- package/src/components/skeleton/index.ts +2 -0
- package/src/components/skeleton/skeleton.stories.tsx +68 -0
- package/src/components/skeleton/skeleton.test.tsx +38 -0
- package/src/components/skeleton/skeleton.tsx +43 -0
- package/src/components/success-animation/index.ts +2 -0
- package/src/components/success-animation/success-animation.stories.tsx +50 -0
- package/src/components/success-animation/success-animation.test.tsx +22 -0
- package/src/components/success-animation/success-animation.tsx +26 -0
- package/src/components/switch/index.ts +2 -0
- package/src/components/switch/switch.stories.tsx +60 -0
- package/src/components/switch/switch.test.tsx +50 -0
- package/src/components/switch/switch.tsx +49 -0
- package/src/components/table/index.ts +25 -0
- package/src/components/table/table.stories.tsx +306 -0
- package/src/components/table/table.test.tsx +115 -0
- package/src/components/table/table.tsx +543 -0
- package/src/components/tabs/index.ts +8 -0
- package/src/components/tabs/tabs.tsx +141 -0
- package/src/components/tag-input/index.ts +2 -0
- package/src/components/tag-input/tag-input.stories.tsx +73 -0
- package/src/components/tag-input/tag-input.test.tsx +80 -0
- package/src/components/tag-input/tag-input.tsx +193 -0
- package/src/components/textarea/index.ts +2 -0
- package/src/components/textarea/textarea.stories.tsx +75 -0
- package/src/components/textarea/textarea.test.tsx +41 -0
- package/src/components/textarea/textarea.tsx +86 -0
- package/src/components/time-field/index.ts +2 -0
- package/src/components/time-field/time-field.stories.tsx +76 -0
- package/src/components/time-field/time-field.test.tsx +51 -0
- package/src/components/time-field/time-field.tsx +98 -0
- package/src/components/tooltip/index.ts +2 -0
- package/src/components/tooltip/tooltip.stories.tsx +176 -0
- package/src/components/tooltip/tooltip.test.tsx +52 -0
- package/src/components/tooltip/tooltip.tsx +56 -0
- package/src/index.ts +47 -0
- package/src/lib/cn.ts +6 -0
- package/src/lib/commands/discovery-store.test.tsx +76 -0
- package/src/lib/commands/discovery-store.ts +110 -0
- package/src/lib/commands/index.ts +9 -0
- package/src/lib/commands/match-event.test.ts +99 -0
- package/src/lib/commands/match-event.ts +31 -0
- package/src/lib/commands/parse-combo.test.ts +65 -0
- package/src/lib/commands/parse-combo.ts +48 -0
- package/src/lib/commands/platform.ts +27 -0
- package/src/lib/commands/protected-combos.test.ts +18 -0
- package/src/lib/commands/protected-combos.ts +38 -0
- package/src/lib/commands/registry.test.ts +97 -0
- package/src/lib/commands/registry.ts +59 -0
- package/src/lib/commands/use-command.test.tsx +91 -0
- package/src/lib/commands/use-command.ts +32 -0
- package/src/lib/commands/use-escape-key.test.tsx +97 -0
- package/src/lib/commands/use-escape-key.ts +62 -0
- package/src/styles.css +321 -0
- package/src/test/setup.ts +9 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { render } from '@testing-library/react'
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { _resetEscapeStack, useEscapeKey } from './use-escape-key'
|
|
5
|
+
|
|
6
|
+
function Bind({
|
|
7
|
+
handler,
|
|
8
|
+
enabled,
|
|
9
|
+
}: {
|
|
10
|
+
handler: (e: KeyboardEvent) => void
|
|
11
|
+
enabled?: boolean
|
|
12
|
+
}) {
|
|
13
|
+
useEscapeKey(handler, enabled !== undefined ? { enabled } : undefined)
|
|
14
|
+
return null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function dispatchEscape(target?: EventTarget) {
|
|
18
|
+
const ev = new KeyboardEvent('keydown', {
|
|
19
|
+
bubbles: true,
|
|
20
|
+
cancelable: true,
|
|
21
|
+
key: 'Escape',
|
|
22
|
+
})
|
|
23
|
+
;(target ?? window).dispatchEvent(ev)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('useEscapeKey', () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
_resetEscapeStack()
|
|
29
|
+
})
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
_resetEscapeStack()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('fires the handler on Escape', () => {
|
|
35
|
+
const fn = vi.fn()
|
|
36
|
+
render(<Bind handler={fn} />)
|
|
37
|
+
dispatchEscape()
|
|
38
|
+
expect(fn).toHaveBeenCalledTimes(1)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('skips editable targets', () => {
|
|
42
|
+
const fn = vi.fn()
|
|
43
|
+
render(<Bind handler={fn} />)
|
|
44
|
+
const input = document.createElement('input')
|
|
45
|
+
document.body.appendChild(input)
|
|
46
|
+
dispatchEscape(input)
|
|
47
|
+
document.body.removeChild(input)
|
|
48
|
+
expect(fn).not.toHaveBeenCalled()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('respects enabled=false', () => {
|
|
52
|
+
const fn = vi.fn()
|
|
53
|
+
render(<Bind handler={fn} enabled={false} />)
|
|
54
|
+
dispatchEscape()
|
|
55
|
+
expect(fn).not.toHaveBeenCalled()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('newest mount wins; only one handler fires per event', () => {
|
|
59
|
+
const outer = vi.fn()
|
|
60
|
+
const inner = vi.fn()
|
|
61
|
+
render(
|
|
62
|
+
<>
|
|
63
|
+
<Bind handler={outer} />
|
|
64
|
+
<Bind handler={inner} />
|
|
65
|
+
</>,
|
|
66
|
+
)
|
|
67
|
+
dispatchEscape()
|
|
68
|
+
expect(inner).toHaveBeenCalledTimes(1)
|
|
69
|
+
expect(outer).not.toHaveBeenCalled()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('cleans up on unmount; the previous handler becomes top', () => {
|
|
73
|
+
const outer = vi.fn()
|
|
74
|
+
const inner = vi.fn()
|
|
75
|
+
function Outer() {
|
|
76
|
+
useEscapeKey(outer)
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
function Inner() {
|
|
80
|
+
useEscapeKey(inner)
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
83
|
+
function Wrap({ showInner }: { showInner: boolean }) {
|
|
84
|
+
return (
|
|
85
|
+
<>
|
|
86
|
+
<Outer />
|
|
87
|
+
{showInner ? <Inner /> : null}
|
|
88
|
+
</>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
const { rerender } = render(<Wrap showInner={true} />)
|
|
92
|
+
rerender(<Wrap showInner={false} />)
|
|
93
|
+
dispatchEscape()
|
|
94
|
+
expect(outer).toHaveBeenCalledTimes(1)
|
|
95
|
+
expect(inner).not.toHaveBeenCalled()
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* Bare-Escape handler. Doesn't go through the `mod+<key>` registry because
|
|
2
|
+
Escape has no modifier. Same editable-target guard. Multiple subscribers
|
|
3
|
+
stack — only the newest fires per event, matching the "newest wins"
|
|
4
|
+
semantics of the command registry so nested overlays (peek over a
|
|
5
|
+
record stack, say) close one layer at a time. */
|
|
6
|
+
|
|
7
|
+
import { useEffect, useRef } from 'react'
|
|
8
|
+
|
|
9
|
+
import { isEditableTarget } from './match-event'
|
|
10
|
+
|
|
11
|
+
export type UseEscapeKeyOptions = {
|
|
12
|
+
/** Defaults to `true`. Set to `false` to temporarily disable without unmounting. */
|
|
13
|
+
enabled?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type Entry = { handler: (event: KeyboardEvent) => void }
|
|
17
|
+
|
|
18
|
+
const stack: Entry[] = []
|
|
19
|
+
let listenerInstalled = false
|
|
20
|
+
|
|
21
|
+
function onWindowKeyDown(event: KeyboardEvent): void {
|
|
22
|
+
if (event.key !== 'Escape') return
|
|
23
|
+
if (isEditableTarget(event.target)) return
|
|
24
|
+
const top = stack[stack.length - 1]
|
|
25
|
+
if (!top) return
|
|
26
|
+
top.handler(event)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ensureListener(): void {
|
|
30
|
+
if (listenerInstalled) return
|
|
31
|
+
if (typeof window === 'undefined') return
|
|
32
|
+
window.addEventListener('keydown', onWindowKeyDown, { capture: true })
|
|
33
|
+
listenerInstalled = true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useEscapeKey(
|
|
37
|
+
handler: (event: KeyboardEvent) => void,
|
|
38
|
+
options?: UseEscapeKeyOptions,
|
|
39
|
+
): void {
|
|
40
|
+
const enabled = options?.enabled ?? true
|
|
41
|
+
const handlerRef = useRef(handler)
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
handlerRef.current = handler
|
|
44
|
+
})
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!enabled) return undefined
|
|
47
|
+
ensureListener()
|
|
48
|
+
const entry: Entry = {
|
|
49
|
+
handler: (event) => handlerRef.current(event),
|
|
50
|
+
}
|
|
51
|
+
stack.push(entry)
|
|
52
|
+
return () => {
|
|
53
|
+
const idx = stack.lastIndexOf(entry)
|
|
54
|
+
if (idx >= 0) stack.splice(idx, 1)
|
|
55
|
+
}
|
|
56
|
+
}, [enabled])
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Test-only.
|
|
60
|
+
export function _resetEscapeStack(): void {
|
|
61
|
+
stack.length = 0
|
|
62
|
+
}
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/* Google Fonts @import MUST come before `@import "tailwindcss"` — Tailwind's
|
|
2
|
+
directive expands inline into many rules, and any @import that follows them
|
|
3
|
+
violates CSS's "all @imports come first" rule. */
|
|
4
|
+
@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600&family=Inter:wght@300;400;450;500;600&display=swap");
|
|
5
|
+
|
|
6
|
+
@import "tailwindcss";
|
|
7
|
+
|
|
8
|
+
/* Make Tailwind's content scanner pick up classes used inside the lib's own
|
|
9
|
+
components when this stylesheet is consumed from another package (e.g.
|
|
10
|
+
`clients/web` imports it via `@import "@proteos/ui/styles.css"`). Without
|
|
11
|
+
this, utilities that only appear in lib source — `border-transparent`, the
|
|
12
|
+
indigo color-mix variants, `rounded-pill`, etc. — are tree-shaken from the
|
|
13
|
+
consumer's build and components render unstyled. Path is resolved relative
|
|
14
|
+
to *this* file. */
|
|
15
|
+
@source "./components/**/*.{ts,tsx}";
|
|
16
|
+
|
|
17
|
+
/* ==========================================================
|
|
18
|
+
PROTEOS · INDIGO — design tokens
|
|
19
|
+
Mirrors Proteos Design System v0.1 (spec: revision 2026.04.24).
|
|
20
|
+
Exposed to Tailwind v4 utilities via @theme, and preserved as
|
|
21
|
+
raw CSS custom properties so components can reference them.
|
|
22
|
+
========================================================== */
|
|
23
|
+
|
|
24
|
+
@theme {
|
|
25
|
+
/* Base — the page/chrome background the app shell paints. The majority
|
|
26
|
+
surface: pages, panels, headers, toolbars, tables, ghost buttons.
|
|
27
|
+
Split from --color-surface so themes can lift raised blocks off it;
|
|
28
|
+
in paper the two are identical. */
|
|
29
|
+
--color-bg: #f2f0ea;
|
|
30
|
+
|
|
31
|
+
/* Surfaces — raised blocks that sit ON the base: cards, popovers, modals,
|
|
32
|
+
inputs. surface-2/-3 are the recessed tint steps (hovers, read-only
|
|
33
|
+
boxes, chips, table-header fill), stepped darker in paper. */
|
|
34
|
+
--color-surface: #f2f0ea;
|
|
35
|
+
--color-surface-2: #e7e4db;
|
|
36
|
+
--color-surface-3: #dcd8cc;
|
|
37
|
+
|
|
38
|
+
/* Ink — indigo-tinted slate */
|
|
39
|
+
--color-ink: #1a1d2b;
|
|
40
|
+
--color-ink-2: #353a4b;
|
|
41
|
+
--color-ink-3: #686c7a;
|
|
42
|
+
--color-ink-4: #9a9ca6;
|
|
43
|
+
|
|
44
|
+
/* Hairlines. rule-strong marks interactive-control borders (inputs,
|
|
45
|
+
ghost buttons, segmented tracks) — equal to rule in paper, darker in
|
|
46
|
+
the light/dark themes so touchable chrome pops. */
|
|
47
|
+
--color-rule: #cfcbbd;
|
|
48
|
+
--color-rule-2: #dbd7c9;
|
|
49
|
+
--color-rule-strong: #cfcbbd;
|
|
50
|
+
|
|
51
|
+
/* Accent — deep indigo */
|
|
52
|
+
--color-accent: #2f3a66;
|
|
53
|
+
--color-accent-2: #4a5580;
|
|
54
|
+
--color-accent-ink: #1a2042;
|
|
55
|
+
|
|
56
|
+
/* Semantic — muted, paper-compatible */
|
|
57
|
+
--color-success: #3d5c3a;
|
|
58
|
+
--color-warning: #8a6a1f;
|
|
59
|
+
--color-danger: #7a2a2a;
|
|
60
|
+
--color-info: #2f3a66;
|
|
61
|
+
|
|
62
|
+
/* Indigo ramp — for edge cases & future components */
|
|
63
|
+
--color-indigo-50: #e8e9f0;
|
|
64
|
+
--color-indigo-100: #c9ccdc;
|
|
65
|
+
--color-indigo-200: #9aa0bf;
|
|
66
|
+
--color-indigo-300: #6b73a0;
|
|
67
|
+
--color-indigo-400: #4a5580;
|
|
68
|
+
--color-indigo-500: #2f3a66;
|
|
69
|
+
--color-indigo-600: #1a2042;
|
|
70
|
+
--color-indigo-700: #121630;
|
|
71
|
+
--color-indigo-800: #0a0c1e;
|
|
72
|
+
|
|
73
|
+
/* App tile palette — coloured tiles in AppSwitcher and the canvas
|
|
74
|
+
element-type badge. `tile-fg` is the ink-on-tile colour, slightly
|
|
75
|
+
warmer than --color-surface so it reads as "printed on coloured paper". */
|
|
76
|
+
--color-tile-indigo: #2f3a66;
|
|
77
|
+
--color-tile-moss: #5b7052;
|
|
78
|
+
--color-tile-terra: #8a5a3f;
|
|
79
|
+
--color-tile-slate: #1a1d2b;
|
|
80
|
+
--color-tile-fg: #f0efe8;
|
|
81
|
+
|
|
82
|
+
/* Type */
|
|
83
|
+
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
84
|
+
--font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
85
|
+
|
|
86
|
+
/* Radii */
|
|
87
|
+
--radius-sm: 6px;
|
|
88
|
+
--radius-md: 10px;
|
|
89
|
+
--radius-lg: 14px;
|
|
90
|
+
--radius-xl: 18px;
|
|
91
|
+
--radius-pill: 999px;
|
|
92
|
+
|
|
93
|
+
/* Shadows — warm, low-key */
|
|
94
|
+
--shadow-inset: 0 1px 0 rgba(255, 255, 255, 0.6) inset,
|
|
95
|
+
0 1px 2px rgba(26, 29, 43, 0.05);
|
|
96
|
+
--shadow-rest: 0 1px 0 rgba(255, 255, 255, 0.6) inset,
|
|
97
|
+
0 4px 10px -4px rgba(26, 29, 43, 0.1), 0 1px 2px rgba(26, 29, 43, 0.05);
|
|
98
|
+
--shadow-raised: 0 1px 0 rgba(255, 255, 255, 0.6) inset,
|
|
99
|
+
0 14px 30px -14px rgba(26, 29, 43, 0.18),
|
|
100
|
+
0 2px 4px rgba(26, 29, 43, 0.05);
|
|
101
|
+
--shadow-float: 0 1px 0 rgba(255, 255, 255, 0.6) inset,
|
|
102
|
+
0 30px 60px -30px rgba(26, 29, 43, 0.28),
|
|
103
|
+
0 2px 6px rgba(26, 29, 43, 0.06);
|
|
104
|
+
--shadow-focus: 0 0 0 4px color-mix(in oklab, var(--color-accent) 22%, transparent);
|
|
105
|
+
--shadow-focus-danger: 0 0 0 4px
|
|
106
|
+
color-mix(in oklab, var(--color-danger) 18%, transparent);
|
|
107
|
+
|
|
108
|
+
/* Motion — durations & easing. 150ms colour/border, 200ms transform,
|
|
109
|
+
280ms layout. Default ease for layout transitions is the "out-soft"
|
|
110
|
+
curve; everything else uses CSS's `ease`. Nothing bounces. */
|
|
111
|
+
--ease-out-soft: cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
112
|
+
--duration-fast: 150ms;
|
|
113
|
+
--duration-base: 200ms;
|
|
114
|
+
--duration-slow: 280ms;
|
|
115
|
+
|
|
116
|
+
/* Indeterminate progress — a 35%-wide segment that slides off the right
|
|
117
|
+
edge and re-enters from the left on a continuous loop. Paired with the
|
|
118
|
+
ProgressBar component's `w-[35%]` fill. */
|
|
119
|
+
--animate-indeterminate-progress: indeterminate-progress 1.5s ease-in-out infinite;
|
|
120
|
+
|
|
121
|
+
/* Status pop — one-shot scale+fade entrance for the large success/error
|
|
122
|
+
status icons. Soft ease, no overshoot (nothing bounces). */
|
|
123
|
+
--animate-status-pop: status-pop var(--duration-slow) var(--ease-out-soft) both;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@keyframes indeterminate-progress {
|
|
127
|
+
0% {
|
|
128
|
+
left: -35%;
|
|
129
|
+
}
|
|
130
|
+
60% {
|
|
131
|
+
left: 100%;
|
|
132
|
+
}
|
|
133
|
+
100% {
|
|
134
|
+
left: 100%;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@keyframes status-pop {
|
|
139
|
+
from {
|
|
140
|
+
opacity: 0;
|
|
141
|
+
transform: scale(0.85);
|
|
142
|
+
}
|
|
143
|
+
to {
|
|
144
|
+
opacity: 1;
|
|
145
|
+
transform: scale(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Raw variables (non-color) for direct component consumption.
|
|
150
|
+
Tailwind v4 mirrors @theme into :root for colors/radii/shadows, but we
|
|
151
|
+
also expose spacing tokens here for components that need raw var() access. */
|
|
152
|
+
:root {
|
|
153
|
+
--s-1: 4px;
|
|
154
|
+
--s-2: 8px;
|
|
155
|
+
--s-3: 12px;
|
|
156
|
+
--s-4: 16px;
|
|
157
|
+
--s-5: 20px;
|
|
158
|
+
--s-6: 24px;
|
|
159
|
+
--s-7: 32px;
|
|
160
|
+
--s-8: 40px;
|
|
161
|
+
--s-9: 56px;
|
|
162
|
+
--s-10: 72px;
|
|
163
|
+
--s-11: 96px;
|
|
164
|
+
--s-12: 128px;
|
|
165
|
+
|
|
166
|
+
/* Table zebra-stripe + row-hover strength — the % of ink mixed into the
|
|
167
|
+
page bg per row (see Table). Per-theme so each mode tunes independently:
|
|
168
|
+
near-white bases show a given delta more, so light dials down. Paper 3%
|
|
169
|
+
matches the pre-theme stripe tone. */
|
|
170
|
+
--row-stripe: 3%;
|
|
171
|
+
--row-hover: 6%;
|
|
172
|
+
|
|
173
|
+
color-scheme: light;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* ==========================================================
|
|
177
|
+
Theme modes — paper (default, tokens above) · light · dark.
|
|
178
|
+
Switched via data-theme on <html> (see clients/web lib/theme.ts).
|
|
179
|
+
Plain unlayered blocks: they beat Tailwind's @layer theme :root
|
|
180
|
+
declarations, and [data-theme] beats :root on specificity. Tailwind
|
|
181
|
+
utilities resolve var(--color-*) / var(--shadow-*) at use-time, so
|
|
182
|
+
redefining the custom properties re-skins every component.
|
|
183
|
+
========================================================== */
|
|
184
|
+
|
|
185
|
+
/* Light — white surfaces lifted off a cool-gray base. */
|
|
186
|
+
:root[data-theme='light'] {
|
|
187
|
+
--color-bg: #fbfbfc;
|
|
188
|
+
--color-surface: #ffffff;
|
|
189
|
+
--color-surface-2: #f0f0f2;
|
|
190
|
+
--color-surface-3: #e5e5e9;
|
|
191
|
+
|
|
192
|
+
--color-ink: #1a1d2b;
|
|
193
|
+
--color-ink-2: #383d4e;
|
|
194
|
+
--color-ink-3: #63677a;
|
|
195
|
+
--color-ink-4: #7e818f;
|
|
196
|
+
|
|
197
|
+
--color-rule: #d7d7dc;
|
|
198
|
+
--color-rule-2: #e6e6ea;
|
|
199
|
+
--color-rule-strong: #b0b1ba;
|
|
200
|
+
|
|
201
|
+
/* Softer stripes — the near-white base makes any delta read stronger. */
|
|
202
|
+
--row-stripe: 2%;
|
|
203
|
+
--row-hover: 4%;
|
|
204
|
+
|
|
205
|
+
/* Accent, semantics, indigo ramp, tiles — unchanged from paper. */
|
|
206
|
+
|
|
207
|
+
/* Cooler, crisper shadows; the white top-inset highlight from paper is
|
|
208
|
+
dropped — invisible on white surfaces. */
|
|
209
|
+
--shadow-inset: 0 1px 2px rgba(23, 25, 45, 0.05);
|
|
210
|
+
--shadow-rest: 0 1px 2px rgba(23, 25, 45, 0.05),
|
|
211
|
+
0 3px 10px -3px rgba(23, 25, 45, 0.07);
|
|
212
|
+
--shadow-raised: 0 2px 4px rgba(23, 25, 45, 0.05),
|
|
213
|
+
0 12px 28px -10px rgba(23, 25, 45, 0.14);
|
|
214
|
+
--shadow-float: 0 4px 10px rgba(23, 25, 45, 0.06),
|
|
215
|
+
0 28px 60px -20px rgba(23, 25, 45, 0.22);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* Dark — parallel ramp, not an inversion. Accent lifts to a lighter
|
|
219
|
+
indigo so fills and focus halos hold contrast against dark surfaces. */
|
|
220
|
+
:root[data-theme='dark'] {
|
|
221
|
+
color-scheme: dark;
|
|
222
|
+
|
|
223
|
+
--color-bg: #111318;
|
|
224
|
+
--color-surface: #191c24;
|
|
225
|
+
--color-surface-2: #222633;
|
|
226
|
+
--color-surface-3: #2c3140;
|
|
227
|
+
|
|
228
|
+
--color-ink: #e9eaf0;
|
|
229
|
+
--color-ink-2: #c5c8d4;
|
|
230
|
+
--color-ink-3: #9297a8;
|
|
231
|
+
--color-ink-4: #6d7183;
|
|
232
|
+
|
|
233
|
+
--color-rule: #32384a;
|
|
234
|
+
--color-rule-2: #272c38;
|
|
235
|
+
--color-rule-strong: #4a5166;
|
|
236
|
+
|
|
237
|
+
/* Dark rows lighten as they stripe; a touch more to stay legible. */
|
|
238
|
+
--row-stripe: 4%;
|
|
239
|
+
--row-hover: 7%;
|
|
240
|
+
|
|
241
|
+
/* accent-2 is the hover fill on accent chrome under fixed light text —
|
|
242
|
+
in dark it must darken (rest accent is already light-ish), not lighten.
|
|
243
|
+
accent-ink flips to a light indigo: it's the text color on tinted
|
|
244
|
+
surfaces and the hover fill under text-surface (dark) — both want light. */
|
|
245
|
+
--color-accent: #6b78b4;
|
|
246
|
+
--color-accent-2: #5d6aa6;
|
|
247
|
+
--color-accent-ink: #ccd3f0;
|
|
248
|
+
|
|
249
|
+
--color-success: #7fae7c;
|
|
250
|
+
--color-warning: #d0a75a;
|
|
251
|
+
--color-danger: #d08a83;
|
|
252
|
+
--color-info: #6b78b4;
|
|
253
|
+
|
|
254
|
+
/* Indigo ramp inverted so a low index stays "subtle against bg". */
|
|
255
|
+
--color-indigo-50: #232945;
|
|
256
|
+
--color-indigo-100: #2c3354;
|
|
257
|
+
--color-indigo-200: #3a4370;
|
|
258
|
+
--color-indigo-300: #4d5890;
|
|
259
|
+
--color-indigo-400: #6b78b4;
|
|
260
|
+
--color-indigo-500: #8290c8;
|
|
261
|
+
--color-indigo-600: #a5b0d9;
|
|
262
|
+
--color-indigo-700: #c7cee9;
|
|
263
|
+
--color-indigo-800: #e4e7f5;
|
|
264
|
+
|
|
265
|
+
/* Tile fills stay saturated mid-tones; tile-fg unchanged. */
|
|
266
|
+
|
|
267
|
+
--shadow-inset: 0 1px 2px rgba(0, 0, 0, 0.35);
|
|
268
|
+
--shadow-rest: 0 1px 2px rgba(0, 0, 0, 0.35),
|
|
269
|
+
0 4px 12px -4px rgba(0, 0, 0, 0.45);
|
|
270
|
+
--shadow-raised: 0 2px 4px rgba(0, 0, 0, 0.35),
|
|
271
|
+
0 14px 30px -12px rgba(0, 0, 0, 0.55);
|
|
272
|
+
--shadow-float: 0 4px 10px rgba(0, 0, 0, 0.4),
|
|
273
|
+
0 30px 60px -24px rgba(0, 0, 0, 0.65);
|
|
274
|
+
--shadow-focus: 0 0 0 4px color-mix(in oklab, var(--color-accent) 35%, transparent);
|
|
275
|
+
--shadow-focus-danger: 0 0 0 4px
|
|
276
|
+
color-mix(in oklab, var(--color-danger) 28%, transparent);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* Paper grain — faint dotted multiply overlay, identical to the design system spec.
|
|
280
|
+
Applied as a fixed decorative layer above content but below interactive chrome. */
|
|
281
|
+
.proteos-grain::before {
|
|
282
|
+
content: "";
|
|
283
|
+
position: fixed;
|
|
284
|
+
inset: 0;
|
|
285
|
+
pointer-events: none;
|
|
286
|
+
z-index: 100;
|
|
287
|
+
opacity: 0.32;
|
|
288
|
+
mix-blend-mode: multiply;
|
|
289
|
+
background-image:
|
|
290
|
+
radial-gradient(rgba(40, 38, 55, 0.035) 1px, transparent 1px),
|
|
291
|
+
radial-gradient(rgba(40, 38, 55, 0.025) 1px, transparent 1px);
|
|
292
|
+
background-size: 3px 3px, 7px 7px;
|
|
293
|
+
background-position: 0 0, 1px 2px;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* Grain is a paper-mode signature only. On white it reads as dirt; the
|
|
297
|
+
multiply dots vanish or muddy on dark. Absent attribute = paper.
|
|
298
|
+
`.proteos-grain-dots` gates inline-styled dot overlays (page-designer
|
|
299
|
+
canvas) the same way. */
|
|
300
|
+
:root[data-theme='light'] .proteos-grain::before,
|
|
301
|
+
:root[data-theme='dark'] .proteos-grain::before {
|
|
302
|
+
content: none;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
:root[data-theme='light'] .proteos-grain-dots,
|
|
306
|
+
:root[data-theme='dark'] .proteos-grain-dots {
|
|
307
|
+
display: none;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/* DEPRECATED aliases for module components built before the surface rename
|
|
311
|
+
(2026-07). Already-deployed module bundles bake `var(--color-bg-2)` /
|
|
312
|
+
`var(--color-bg-3)` strings that no longer exist after the rename; these
|
|
313
|
+
keep them resolving (and they chain through the theme overrides above,
|
|
314
|
+
since custom properties resolve at use-time). Remove once every module is
|
|
315
|
+
redeployed against the surface-* names. Note: old `var(--color-bg)` now
|
|
316
|
+
resolves to the new base (was the surface color) — near-identical in every
|
|
317
|
+
theme, so no alias is provided for it. */
|
|
318
|
+
:root {
|
|
319
|
+
--color-bg-2: var(--color-surface-2);
|
|
320
|
+
--color-bg-3: var(--color-surface-3);
|
|
321
|
+
}
|