File tree Expand file tree Collapse file tree 6 files changed +330
-1
lines changed
shared/ContextPanel/Blocks Expand file tree Collapse file tree 6 files changed +330
-1
lines changed Original file line number Diff line number Diff line change 1+ import { type ReactNode } from "react" ;
2+
3+ import { Icon , type IconName } from "@/components/ui/icon" ;
4+ import { InlineStack } from "@/components/ui/layout" ;
5+
6+ import TooltipButton from "../../Buttons/TooltipButton" ;
7+
8+ export type Action = {
9+ label : string ;
10+ destructive ?: boolean ;
11+ hidden ?: boolean ;
12+ onClick : ( ) => void ;
13+ } & (
14+ | { icon : IconName ; content ?: never }
15+ | { content : ReactNode ; icon ?: never }
16+ ) ;
17+
18+ // Temporary: ReactNode included for backward compatibility with some existing buttons. In the long-term we should strive for only Action types.
19+ export type ActionOrReactNode = Action | ReactNode ;
20+
21+ interface ActionBlockProps {
22+ actions : ActionOrReactNode [ ] ;
23+ className ?: string ;
24+ }
25+
26+ export const ActionBlock = ( { actions, className } : ActionBlockProps ) => {
27+ return (
28+ < InlineStack gap = "2" className = { className } >
29+ { actions . map ( ( action , index ) => {
30+ if ( ! action || typeof action !== "object" || ! ( "label" in action ) ) {
31+ return < div key = { index } > { action } </ div > ;
32+ }
33+
34+ if ( action . hidden ) {
35+ return null ;
36+ }
37+
38+ return (
39+ < TooltipButton
40+ key = { action . label }
41+ variant = { action . destructive ? "destructive" : "outline" }
42+ tooltip = { action . label }
43+ onClick = { action . onClick }
44+ >
45+ { action . content === undefined && action . icon ? (
46+ < Icon name = { action . icon } />
47+ ) : (
48+ action . content
49+ ) }
50+ </ TooltipButton >
51+ ) ;
52+ } ) }
53+ </ InlineStack >
54+ ) ;
55+ } ;
Original file line number Diff line number Diff line change 1+ import { type ReactNode } from "react" ;
2+
3+ import { Button } from "@/components/ui/button" ;
4+ import {
5+ Collapsible ,
6+ CollapsibleContent ,
7+ CollapsibleTrigger ,
8+ } from "@/components/ui/collapsible" ;
9+ import { Icon } from "@/components/ui/icon" ;
10+ import { BlockStack , InlineStack } from "@/components/ui/layout" ;
11+ import { Heading } from "@/components/ui/typography" ;
12+
13+ type ContentBlockProps =
14+ | {
15+ title : string ;
16+ children ?: ReactNode ;
17+ collapsible ?: false ;
18+ defaultOpen ?: never ;
19+ className ?: string ;
20+ }
21+ | {
22+ title : string ;
23+ children ?: ReactNode ;
24+ collapsible : true ;
25+ defaultOpen ?: boolean ;
26+ className ?: string ;
27+ } ;
28+
29+ export const ContentBlock = ( {
30+ title,
31+ children,
32+ collapsible,
33+ defaultOpen = false ,
34+ className,
35+ } : ContentBlockProps ) => {
36+ if ( ! children ) {
37+ return null ;
38+ }
39+
40+ return (
41+ < BlockStack className = { className } >
42+ < Collapsible className = "w-full" defaultOpen = { defaultOpen } >
43+ < InlineStack blockAlign = "center" gap = "1" >
44+ < Heading level = { 3 } > { title } </ Heading >
45+ { collapsible && (
46+ < CollapsibleTrigger asChild >
47+ < Button variant = "ghost" size = "sm" >
48+ < Icon name = "ChevronsUpDown" />
49+ < span className = "sr-only" > Toggle</ span >
50+ </ Button >
51+ </ CollapsibleTrigger >
52+ ) }
53+ </ InlineStack >
54+
55+ { collapsible ? (
56+ < CollapsibleContent className = "w-full mt-1" >
57+ { children }
58+ </ CollapsibleContent >
59+ ) : (
60+ children
61+ ) }
62+ </ Collapsible >
63+ </ BlockStack >
64+ ) ;
65+ } ;
Original file line number Diff line number Diff line change 1+ import { InlineStack } from "@/components/ui/layout" ;
2+ import { Link } from "@/components/ui/link" ;
3+ import { Paragraph } from "@/components/ui/typography" ;
4+ import { cn } from "@/lib/utils" ;
5+
6+ import { CopyText } from "../../CopyText/CopyText" ;
7+
8+ export interface KeyValuePairProps {
9+ label ?: string ;
10+ value ?: string | { href : string ; text : string } ;
11+ critical ?: boolean ;
12+ copyable ?: boolean ;
13+ }
14+
15+ export const KeyValuePair = ( {
16+ label,
17+ value,
18+ critical,
19+ copyable,
20+ } : KeyValuePairProps ) => {
21+ if ( ! value ) {
22+ return null ;
23+ }
24+
25+ return (
26+ < InlineStack gap = "2" blockAlign = "center" wrap = "nowrap" >
27+ { label && (
28+ < Paragraph
29+ size = "xs"
30+ tone = { critical ? "critical" : "inherit" }
31+ className = "shrink-0 whitespace-nowrap"
32+ >
33+ { label } :
34+ </ Paragraph >
35+ ) }
36+
37+ < div className = "min-w-0 flex-1 overflow-hidden" >
38+ { isLink ( value ) ? (
39+ < Link
40+ href = { value . href }
41+ size = "xs"
42+ variant = "classic"
43+ external
44+ target = "_blank"
45+ rel = "noopener noreferrer"
46+ >
47+ { value . text }
48+ </ Link >
49+ ) : copyable ? (
50+ < CopyText
51+ className = { cn (
52+ "text-xs truncate" ,
53+ critical ? "text-destructive" : "text-muted-foreground" ,
54+ ) }
55+ >
56+ { value }
57+ </ CopyText >
58+ ) : (
59+ < Paragraph
60+ size = "xs"
61+ tone = { critical ? "critical" : "subdued" }
62+ className = "truncate"
63+ >
64+ { value }
65+ </ Paragraph >
66+ ) }
67+ </ div >
68+ </ InlineStack >
69+ ) ;
70+ } ;
71+
72+ const isLink = (
73+ val : string | { href : string ; text : string } ,
74+ ) : val is { href : string ; text : string } => {
75+ return typeof val === "object" && val !== null && "href" in val ;
76+ } ;
Original file line number Diff line number Diff line change 1+ import { BlockStack } from "@/components/ui/layout" ;
2+ import { Heading } from "@/components/ui/typography" ;
3+
4+ import { KeyValuePair , type KeyValuePairProps } from "./KeyValuePair" ;
5+
6+ interface ListBlockProps {
7+ title ?: string ;
8+ items : KeyValuePairProps [ ] ;
9+ marker ?: "bullet" | "number" | "none" ;
10+ className ?: string ;
11+ }
12+
13+ export const ListBlock = ( {
14+ title,
15+ items,
16+ marker = "bullet" ,
17+ className,
18+ } : ListBlockProps ) => {
19+ const listElement = marker === "number" ? "ol" : "ul" ;
20+
21+ const getListStyle = ( ) => {
22+ switch ( marker ) {
23+ case "bullet" :
24+ return "pl-5 list-disc" ;
25+ case "number" :
26+ return "pl-5 list-decimal" ;
27+ case "none" :
28+ return "list-none" ;
29+ default :
30+ return "pl-5 list-disc" ;
31+ }
32+ } ;
33+
34+ return (
35+ < BlockStack className = { className } >
36+ { title && < Heading level = { 3 } > { title } </ Heading > }
37+ < BlockStack as = { listElement } gap = "1" className = { getListStyle ( ) } >
38+ { items . map ( ( item , index ) => {
39+ if ( ! item . value ) {
40+ return null ;
41+ }
42+
43+ return (
44+ < li key = { index } >
45+ < KeyValuePair { ...item } />
46+ </ li >
47+ ) ;
48+ } ) }
49+ </ BlockStack >
50+ </ BlockStack >
51+ ) ;
52+ } ;
Original file line number Diff line number Diff line change 1+ import { Button } from "@/components/ui/button" ;
2+ import {
3+ Collapsible ,
4+ CollapsibleContent ,
5+ CollapsibleTrigger ,
6+ } from "@/components/ui/collapsible" ;
7+ import { Icon } from "@/components/ui/icon" ;
8+ import { BlockStack , InlineStack } from "@/components/ui/layout" ;
9+ import { Heading , Paragraph } from "@/components/ui/typography" ;
10+ import { cn } from "@/lib/utils" ;
11+
12+ import { CopyText } from "../../CopyText/CopyText" ;
13+
14+ interface TextBlockProps {
15+ title : string ;
16+ text ?: string ;
17+ copyable ?: boolean ;
18+ collapsible ?: boolean ;
19+ mono ?: boolean ;
20+ className ?: string ;
21+ }
22+
23+ export const TextBlock = ( {
24+ title,
25+ text,
26+ copyable,
27+ collapsible,
28+ mono,
29+ className,
30+ } : TextBlockProps ) => {
31+ if ( ! text ) {
32+ return null ;
33+ }
34+
35+ const content = copyable ? (
36+ < CopyText
37+ className = { cn ( "text-xs text-muted-foreground truncate" , {
38+ "font-mono" : mono ,
39+ } ) }
40+ >
41+ { text }
42+ </ CopyText >
43+ ) : (
44+ < Paragraph
45+ tone = "subdued"
46+ font = { mono ? "mono" : "default" }
47+ size = "xs"
48+ className = "truncate"
49+ >
50+ { text }
51+ </ Paragraph >
52+ ) ;
53+
54+ return (
55+ < BlockStack className = { className } >
56+ < Collapsible className = "w-full" >
57+ < InlineStack blockAlign = "center" gap = "1" >
58+ < Heading level = { 3 } > { title } </ Heading >
59+ { collapsible && (
60+ < CollapsibleTrigger asChild >
61+ < Button variant = "ghost" size = "sm" >
62+ < Icon name = "ChevronsUpDown" />
63+ < span className = "sr-only" > Toggle</ span >
64+ </ Button >
65+ </ CollapsibleTrigger >
66+ ) }
67+ </ InlineStack >
68+
69+ { collapsible ? (
70+ < CollapsibleContent className = "w-full mt-1" >
71+ { content }
72+ </ CollapsibleContent >
73+ ) : (
74+ content
75+ ) }
76+ </ Collapsible >
77+ </ BlockStack >
78+ ) ;
79+ } ;
Original file line number Diff line number Diff line change @@ -15,8 +15,10 @@ const iconVariants = cva("", {
1515 } ,
1616} ) ;
1717
18+ export type IconName = keyof typeof icons ;
19+
1820interface IconProps extends VariantProps < typeof iconVariants > {
19- name : keyof typeof icons ;
21+ name : IconName ;
2022 className ?: string ;
2123}
2224
You can’t perform that action at this time.
0 commit comments