@linhey/rhine-design 0.2.0 → 0.3.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/CHANGELOG.md +6 -0
- package/CONTRIBUTING.md +6 -0
- package/README.md +19 -14
- package/components/Actions.tsx +4 -4
- package/components/COMMON_COMPONENTS.md +1 -1
- package/components/Data.tsx +8 -8
- package/components/Forms.tsx +11 -11
- package/components/Layout.tsx +4 -4
- package/components/Logo.tsx +7 -7
- package/components/MOTION.md +1 -1
- package/components/Overlays.tsx +9 -9
- package/components/README.md +6 -6
- package/components/Select.tsx +5 -5
- package/components/TYPOGRAPHY.md +1 -1
- package/components/Toast.tsx +2 -2
- package/components/Toolbar.tsx +3 -3
- package/components/Typography.tsx +1 -1
- package/components/common.css +69 -69
- package/components/components.tsx +28 -28
- package/components/styles.css +101 -101
- package/components/tokens.css +55 -55
- package/components/typography.css +31 -31
- package/docs/DESIGN_SYSTEM.md +12 -11
- package/package.json +1 -1
- package/preview/MotionGallery.tsx +3 -3
- package/preview/StartupGallery.tsx +1 -1
- package/preview/brand-assembly.css +8 -8
- package/preview/main.tsx +1 -1
- package/preview/motion.css +14 -14
- package/preview/preview.css +19 -19
- package/preview/startup.css +15 -15
- package/scripts/check-build.mjs +1 -1
- package/skills/rhine-design/SKILL.md +3 -3
- package/skills/rhine-design/references/integration.md +4 -4
- package/tests/CommonComponents.test.tsx +1 -1
- package/tests/StartupGallery.test.tsx +3 -3
package/components/common.css
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
.
|
|
2
|
-
.
|
|
3
|
-
.
|
|
4
|
-
.
|
|
5
|
-
.
|
|
6
|
-
.
|
|
7
|
-
.
|
|
8
|
-
.
|
|
9
|
-
.
|
|
10
|
-
.
|
|
11
|
-
.
|
|
12
|
-
.
|
|
13
|
-
.
|
|
14
|
-
.
|
|
15
|
-
.
|
|
16
|
-
.
|
|
17
|
-
.
|
|
18
|
-
.
|
|
19
|
-
.
|
|
20
|
-
.
|
|
21
|
-
.
|
|
22
|
-
.
|
|
23
|
-
.
|
|
24
|
-
.
|
|
25
|
-
.
|
|
26
|
-
.
|
|
27
|
-
.
|
|
28
|
-
.
|
|
29
|
-
.
|
|
30
|
-
.
|
|
31
|
-
.
|
|
32
|
-
.
|
|
33
|
-
.
|
|
34
|
-
.
|
|
35
|
-
.
|
|
36
|
-
.
|
|
37
|
-
.
|
|
38
|
-
.
|
|
39
|
-
.
|
|
40
|
-
.
|
|
41
|
-
.
|
|
42
|
-
.
|
|
43
|
-
.
|
|
44
|
-
.
|
|
45
|
-
.
|
|
46
|
-
.
|
|
47
|
-
.
|
|
48
|
-
.
|
|
49
|
-
.
|
|
50
|
-
.
|
|
51
|
-
.
|
|
52
|
-
.
|
|
53
|
-
.
|
|
54
|
-
.
|
|
55
|
-
.
|
|
56
|
-
.
|
|
57
|
-
.
|
|
58
|
-
.
|
|
59
|
-
.
|
|
60
|
-
.
|
|
61
|
-
.
|
|
62
|
-
.
|
|
63
|
-
.
|
|
64
|
-
.
|
|
65
|
-
.
|
|
66
|
-
.
|
|
67
|
-
.
|
|
68
|
-
@keyframes
|
|
69
|
-
@media(forced-colors:active) { .
|
|
1
|
+
.rhine-icon { display:inline-block; vertical-align:middle; flex:none; fill:none; stroke:currentColor; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; }
|
|
2
|
+
.rhine-icon-button { padding:10px; width:44px; height:44px; flex:none; }
|
|
3
|
+
.rhine-link { color:var(--rhine-green); text-decoration:underline; text-underline-offset:3px; overflow-wrap:anywhere; }
|
|
4
|
+
.rhine-link:hover { text-decoration-thickness:2px; }
|
|
5
|
+
.rhine-link>.rhine-icon { margin-left:4px; }
|
|
6
|
+
.rhine-copy { display:inline-flex; align-items:center; gap:12px; flex-wrap:wrap; }
|
|
7
|
+
.rhine-copy>[role] { font-size:var(--rhine-text-caption); }
|
|
8
|
+
.rhine-stack { display:flex; flex-direction:column; min-width:0; }
|
|
9
|
+
.rhine-inline { display:flex; align-items:center; flex-wrap:wrap; gap:var(--rhine-space-3); min-width:0; }
|
|
10
|
+
.rhine-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,var(--rhine-grid-min)),1fr)); min-width:0; }
|
|
11
|
+
.rhine-grid>*,.rhine-stack>* { min-width:0; }
|
|
12
|
+
.rhine-divider { border:0; border-top:1px solid var(--rhine-line); margin:0; width:100%; }
|
|
13
|
+
.rhine-metric { min-width:0; display:grid; gap:8px; align-content:start; }
|
|
14
|
+
.rhine-metric__unit { font-size:var(--rhine-text-caption); font-weight:400; color:var(--rhine-muted); }
|
|
15
|
+
.rhine-key-value { margin:0; display:grid; gap:12px; }
|
|
16
|
+
.rhine-key-value>div { display:grid; grid-template-columns:minmax(80px,1fr) minmax(0,2fr); gap:16px; }
|
|
17
|
+
.rhine-key-value dt { color:var(--rhine-muted); font-size:12px; }.rhine-key-value dd { margin:0; overflow-wrap:anywhere; }
|
|
18
|
+
.rhine-data { display:grid; gap:12px; min-width:0; }
|
|
19
|
+
.rhine-table-scroll { overflow:auto; max-width:100%; }
|
|
20
|
+
.rhine-table { width:100%; min-width:500px; border-collapse:collapse; text-align:left; font-size:12px; }
|
|
21
|
+
.rhine-table caption { text-align:left; font-size:16px; font-weight:600; margin-bottom:16px; }
|
|
22
|
+
.rhine-table th,.rhine-table td { padding:12px; border-bottom:1px solid var(--rhine-line); max-width:240px; overflow-wrap:anywhere; }
|
|
23
|
+
.rhine-table th { color:var(--rhine-muted); font-weight:500; }.rhine-table td { font-variant-numeric:tabular-nums; }
|
|
24
|
+
.rhine-table [data-align=right] { text-align:right; }.rhine-table tbody tr:hover { background:var(--rhine-selected); }
|
|
25
|
+
.rhine-table th button { font:inherit; color:inherit; background:none; border:0; min-height:44px; cursor:pointer; padding:0; }
|
|
26
|
+
.rhine-table th button:disabled { cursor:default; }
|
|
27
|
+
.rhine-pagination { display:flex; flex-wrap:wrap; justify-content:flex-end; align-items:center; gap:12px; }
|
|
28
|
+
.rhine-check>label,.rhine-radio-group>label { display:flex; align-items:center; gap:10px; min-height:44px; cursor:pointer; overflow-wrap:anywhere; }
|
|
29
|
+
.rhine-check input,.rhine-radio-group input { width:18px; height:18px; accent-color:var(--rhine-green); flex:none; margin:0; }
|
|
30
|
+
.rhine-check input:disabled+span,.rhine-radio-group input:disabled+span,.rhine-radio-group:disabled span { color:var(--rhine-muted); cursor:default; }
|
|
31
|
+
.rhine-radio-group { border:0; padding:0; margin:0; min-width:0; }.rhine-radio-group legend { font-weight:500; padding:0; }
|
|
32
|
+
.rhine-switch label { display:flex; position:relative; align-items:center; gap:12px; min-height:44px; cursor:pointer; }
|
|
33
|
+
.rhine-switch input { position:absolute; opacity:0; width:1px; height:1px; }
|
|
34
|
+
.rhine-switch__track { display:inline-flex; width:36px; height:22px; border:1px solid var(--rhine-control); border-radius:12px; padding:3px; flex:none; background:var(--rhine-surface); transition:background-color var(--rhine-duration-hover) var(--rhine-ease-standard); }
|
|
35
|
+
.rhine-switch__track>span { width:14px; height:14px; border-radius:50%; background:var(--rhine-muted); transition:transform var(--rhine-duration-hover) var(--rhine-ease-standard); }
|
|
36
|
+
.rhine-switch input:checked+.rhine-switch__track { background:var(--rhine-green); border-color:var(--rhine-green); }
|
|
37
|
+
.rhine-switch input:checked+.rhine-switch__track>span { transform:translateX(14px); background:var(--rhine-surface); }
|
|
38
|
+
.rhine-switch input:focus-visible+.rhine-switch__track { outline:2px solid var(--rhine-green); outline-offset:3px; }
|
|
39
|
+
.rhine-switch input:disabled~span { opacity:.6; cursor:default; }
|
|
40
|
+
.rhine-field textarea { font:var(--rhine-text-body)/var(--rhine-leading-body) var(--rhine-font); color:var(--rhine-ink); background:var(--rhine-surface); border:1px solid var(--rhine-control); border-radius:var(--rhine-radius-control); padding:10px 12px; min-height:88px; resize:vertical; width:100%; transition:border-color var(--rhine-duration-hover) var(--rhine-ease-standard); }
|
|
41
|
+
.rhine-field textarea[aria-invalid=true] { border-color:var(--rhine-error); }.rhine-field textarea:disabled { color:var(--rhine-muted); background:var(--rhine-selected); }
|
|
42
|
+
.rhine-search>.rhine-inline>input { flex:1 1 140px; width:auto; }.rhine-search input::-webkit-search-cancel-button { appearance:none; }
|
|
43
|
+
.rhine-combobox { position:relative; }
|
|
44
|
+
.rhine-combobox__input { position:relative; }.rhine-combobox__input input { padding-right:40px; }
|
|
45
|
+
.rhine-combobox__arrow { position:absolute; right:12px; top:50%; transform:translateY(-50%); pointer-events:none; }
|
|
46
|
+
.rhine-combobox__list { z-index:50; width:var(--radix-popover-trigger-width); max-width:calc(100vw - 24px); max-height:min(240px,var(--radix-popover-content-available-height)); overflow:auto; background:var(--rhine-surface); color:var(--rhine-ink); border:1px solid var(--rhine-control); border-radius:4px; box-shadow:0 8px 24px #242b2818; }
|
|
47
|
+
.rhine-combobox__list [role=option],.rhine-combobox__empty { padding:12px; min-height:44px; cursor:pointer; overflow-wrap:anywhere; }
|
|
48
|
+
.rhine-combobox__list [role=option][data-active=true],.rhine-combobox__list [role=option][aria-selected=true] { background:var(--rhine-selected); }
|
|
49
|
+
.rhine-combobox__list [role=option][aria-disabled=true],.rhine-combobox__empty { color:var(--rhine-muted); cursor:default; }
|
|
50
|
+
.rhine-overlay-anchor { display:inline-block; vertical-align:middle; max-width:100%; }
|
|
51
|
+
.rhine-overlay { position:fixed; inset:0; background:#242b2870; z-index:40; }
|
|
52
|
+
.rhine-dialog { position:fixed; z-index:41; top:50%; left:50%; transform:translate(-50%,-50%); width:min(480px,calc(100vw - 32px)); max-height:calc(100dvh - 32px); overflow:auto; padding:24px; border:1px solid var(--rhine-line); border-radius:4px; background:var(--rhine-surface); color:var(--rhine-ink); box-shadow:0 16px 48px #242b2826; }
|
|
53
|
+
.rhine-dialog>p { margin-top:12px; }.rhine-dialog>.rhine-notice { margin-top:16px; }
|
|
54
|
+
.rhine-dialog__head { display:flex; align-items:center; justify-content:space-between; gap:12px; }
|
|
55
|
+
.rhine-dialog__head>:first-child { min-width:0; overflow-wrap:anywhere; }
|
|
56
|
+
.rhine-dialog__body { margin-top:24px; }.rhine-dialog__actions { display:flex; justify-content:flex-end; flex-wrap:wrap; gap:12px; margin-top:24px; }
|
|
57
|
+
.rhine-popover,.rhine-menu { z-index:50; background:var(--rhine-surface); color:var(--rhine-ink); border:1px solid var(--rhine-control); border-radius:4px; box-shadow:0 8px 24px #242b2820; max-width:calc(100vw - 24px); overflow:auto; }
|
|
58
|
+
.rhine-popover { width:300px; padding:16px; max-height:var(--radix-popover-content-available-height); }
|
|
59
|
+
.rhine-menu { min-width:180px; max-height:var(--radix-dropdown-menu-content-available-height); padding:4px; }
|
|
60
|
+
.rhine-menu__item { min-height:44px; padding:10px 12px; cursor:pointer; outline:none; overflow-wrap:anywhere; }
|
|
61
|
+
.rhine-menu__item[data-highlighted] { background:var(--rhine-selected); box-shadow:inset 0 0 0 2px var(--rhine-green); }
|
|
62
|
+
.rhine-menu__item[data-disabled] { color:var(--rhine-muted); cursor:default; }.rhine-menu__item[data-danger=true] { color:var(--rhine-error); }
|
|
63
|
+
.rhine-tooltip { z-index:60; background:var(--rhine-ink); color:var(--rhine-surface); padding:8px 12px; border-radius:3px; max-width:min(280px,calc(100vw - 24px)); font-size:12px; line-height:18px; overflow-wrap:anywhere; }
|
|
64
|
+
.rhine-toast-region { position:relative; }.rhine-toast { display:flex; align-items:start; gap:12px; justify-content:space-between; padding:16px; background:var(--rhine-tone-bg); border-left:3px solid var(--rhine-tone); color:var(--rhine-ink); animation:rhine-enter var(--rhine-duration-panel) var(--rhine-ease-standard); }
|
|
65
|
+
.rhine-toast p { margin:4px 0 0; overflow-wrap:anywhere; }.rhine-toast strong { overflow-wrap:anywhere; }.rhine-toast>div { min-width:0; }
|
|
66
|
+
.rhine-dialog[data-state=open],.rhine-overlay[data-state=open] { animation:rhine-overlay-fade var(--rhine-duration-overlay) var(--rhine-ease-standard); }
|
|
67
|
+
.rhine-popover[data-state=open],.rhine-menu[data-state=open],.rhine-combobox__list[data-state=open],.rhine-tooltip[data-state=delayed-open],.rhine-tooltip[data-state=instant-open] { animation:rhine-enter var(--rhine-duration-overlay) var(--rhine-ease-standard); }
|
|
68
|
+
@keyframes rhine-overlay-fade { from { opacity:0; } to { opacity:1; } }
|
|
69
|
+
@media(forced-colors:active) { .rhine-switch__track { border-color:ButtonText; }.rhine-switch input:checked+.rhine-switch__track { background:Highlight; }.rhine-switch__track>span { background:ButtonText; }.rhine-switch input:checked+.rhine-switch__track>span { background:HighlightText; }.rhine-dialog,.rhine-tooltip,.rhine-menu,.rhine-popover { border:1px solid CanvasText; } }
|
|
@@ -10,35 +10,35 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
10
10
|
}
|
|
11
11
|
export function Button({ variant = 'primary', busy = false, busyLabel = '处理中', disabled, className, children, onClick, type = 'button', ...props }: ButtonProps) {
|
|
12
12
|
return <button {...props} type={type} disabled={disabled} aria-disabled={busy || disabled || undefined}
|
|
13
|
-
aria-busy={busy || undefined} className={classes('
|
|
13
|
+
aria-busy={busy || undefined} className={classes('rhine-button', `rhine-button--${variant}`, className)}
|
|
14
14
|
onClick={event => { if (busy) { event.preventDefault(); return; } onClick?.(event); }}>
|
|
15
|
-
<span className={busy ? '
|
|
16
|
-
{busy && <span className="
|
|
15
|
+
<span className={busy ? 'rhine-button__ghost' : undefined}>{children}</span>
|
|
16
|
+
{busy && <span className="rhine-button__busy"><span className="rhine-spinner" aria-hidden="true" /><span className="rhine-sr-only">{busyLabel}</span></span>}
|
|
17
17
|
</button>;
|
|
18
18
|
}
|
|
19
19
|
export function Badge({ tone = 'neutral', children }: { tone?: Tone; children: ReactNode }) {
|
|
20
|
-
return <span className={`
|
|
20
|
+
return <span className={`rhine-badge rhine-tone-${tone}`}>{children}</span>;
|
|
21
21
|
}
|
|
22
22
|
export function Surface({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
|
|
23
|
-
return <div {...props} className={classes('
|
|
23
|
+
return <div {...props} className={classes('rhine-surface', className)} />;
|
|
24
24
|
}
|
|
25
25
|
export function SelectionCard({ label, selected, onSelectedChange, disabled = false, children }: {
|
|
26
26
|
label: string; selected: boolean; onSelectedChange: (value: boolean) => void; disabled?: boolean; children: ReactNode;
|
|
27
27
|
}) {
|
|
28
|
-
return <label className="
|
|
29
|
-
<span className="
|
|
30
|
-
<span className="
|
|
28
|
+
return <label className="rhine-selection" data-selected={selected} data-disabled={disabled}>
|
|
29
|
+
<span className="rhine-selection__heading"><strong>{label}</strong><input type="checkbox" aria-label={label} checked={selected} disabled={disabled} onChange={event => onSelectedChange(event.target.checked)} /></span>
|
|
30
|
+
<span className="rhine-selection__content">{children}</span>
|
|
31
31
|
</label>;
|
|
32
32
|
}
|
|
33
33
|
export function TextField({ label, hint, error, id: providedId, className, ...props }: InputHTMLAttributes<HTMLInputElement> & { label: string; hint?: string; error?: string }) {
|
|
34
34
|
const generatedId = useId();
|
|
35
35
|
const id = providedId || generatedId;
|
|
36
36
|
const describedBy = [props['aria-describedby'], hint && `${id}-hint`, error && `${id}-error`].filter(Boolean).join(' ');
|
|
37
|
-
return <div className={classes('
|
|
37
|
+
return <div className={classes('rhine-field', className)}>
|
|
38
38
|
<label htmlFor={id}>{label}</label>
|
|
39
39
|
<input {...props} id={id} aria-invalid={error ? true : props['aria-invalid']} aria-describedby={describedBy || undefined} />
|
|
40
|
-
{hint && <span id={`${id}-hint`} className="
|
|
41
|
-
{error && <span id={`${id}-error`} className="
|
|
40
|
+
{hint && <span id={`${id}-hint`} className="rhine-field__hint">{hint}</span>}
|
|
41
|
+
{error && <span id={`${id}-error`} className="rhine-field__error" role="alert">{error}</span>}
|
|
42
42
|
</div>;
|
|
43
43
|
}
|
|
44
44
|
export interface Choice { value: string; label: string; disabled?: boolean }
|
|
@@ -46,8 +46,8 @@ export interface TabItem extends Choice { content: ReactNode }
|
|
|
46
46
|
export function Tabs({ label, items, value, onValueChange }: { label: string; items: TabItem[]; value: string; onValueChange: (value: string) => void }) {
|
|
47
47
|
const id = useId();
|
|
48
48
|
const buttons = useRef<(HTMLButtonElement | null)[]>([]);
|
|
49
|
-
return <div className="
|
|
50
|
-
<div role="tablist" aria-label={label} className="
|
|
49
|
+
return <div className="rhine-tabs">
|
|
50
|
+
<div role="tablist" aria-label={label} className="rhine-tabs__list" onKeyDown={event => {
|
|
51
51
|
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
|
|
52
52
|
const available = items.map((item, index) => ({ ...item, index })).filter(item => !item.disabled);
|
|
53
53
|
if (!available.length) return;
|
|
@@ -61,36 +61,36 @@ export function Tabs({ label, items, value, onValueChange }: { label: string; it
|
|
|
61
61
|
aria-selected={value === item.value} tabIndex={value === item.value ? 0 : -1} onClick={() => onValueChange(item.value)}>{item.label}</button>)}
|
|
62
62
|
</div>
|
|
63
63
|
{items.map((item, index) => <div key={item.value} role="tabpanel" id={`${id}-panel-${index}`} aria-labelledby={`${id}-tab-${index}`}
|
|
64
|
-
hidden={value !== item.value} tabIndex={0} className="
|
|
64
|
+
hidden={value !== item.value} tabIndex={0} className="rhine-tabs__panel">{item.content}</div>)}
|
|
65
65
|
</div>;
|
|
66
66
|
}
|
|
67
67
|
export function SegmentedControl({ label, items, value, onValueChange }: { label: string; items: Choice[]; value: string; onValueChange: (value: string) => void }) {
|
|
68
68
|
const name = useId();
|
|
69
|
-
return <fieldset className="
|
|
70
|
-
{items.map(item => <label key={item.value} className="
|
|
69
|
+
return <fieldset className="rhine-segmented"><legend className="rhine-sr-only">{label}</legend>
|
|
70
|
+
{items.map(item => <label key={item.value} className="rhine-segmented__option">
|
|
71
71
|
<input type="radio" name={name} value={item.value} checked={value === item.value} disabled={item.disabled} onChange={() => onValueChange(item.value)} />
|
|
72
72
|
<span>{item.label}</span>
|
|
73
73
|
</label>)}
|
|
74
74
|
</fieldset>;
|
|
75
75
|
}
|
|
76
76
|
export function Notice({ tone = 'neutral', title, children, action }: { tone?: Tone; title: string; children?: ReactNode; action?: ReactNode }) {
|
|
77
|
-
return <div className={`
|
|
78
|
-
<div><strong>{title}</strong>{children && <div className="
|
|
77
|
+
return <div className={`rhine-notice rhine-tone-${tone}`} role={tone === 'error' ? 'alert' : 'status'}>
|
|
78
|
+
<div><strong>{title}</strong>{children && <div className="rhine-notice__body">{children}</div>}</div>{action}
|
|
79
79
|
</div>;
|
|
80
80
|
}
|
|
81
81
|
export function Progress({ label, value, valueText }: { label: string; value: number | null; valueText?: string }) {
|
|
82
82
|
const determinate = value !== null && Number.isFinite(value);
|
|
83
83
|
const amount = determinate ? Math.max(0, Math.min(100, value)) : null;
|
|
84
|
-
return <div className="
|
|
85
|
-
<div className="
|
|
86
|
-
<div className="
|
|
84
|
+
return <div className="rhine-progress">
|
|
85
|
+
<div className="rhine-progress__label"><span>{label}</span><strong>{valueText ?? (amount === null ? '处理中' : `${amount}%`)}</strong></div>
|
|
86
|
+
<div className="rhine-progress__track" role="progressbar" aria-label={label} aria-valuemin={0} aria-valuemax={100}
|
|
87
87
|
aria-valuenow={amount ?? undefined} aria-valuetext={valueText ?? (amount === null ? '进度未知' : `${amount}%`)} data-indeterminate={amount === null}>
|
|
88
88
|
<span style={amount === null ? undefined : { transform: `scaleX(${amount / 100})` }} />
|
|
89
89
|
</div>
|
|
90
90
|
</div>;
|
|
91
91
|
}
|
|
92
92
|
export function Skeleton({ label = '正在加载', lines = 3 }: { label?: string; lines?: number }) {
|
|
93
|
-
return <div className="
|
|
93
|
+
return <div className="rhine-skeleton" role="status"><span className="rhine-sr-only">{label}</span>
|
|
94
94
|
{Array.from({ length: Math.max(1, Math.min(8, Math.floor(lines) || 1)) }, (_, index) => <span aria-hidden="true" key={index} />)}
|
|
95
95
|
</div>;
|
|
96
96
|
}
|
|
@@ -111,7 +111,7 @@ export function Disclosure({ title, children, defaultOpen = false }: { title: st
|
|
|
111
111
|
}, []);
|
|
112
112
|
useEffect(() => {
|
|
113
113
|
if (!animation.current || !node.current) return;
|
|
114
|
-
if (parseFloat(getComputedStyle(node.current).getPropertyValue('--
|
|
114
|
+
if (parseFloat(getComputedStyle(node.current).getPropertyValue('--rhine-duration-notice')) !== 0) return;
|
|
115
115
|
animation.current.cancel(); animation.current = null;
|
|
116
116
|
node.current.open = intent.current; setOpen(intent.current);
|
|
117
117
|
});
|
|
@@ -130,22 +130,22 @@ export function Disclosure({ title, children, defaultOpen = false }: { title: st
|
|
|
130
130
|
const reduced = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
131
131
|
if (reduced || !el.animate) { el.open = expanded; setOpen(expanded); return; }
|
|
132
132
|
const style = getComputedStyle(el);
|
|
133
|
-
const parsedDuration = parseFloat(style.getPropertyValue('--
|
|
133
|
+
const parsedDuration = parseFloat(style.getPropertyValue('--rhine-duration-notice'));
|
|
134
134
|
const duration = Number.isFinite(parsedDuration) ? parsedDuration : 200;
|
|
135
135
|
if (duration === 0) { el.open = expanded; setOpen(expanded); return; }
|
|
136
136
|
const to = expanded ? el.getBoundingClientRect().height : summary.getBoundingClientRect().height + parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
|
|
137
|
-
const next = el.animate([{ height: `${from}px` }, { height: `${to}px` }], { duration, easing: style.getPropertyValue(expanded ? '--
|
|
137
|
+
const next = el.animate([{ height: `${from}px` }, { height: `${to}px` }], { duration, easing: style.getPropertyValue(expanded ? '--rhine-ease-standard' : '--rhine-ease-exit').trim() || 'ease' });
|
|
138
138
|
animation.current = next;
|
|
139
139
|
next.onfinish = () => {
|
|
140
140
|
if (animation.current !== next) return;
|
|
141
141
|
el.open = expanded; setOpen(expanded); animation.current = null;
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
|
-
return <details ref={node} open={open} data-expanded={open} className="
|
|
144
|
+
return <details ref={node} open={open} data-expanded={open} className="rhine-disclosure">
|
|
145
145
|
<summary onClick={event => { event.preventDefault(); toggle(); }}>{title}<span aria-hidden="true">+</span></summary>
|
|
146
|
-
<div className="
|
|
146
|
+
<div className="rhine-disclosure__content">{children}</div>
|
|
147
147
|
</details>;
|
|
148
148
|
}
|
|
149
149
|
export function EmptyState({ title, description, action }: { title: string; description: string; action?: ReactNode }) {
|
|
150
|
-
return <div className="
|
|
150
|
+
return <div className="rhine-empty"><strong>{title}</strong><p>{description}</p>{action}</div>;
|
|
151
151
|
}
|
package/components/styles.css
CHANGED
|
@@ -2,108 +2,108 @@
|
|
|
2
2
|
@import './typography.css';
|
|
3
3
|
@import './common.css';
|
|
4
4
|
|
|
5
|
-
.
|
|
6
|
-
.
|
|
7
|
-
.
|
|
8
|
-
.
|
|
9
|
-
.
|
|
10
|
-
.
|
|
11
|
-
.
|
|
12
|
-
.
|
|
13
|
-
.
|
|
14
|
-
.
|
|
15
|
-
.
|
|
16
|
-
.
|
|
17
|
-
.
|
|
18
|
-
.
|
|
19
|
-
.
|
|
20
|
-
.
|
|
21
|
-
.
|
|
22
|
-
.
|
|
23
|
-
.
|
|
24
|
-
.
|
|
25
|
-
.
|
|
26
|
-
.
|
|
27
|
-
.
|
|
28
|
-
.
|
|
29
|
-
.
|
|
30
|
-
.
|
|
31
|
-
.
|
|
32
|
-
.
|
|
33
|
-
.
|
|
34
|
-
.
|
|
35
|
-
.
|
|
36
|
-
.
|
|
37
|
-
.
|
|
38
|
-
.
|
|
39
|
-
.
|
|
40
|
-
.
|
|
41
|
-
.
|
|
42
|
-
.
|
|
43
|
-
.
|
|
44
|
-
.
|
|
45
|
-
.
|
|
46
|
-
.
|
|
47
|
-
.
|
|
48
|
-
.
|
|
49
|
-
.
|
|
50
|
-
.
|
|
51
|
-
.
|
|
52
|
-
.
|
|
53
|
-
.
|
|
54
|
-
.
|
|
55
|
-
.
|
|
56
|
-
.
|
|
57
|
-
.
|
|
58
|
-
.
|
|
59
|
-
.
|
|
60
|
-
.
|
|
61
|
-
.
|
|
62
|
-
.
|
|
63
|
-
.
|
|
64
|
-
.
|
|
65
|
-
.
|
|
66
|
-
.
|
|
67
|
-
.
|
|
68
|
-
.
|
|
69
|
-
.
|
|
70
|
-
.
|
|
71
|
-
.
|
|
72
|
-
.
|
|
73
|
-
.
|
|
74
|
-
.
|
|
75
|
-
.
|
|
76
|
-
.
|
|
77
|
-
.
|
|
78
|
-
.
|
|
79
|
-
.
|
|
80
|
-
.
|
|
81
|
-
@keyframes
|
|
82
|
-
.
|
|
83
|
-
.
|
|
84
|
-
.
|
|
85
|
-
.
|
|
86
|
-
.
|
|
87
|
-
.
|
|
88
|
-
.
|
|
89
|
-
.
|
|
90
|
-
.
|
|
91
|
-
.
|
|
92
|
-
.
|
|
93
|
-
@keyframes
|
|
5
|
+
.rhine-theme { color:var(--rhine-ink); background:var(--rhine-bg); font:var(--rhine-text-body)/var(--rhine-leading-body) var(--rhine-font); font-synthesis:none; }
|
|
6
|
+
.rhine-theme *, .rhine-theme *::before, .rhine-theme *::after { box-sizing:border-box; }
|
|
7
|
+
.rhine-theme [hidden] { display:none!important; }
|
|
8
|
+
.rhine-theme :is(button,input,select,textarea,summary,a,[tabindex]):focus-visible { outline:2px solid var(--rhine-green); outline-offset:3px; }
|
|
9
|
+
.rhine-sr-only { position:absolute!important; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0; }
|
|
10
|
+
.rhine-button { position:relative; display:inline-flex; align-items:center; justify-content:center; min-height:44px; min-width:44px; padding:10px 16px; border:1px solid var(--rhine-ink); border-radius:var(--rhine-radius-control); font:500 14px/22px var(--rhine-font); background:var(--rhine-ink); color:var(--rhine-surface); cursor:pointer; overflow-wrap:anywhere; transition:background-color var(--rhine-duration-hover) var(--rhine-ease-standard),border-color var(--rhine-duration-hover) var(--rhine-ease-standard),transform var(--rhine-duration-press) var(--rhine-ease-standard); }
|
|
11
|
+
.rhine-button:hover:not(:disabled):not([aria-disabled=true]) { background:var(--rhine-green); border-color:var(--rhine-green); }
|
|
12
|
+
.rhine-button:active:not(:disabled):not([aria-disabled=true]) { transform:translateY(1px); }
|
|
13
|
+
.rhine-button--secondary { background:var(--rhine-surface); color:var(--rhine-ink); border-color:var(--rhine-control); }
|
|
14
|
+
.rhine-button--quiet { background:transparent; color:var(--rhine-green); border-color:transparent; }
|
|
15
|
+
.rhine-button--secondary:hover:not(:disabled):not([aria-disabled=true]),.rhine-button--quiet:hover:not(:disabled):not([aria-disabled=true]) { background:var(--rhine-selected); }
|
|
16
|
+
.rhine-button--danger { background:var(--rhine-error); border-color:var(--rhine-error); }
|
|
17
|
+
.rhine-button--danger:hover:not(:disabled):not([aria-disabled=true]) { background:#81342e; border-color:#81342e; }
|
|
18
|
+
.rhine-button:disabled { background:var(--rhine-selected); border-color:var(--rhine-line); color:var(--rhine-muted); cursor:default; }
|
|
19
|
+
.rhine-button[aria-busy=true] { cursor:wait; }
|
|
20
|
+
.rhine-button__ghost { visibility:hidden; }
|
|
21
|
+
.rhine-button__busy { position:absolute; inset:0; display:grid; place-items:center; }
|
|
22
|
+
.rhine-spinner { width:18px; height:18px; border:2px solid currentColor; border-right-color:transparent; border-radius:50%; animation:rhine-spin var(--rhine-duration-loading) linear infinite; }
|
|
23
|
+
.rhine-tone-neutral { --rhine-tone:var(--rhine-muted); --rhine-tone-bg:var(--rhine-selected); }
|
|
24
|
+
.rhine-tone-success { --rhine-tone:var(--rhine-green); --rhine-tone-bg:var(--rhine-selected); }
|
|
25
|
+
.rhine-tone-warning { --rhine-tone:var(--rhine-warning); --rhine-tone-bg:var(--rhine-warning-bg); }
|
|
26
|
+
.rhine-tone-error { --rhine-tone:var(--rhine-error); --rhine-tone-bg:var(--rhine-error-bg); }
|
|
27
|
+
.rhine-badge { display:inline-block; padding:2px 8px; border-radius:2px; background:var(--rhine-tone-bg); color:var(--rhine-tone); font:500 12px/20px var(--rhine-font); }
|
|
28
|
+
.rhine-surface { padding:24px; border:1px solid var(--rhine-line); border-radius:var(--rhine-radius-card); background:var(--rhine-surface); min-width:0; }
|
|
29
|
+
.rhine-selection { display:block; border:1px solid var(--rhine-control); background:var(--rhine-surface); border-radius:var(--rhine-radius-card); padding:20px; cursor:pointer; min-width:0; transition:background-color var(--rhine-duration-hover) var(--rhine-ease-standard),border-color var(--rhine-duration-hover) var(--rhine-ease-standard),box-shadow var(--rhine-duration-hover) var(--rhine-ease-standard); }
|
|
30
|
+
.rhine-selection:hover:not([data-disabled=true]) { border-color:var(--rhine-green); background:#f4f7ef; }
|
|
31
|
+
.rhine-selection[data-selected=true] { border-color:var(--rhine-green); background:var(--rhine-selected); box-shadow:inset 0 0 0 1px var(--rhine-green); }
|
|
32
|
+
.rhine-selection[data-disabled=true] { cursor:default; color:var(--rhine-muted); background:var(--rhine-bg); }
|
|
33
|
+
.rhine-selection:focus-within { outline:2px solid var(--rhine-green); outline-offset:3px; }
|
|
34
|
+
.rhine-selection__heading { display:flex; align-items:center; justify-content:space-between; gap:16px; font-size:16px; overflow-wrap:anywhere; }
|
|
35
|
+
.rhine-selection input { width:18px; height:18px; accent-color:var(--rhine-green); flex-shrink:0; }
|
|
36
|
+
.rhine-selection__content { display:block; margin-top:16px; overflow-wrap:anywhere; }
|
|
37
|
+
.rhine-field { display:flex; flex-direction:column; gap:8px; min-width:0; }
|
|
38
|
+
.rhine-field>label { font-weight:500; }
|
|
39
|
+
.rhine-field input { width:100%; min-width:0; min-height:44px; padding:10px 12px; color:var(--rhine-ink); background:var(--rhine-surface); border:1px solid var(--rhine-control); border-radius:var(--rhine-radius-control); font:14px/22px var(--rhine-font); transition:border-color var(--rhine-duration-hover) var(--rhine-ease-standard),background-color var(--rhine-duration-hover) var(--rhine-ease-standard); }
|
|
40
|
+
.rhine-field input::placeholder { color:var(--rhine-muted); }
|
|
41
|
+
.rhine-field input[aria-invalid=true] { border-color:var(--rhine-error); }
|
|
42
|
+
.rhine-field input:disabled { background:var(--rhine-selected); color:var(--rhine-muted); }
|
|
43
|
+
.rhine-field__hint,.rhine-field__error { font-size:12px; line-height:18px; }
|
|
44
|
+
.rhine-field__hint { color:var(--rhine-muted); }
|
|
45
|
+
.rhine-field__error { color:var(--rhine-error); }
|
|
46
|
+
.rhine-tabs__list { display:flex; flex-wrap:wrap; gap:24px; border-bottom:1px solid var(--rhine-line); }
|
|
47
|
+
.rhine-tabs__list button { position:relative; background:none; border:0; padding:12px 0; min-height:44px; color:var(--rhine-muted); font:500 14px/22px var(--rhine-font); cursor:pointer; transition:color var(--rhine-duration-hover) var(--rhine-ease-standard); }
|
|
48
|
+
.rhine-tabs__list button::after { content:''; position:absolute; bottom:-1px; inset-inline:0; height:2px; background:var(--rhine-green); transform:scaleX(0); transform-origin:left; transition:transform var(--rhine-duration-panel) var(--rhine-ease-standard); }
|
|
49
|
+
.rhine-tabs__list button[aria-selected=true] { color:var(--rhine-ink); }
|
|
50
|
+
.rhine-tabs__list button[aria-selected=true]::after { transform:scaleX(1); }
|
|
51
|
+
.rhine-tabs__list button:disabled { color:var(--rhine-muted); opacity:.6; cursor:default; }
|
|
52
|
+
.rhine-tabs__panel { padding-top:20px; animation:rhine-enter var(--rhine-duration-panel) var(--rhine-ease-standard); }
|
|
53
|
+
.rhine-segmented { display:inline-flex; flex-wrap:wrap; gap:3px; padding:3px; border:1px solid var(--rhine-control); border-radius:var(--rhine-radius-control); margin:0; min-width:0; }
|
|
54
|
+
.rhine-segmented__option { position:relative; display:flex; flex:1 0 auto; cursor:pointer; }
|
|
55
|
+
.rhine-segmented input { position:absolute; opacity:0; width:1px; height:1px; }
|
|
56
|
+
.rhine-segmented__option span { display:flex; justify-content:center; align-items:center; min-height:40px; padding:8px 12px; color:var(--rhine-muted); font-size:14px; transition:background-color var(--rhine-duration-hover) var(--rhine-ease-standard),color var(--rhine-duration-hover) var(--rhine-ease-standard); }
|
|
57
|
+
.rhine-segmented input:checked+span { background:var(--rhine-ink); color:var(--rhine-surface); }
|
|
58
|
+
.rhine-segmented input:focus-visible+span { outline:2px solid var(--rhine-green); outline-offset:3px; }
|
|
59
|
+
.rhine-segmented input:disabled+span { opacity:.6; cursor:default; }
|
|
60
|
+
.rhine-notice { padding:16px; border-left:3px solid var(--rhine-tone); background:var(--rhine-tone-bg); color:var(--rhine-tone); display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap; animation:rhine-enter var(--rhine-duration-notice) var(--rhine-ease-standard); }
|
|
61
|
+
.rhine-notice>div { flex:1 1 200px; min-width:0; overflow-wrap:anywhere; }
|
|
62
|
+
.rhine-notice strong { font-size:14px; font-weight:600; }
|
|
63
|
+
.rhine-notice__body { margin-top:4px; font-size:12px; line-height:20px; }
|
|
64
|
+
.rhine-progress__label { display:flex; justify-content:space-between; gap:12px; font-size:12px; margin-bottom:8px; }
|
|
65
|
+
.rhine-progress__label strong { font-variant-numeric:tabular-nums; }
|
|
66
|
+
.rhine-progress__track { height:6px; background:var(--rhine-line); overflow:hidden; }
|
|
67
|
+
.rhine-progress__track>span { display:block; height:100%; background:var(--rhine-green); transform-origin:left; transition:transform var(--rhine-duration-progress) var(--rhine-ease-standard); }
|
|
68
|
+
.rhine-progress__track[data-indeterminate=true]>span { width:30%; animation:rhine-progress var(--rhine-duration-loading) var(--rhine-ease-standard) infinite; }
|
|
69
|
+
.rhine-skeleton { position:relative; display:flex; flex-direction:column; gap:12px; }
|
|
70
|
+
.rhine-skeleton>span[aria-hidden] { display:block; height:16px; background:var(--rhine-line); animation:rhine-pulse var(--rhine-duration-skeleton) ease-in-out infinite; }
|
|
71
|
+
.rhine-skeleton>span[aria-hidden]:last-child { width:65%; }
|
|
72
|
+
.rhine-disclosure { border-top:1px solid var(--rhine-line); border-bottom:1px solid var(--rhine-line); overflow:hidden; }
|
|
73
|
+
.rhine-disclosure summary { min-height:48px; padding:12px 0; list-style:none; cursor:pointer; display:flex; justify-content:space-between; align-items:center; gap:16px; font-weight:500; }
|
|
74
|
+
.rhine-disclosure summary::-webkit-details-marker { display:none; }
|
|
75
|
+
.rhine-theme .rhine-disclosure summary:focus-visible { outline-offset:-3px; }
|
|
76
|
+
.rhine-disclosure summary>span { font:20px var(--rhine-mono); transition:transform var(--rhine-duration-notice) var(--rhine-ease-standard); }
|
|
77
|
+
.rhine-disclosure[data-expanded=true] summary>span { transform:rotate(45deg); }
|
|
78
|
+
.rhine-disclosure__content { padding:4px 0 20px; }
|
|
79
|
+
.rhine-empty { padding:32px 24px; text-align:center; border:1px solid var(--rhine-line); background:var(--rhine-surface); }
|
|
80
|
+
.rhine-empty strong { font-size:16px; }.rhine-empty p { max-width:400px; margin:8px auto 20px; color:var(--rhine-muted); }
|
|
81
|
+
@keyframes rhine-spin { to { transform:rotate(360deg); } }
|
|
82
|
+
.rhine-logo { display:inline-flex; flex:none; align-items:center; vertical-align:middle; color:var(--rhine-green); line-height:1; }
|
|
83
|
+
.rhine-logo__symbol { display:block; width:var(--rhine-logo-size); height:var(--rhine-logo-size); flex:none; fill:currentColor; }
|
|
84
|
+
.rhine-logo--icon { width:var(--rhine-logo-size); height:var(--rhine-logo-size); border-radius:22%; background:var(--rhine-bg); }
|
|
85
|
+
.rhine-logo[data-theme=dark] { color:var(--rhine-bg); }
|
|
86
|
+
.rhine-logo--icon[data-theme=dark] { background:var(--rhine-green); }
|
|
87
|
+
.rhine-logo--lockup { gap:12px; }
|
|
88
|
+
.rhine-logo__wordmark { font:600 20px/1 var(--rhine-font); letter-spacing:.08em; white-space:nowrap; }
|
|
89
|
+
.rhine-logo__plate--left { --rhine-logo-x:calc(var(--rhine-logo-travel) * -.5); --rhine-logo-y:calc(var(--rhine-logo-travel) * -.866); }
|
|
90
|
+
.rhine-logo__plate--right { --rhine-logo-x:calc(var(--rhine-logo-travel) * .5); --rhine-logo-y:calc(var(--rhine-logo-travel) * -.866); }
|
|
91
|
+
.rhine-logo__plate--base { --rhine-logo-x:0px; --rhine-logo-y:var(--rhine-logo-travel); }
|
|
92
|
+
.rhine-logo[data-animate=true] .rhine-logo__plate { animation:rhine-logo-enter var(--rhine-duration-brand) var(--rhine-ease-standard) both; }
|
|
93
|
+
@keyframes rhine-logo-enter { from { opacity:0; transform:translate(var(--rhine-logo-x),var(--rhine-logo-y)); } to { opacity:1; transform:translate(0,0); } }
|
|
94
94
|
@media(prefers-color-scheme:dark) {
|
|
95
|
-
.
|
|
96
|
-
.
|
|
95
|
+
.rhine-logo[data-theme=auto] { color:var(--rhine-bg); }
|
|
96
|
+
.rhine-logo--icon[data-theme=auto] { background:var(--rhine-green); }
|
|
97
97
|
}
|
|
98
|
-
@media(prefers-reduced-motion:reduce) { .
|
|
99
|
-
@keyframes
|
|
100
|
-
@keyframes
|
|
101
|
-
@keyframes
|
|
102
|
-
.
|
|
103
|
-
.
|
|
104
|
-
.
|
|
98
|
+
@media(prefers-reduced-motion:reduce) { .rhine-logo[data-animate=true] .rhine-logo__plate { animation:none; } }
|
|
99
|
+
@keyframes rhine-enter { from { opacity:0; transform:translateY(var(--rhine-motion-distance)); } to { opacity:1; transform:translateY(0); } }
|
|
100
|
+
@keyframes rhine-progress { from { transform:translateX(-100%); } to { transform:translateX(340%); } }
|
|
101
|
+
@keyframes rhine-pulse { 50% { opacity:.55; } }
|
|
102
|
+
.rhine-theme[data-rhine-motion=reduced] { --rhine-duration-hover:0ms; --rhine-duration-press:0ms; --rhine-duration-panel:0ms; --rhine-duration-notice:0ms; --rhine-duration-progress:0ms; }
|
|
103
|
+
.rhine-theme[data-rhine-motion=reduced] *, .rhine-theme[data-rhine-motion=reduced] *::before, .rhine-theme[data-rhine-motion=reduced] *::after { animation:none!important; transition:none!important; }
|
|
104
|
+
.rhine-theme[data-rhine-motion=reduced] .rhine-button:active { transform:none; }
|
|
105
105
|
@media(prefers-reduced-motion:reduce) {
|
|
106
|
-
.
|
|
107
|
-
.
|
|
108
|
-
.
|
|
106
|
+
.rhine-theme { --rhine-duration-hover:0ms; --rhine-duration-press:0ms; --rhine-duration-panel:0ms; --rhine-duration-notice:0ms; --rhine-duration-progress:0ms; }
|
|
107
|
+
.rhine-theme *, .rhine-theme *::before, .rhine-theme *::after { animation:none!important; transition:none!important; }
|
|
108
|
+
.rhine-theme .rhine-button:active { transform:none; }
|
|
109
109
|
}
|