Merge remote-tracking branch 'origin/master' into feat/web-inline-file-mentions
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-primitives/README.md
|
||||
README.md: e6ca935426903ec0cc99f3fcacc1f937aa60b4ba
|
||||
README.zh.md: 4c23e7bd78a8ddde3e3cfe354203e81d3bb5b5e1
|
||||
README.md: 098a202a4ac9ee263ce1beaaee7a8624ebf26b80
|
||||
README.zh.md: 2b33af3316dede35cb5a226e41365f692a8b25d3
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Pure React atoms (zero cordis): StateDot, ic_ds_* icons, Button/Pill/Menu/Modal/Input, the OnboardingSurface first-run takeover (body-portaled mask + opaque stage that holds `#root` inert for exactly its own lifetime), the markdown family (MessageText/MarkdownText/JsonBlock), the read-only JsonTree inspector, the `useAnchoredMaxHeight` hook that clamps a bottom-anchored overlay to the viewport space above its anchor (re-measured on resize, scroll, and a caller-supplied dependency), TerminalBlock, DiffBlock, ReadBlock, SearchBlock, and WebBlock. Contract: api-contracts v3 §8.
|
||||
Pure React atoms (zero cordis): StateDot, DisclosureRow, ic_ds_* icons, Button/Pill/Menu/Modal/Input, the OnboardingSurface first-run takeover (body-portaled mask + opaque stage that holds `#root` inert for exactly its own lifetime), the markdown family (MessageText/MarkdownText/JsonBlock), the read-only JsonTree inspector, the `useAnchoredMaxHeight` hook that clamps a bottom-anchored overlay to the viewport space above its anchor (re-measured on resize, scroll, and a caller-supplied dependency), TerminalBlock, DiffBlock, ReadBlock, SearchBlock, and WebBlock. Contract: api-contracts v3 §8.
|
||||
|
||||
## Hover cards
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
纯 React 原子组件(零 cordis):StateDot、ic_ds_* 图标、Button/Pill/Menu/Modal/Input、OnboardingSurface 首次使用接管层(portal 到 body 的遮罩加不透明展示层,在自身生命周期内保持 `#root` 为 `inert`)、markdown 家族(MessageText/MarkdownText/JsonBlock)、只读 JsonTree 检查器、`useAnchoredMaxHeight` hook(把底部锚定的浮层高度收敛到锚点上方的视口空间,并在 resize、scroll 与调用方提供的依赖变化时重新测量)、TerminalBlock、DiffBlock、ReadBlock、SearchBlock,以及 WebBlock。契约:api-contracts v3 §8。
|
||||
纯 React 原子组件(零 cordis):StateDot、DisclosureRow、ic_ds_* 图标、Button/Pill/Menu/Modal/Input、OnboardingSurface 首次使用接管层(portal 到 body 的遮罩加不透明展示层,在自身生命周期内保持 `#root` 为 `inert`)、markdown 家族(MessageText/MarkdownText/JsonBlock)、只读 JsonTree 检查器、`useAnchoredMaxHeight` hook(把底部锚定的浮层高度收敛到锚点上方的视口空间,并在 resize、scroll 与调用方提供的依赖变化时重新测量)、TerminalBlock、DiffBlock、ReadBlock、SearchBlock,以及 WebBlock。契约:api-contracts v3 §8。
|
||||
|
||||
## 悬浮卡片
|
||||
|
||||
|
||||
69
packages/client/ui-primitives/src/DisclosureRow.module.css
Normal file
69
packages/client/ui-primitives/src/DisclosureRow.module.css
Normal file
@@ -0,0 +1,69 @@
|
||||
/* Shared disclosure header: [16px leading] gap 6 [title 14/24]. */
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.row {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.row[data-expandable] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.leading {
|
||||
position: relative;
|
||||
flex: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 6px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
}
|
||||
|
||||
button.leading {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.iconIdle {
|
||||
display: inline-flex;
|
||||
opacity: 1;
|
||||
transition: opacity 100ms ease;
|
||||
}
|
||||
|
||||
.chevronHover {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
margin: auto;
|
||||
opacity: 0;
|
||||
transition: opacity 100ms ease;
|
||||
}
|
||||
|
||||
.row:hover .iconIdle {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.row:hover .chevronHover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.title {
|
||||
flex: none;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
}
|
||||
104
packages/client/ui-primitives/src/DisclosureRow.tsx
Normal file
104
packages/client/ui-primitives/src/DisclosureRow.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import { type KeyboardEvent, type MouseEvent, type ReactNode } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import { IconChevronDownOutline14 } from './icons/index.tsx'
|
||||
import css from './DisclosureRow.module.css'
|
||||
|
||||
/** Shared 24px disclosure chrome for compact flow rows. */
|
||||
export interface DisclosureRowProps {
|
||||
icon: ReactNode
|
||||
title: string
|
||||
open: boolean
|
||||
expandable: boolean
|
||||
onToggle: () => void
|
||||
/** Makes the complete title row the disclosure target. */
|
||||
expandOnRowClick?: boolean | undefined
|
||||
/** Replaces the collapsed icon with a chevron while the row is hovered. */
|
||||
previewChevron?: boolean | undefined
|
||||
/** Keeps `collapsedContent` inline while open. */
|
||||
keepContentWhenOpen?: boolean | undefined
|
||||
collapsedContent?: ReactNode
|
||||
children?: ReactNode
|
||||
className?: string | undefined
|
||||
rowClassName?: string | undefined
|
||||
leadingClassName?: string | undefined
|
||||
chevronClassName?: string | undefined
|
||||
titleClassName?: string | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Render one disclosure header and its controlled expanded content.
|
||||
* @param props - Visual content, controlled state, and interaction policy.
|
||||
* @returns the disclosure row.
|
||||
*/
|
||||
export function DisclosureRow({
|
||||
icon,
|
||||
title,
|
||||
open,
|
||||
expandable,
|
||||
onToggle,
|
||||
expandOnRowClick = false,
|
||||
previewChevron = expandable,
|
||||
keepContentWhenOpen = false,
|
||||
collapsedContent,
|
||||
children,
|
||||
className,
|
||||
rowClassName,
|
||||
leadingClassName,
|
||||
chevronClassName,
|
||||
titleClassName,
|
||||
}: DisclosureRowProps) {
|
||||
const rowExpands = expandable && expandOnRowClick
|
||||
const toggleFromLeading = (event: MouseEvent<HTMLButtonElement>) => {
|
||||
event.stopPropagation()
|
||||
onToggle()
|
||||
}
|
||||
const toggleFromKeyboard = (event: KeyboardEvent<HTMLDivElement>) => {
|
||||
if (!rowExpands || (event.key !== 'Enter' && event.key !== ' ')) return
|
||||
event.preventDefault()
|
||||
onToggle()
|
||||
}
|
||||
const collapsedLeading = previewChevron
|
||||
? (
|
||||
<>
|
||||
<span className={css.iconIdle}>{icon}</span>
|
||||
<IconChevronDownOutline14 className={clsx(chevronClassName, css.chevronHover)} />
|
||||
</>
|
||||
)
|
||||
: icon
|
||||
const leading = open
|
||||
? <IconChevronDownOutline14 className={chevronClassName} />
|
||||
: collapsedLeading
|
||||
|
||||
return (
|
||||
<div className={clsx(css.root, className)} data-open={open || undefined}>
|
||||
<div
|
||||
className={clsx(css.row, rowClassName)}
|
||||
data-disclosure-row
|
||||
data-expandable={rowExpands || undefined}
|
||||
role={rowExpands ? 'button' : undefined}
|
||||
tabIndex={rowExpands ? 0 : undefined}
|
||||
aria-expanded={rowExpands ? open : undefined}
|
||||
onClick={rowExpands ? onToggle : undefined}
|
||||
onKeyDown={rowExpands ? toggleFromKeyboard : undefined}
|
||||
>
|
||||
{expandable && !rowExpands ? (
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(css.leading, leadingClassName)}
|
||||
aria-expanded={open}
|
||||
onClick={toggleFromLeading}
|
||||
>
|
||||
{leading}
|
||||
</button>
|
||||
) : (
|
||||
<span className={clsx(css.leading, leadingClassName)}>
|
||||
{leading}
|
||||
</span>
|
||||
)}
|
||||
<span className={clsx(css.title, titleClassName)}>{title}</span>
|
||||
{(keepContentWhenOpen || !open) && collapsedContent}
|
||||
</div>
|
||||
{open && children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
export { StateDot } from './StateDot.tsx'
|
||||
export type { StateDotState } from './StateDot.tsx'
|
||||
export { DisclosureRow } from './DisclosureRow.tsx'
|
||||
export type { DisclosureRowProps } from './DisclosureRow.tsx'
|
||||
export { Button } from './Button.tsx'
|
||||
export type { ButtonVariant } from './Button.tsx'
|
||||
export { Pill } from './Pill.tsx'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineConfig } from 'tsdown'
|
||||
import { clientOnly } from '../tsdown.client.ts'
|
||||
|
||||
/**
|
||||
* ui-primitives is browser-only, but its lib bundle IS imported under plain
|
||||
@@ -8,7 +8,7 @@ import { defineConfig } from 'tsdown'
|
||||
* (loader module table / vite source paths), which compile src directly and
|
||||
* never read lib.
|
||||
*/
|
||||
export default defineConfig({
|
||||
export default clientOnly([{
|
||||
entry: ['lib/types/index.js', 'lib/types/invariant.js'],
|
||||
outDir: 'lib',
|
||||
format: ['esm'],
|
||||
@@ -28,4 +28,4 @@ export default defineConfig({
|
||||
return 'export default {};'
|
||||
},
|
||||
}],
|
||||
})
|
||||
}])
|
||||
|
||||
Reference in New Issue
Block a user