Merge remote-tracking branch 'origin/master' into mergebot/pr908
# Conflicts: # packages/client/ui-conversation/README.i18n.yaml
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: 3ff2717af7eeb6ef7f85c24456c7fe23b09d0faa
|
||||
README.zh.md: 56c4e9f1dae3eee1dc7ea64616e2ed4d536928e2
|
||||
README.md: 0ef3c20f848b3d331c007911d0837f11cd72c024
|
||||
README.zh.md: af94551bfb9e12dbadcef6a96a54f9bf7ea71299
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Pure React atoms (zero cordis): StateDot, ic_ds_* icons, Button/Pill/Menu/Modal/Input, the markdown family (MessageText/MarkdownText/JsonBlock), the read-only JsonTree inspector, and TerminalBlock. Contract: api-contracts v3 §8.
|
||||
Pure React atoms (zero cordis): StateDot, ic_ds_* icons, Button/Pill/Menu/Modal/Input, 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), and TerminalBlock. Contract: api-contracts v3 §8.
|
||||
|
||||
## Markdown rendering
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
纯 React 原子组件(零 cordis):StateDot、ic_ds_* 图标、Button/Pill/Menu/Modal/Input、markdown 家族(MessageText/MarkdownText/JsonBlock)、只读 JsonTree 检查器,以及 TerminalBlock。契约:api-contracts v3 §8。
|
||||
纯 React 原子组件(零 cordis):StateDot、ic_ds_* 图标、Button/Pill/Menu/Modal/Input、markdown 家族(MessageText/MarkdownText/JsonBlock)、只读 JsonTree 检查器、`useAnchoredMaxHeight` hook(把底部锚定的浮层高度收敛到锚点上方的视口空间,并在 resize、scroll 与调用方提供的依赖变化时重新测量),以及 TerminalBlock。契约:api-contracts v3 §8。
|
||||
|
||||
## Markdown 渲染
|
||||
|
||||
|
||||
@@ -512,6 +512,18 @@ export const IconPlayOutline16 = ({ size = 16, className }: IconProps) => (
|
||||
</svg>
|
||||
)
|
||||
|
||||
/** ic_ds_pause_outline_16 */
|
||||
export const IconPauseOutline16 = ({ size = 16, className }: IconProps) => (
|
||||
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M14.1448 8.00024C14.1448 4.60644 11.394 1.85563 8.00024 1.85563C4.60644 1.85563 1.85563 4.60644 1.85563 8.00024C1.85563 11.394 4.60644 14.1448 8.00024 14.1448C11.394 14.1448 14.1448 11.394 14.1448 8.00024ZM15.5112 8.00024C15.5112 12.1482 12.1482 15.5112 8.00024 15.5112C3.85226 15.5112 0.489258 12.1482 0.489258 8.00024C0.489258 3.85226 3.85226 0.489258 8.00024 0.489258C12.1482 0.489258 15.5112 3.85226 15.5112 8.00024Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path d="M7.14244 5.14258V10.8569H5.71387V5.14258H7.14244Z" fill="currentColor" />
|
||||
<path d="M10.286 5.14258V10.8569H8.85742V5.14258H10.286Z" fill="currentColor" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
/** ic_ds_fullscreen_outline_16 */
|
||||
export const IconFullscreenOutline16 = ({ size = 16, className }: IconProps) => (
|
||||
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
@@ -10,6 +10,7 @@ export { Pill } from './Pill.tsx'
|
||||
export { Input } from './Input.tsx'
|
||||
export { Menu } from './Menu.tsx'
|
||||
export type { MenuEntry, MenuItem, MenuSeparator, MenuLabel } from './Menu.tsx'
|
||||
export { useAnchoredMaxHeight } from './useAnchoredMaxHeight.ts'
|
||||
export { HoverCard } from './HoverCard.tsx'
|
||||
export { Modal } from './Modal.tsx'
|
||||
export { ConnectionBanner } from './ConnectionBanner.tsx'
|
||||
|
||||
38
packages/client/ui-primitives/src/useAnchoredMaxHeight.ts
Normal file
38
packages/client/ui-primitives/src/useAnchoredMaxHeight.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Viewport-fit hook for bottom-anchored overlays (slash menu, popupSelect):
|
||||
* the element's bottom edge is laid out independent of its height, so it
|
||||
* grows upward and only the top edge can collide with the viewport — clamp
|
||||
* the design cap to the space between that edge and the viewport top.
|
||||
*/
|
||||
import { useLayoutEffect, useState } from 'react'
|
||||
import type { RefObject } from 'react'
|
||||
|
||||
/** Safe distance kept between the overlay and the viewport top edge (mirrors the Menu portal margin). */
|
||||
const MARGIN = 12
|
||||
|
||||
/**
|
||||
* Clamp a bottom-anchored overlay's max-height to the viewport.
|
||||
* @param ref - the overlay element; a null current (overlay closed) skips measuring.
|
||||
* @param cap - design max-height in px (the clamp never exceeds it).
|
||||
* @param signal - re-measure trigger: pass the overlay's render state so anchor
|
||||
* moves (composer growth) re-fit; resize/scroll re-fit while mounted.
|
||||
* @returns the max-height to apply inline, in px.
|
||||
*/
|
||||
export function useAnchoredMaxHeight(ref: RefObject<HTMLElement>, cap: number, signal: unknown): number {
|
||||
const [maxHeight, setMaxHeight] = useState(cap)
|
||||
useLayoutEffect(() => {
|
||||
const el = ref.current
|
||||
if (el === null) return
|
||||
const fit = () => {
|
||||
setMaxHeight(Math.min(cap, Math.max(0, el.getBoundingClientRect().bottom - MARGIN)))
|
||||
}
|
||||
fit()
|
||||
window.addEventListener('resize', fit)
|
||||
window.addEventListener('scroll', fit, true)
|
||||
return () => {
|
||||
window.removeEventListener('resize', fit)
|
||||
window.removeEventListener('scroll', fit, true)
|
||||
}
|
||||
}, [ref, cap, signal])
|
||||
return maxHeight
|
||||
}
|
||||
@@ -14,8 +14,8 @@ const icons = Object.fromEntries(
|
||||
const iconNames = Object.keys(icons)
|
||||
|
||||
describe('ic_ds_ icon set', () => {
|
||||
it('exports the full P-I set (43 deepsuite + 13 figma extracts + the hand-authored sparkle)', () => {
|
||||
expect(iconNames.length).toBe(57)
|
||||
it('exports the full P-I set (44 deepsuite + 13 figma extracts + the hand-authored sparkle)', () => {
|
||||
expect(iconNames.length).toBe(58)
|
||||
})
|
||||
|
||||
it.each(iconNames)('%s renders an svg with currentColor fills and no hardcoded palette', (name) => {
|
||||
|
||||
Reference in New Issue
Block a user