Skip to content

Commit c178f83

Browse files
committed
[toast] Add ability to anchor to an element
1 parent 9cd9ea1 commit c178f83

File tree

22 files changed

+1065
-11
lines changed

22 files changed

+1065
-11
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "ToastArrow",
3+
"description": "Displays an element positioned against the toast anchor.\nRenders a `<div>` element.",
4+
"props": {
5+
"style": {
6+
"type": "CSSProperties | ((state: Toast.Arrow.State) => CSSProperties | undefined)",
7+
"detailedType": "| React.CSSProperties\n| ((state: Toast.Arrow.State) => CSSProperties | undefined)\n| undefined"
8+
},
9+
"className": {
10+
"type": "string | ((state: Toast.Arrow.State) => string | undefined)",
11+
"description": "CSS class applied to the element, or a function that\nreturns a class based on the component’s state.",
12+
"detailedType": "| string\n| ((state: Toast.Arrow.State) => string | undefined)"
13+
},
14+
"render": {
15+
"type": "ReactElement | ((props: HTMLProps, state: Toast.Arrow.State) => ReactElement)",
16+
"description": "Allows you to replace the component’s HTML element\nwith a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render.",
17+
"detailedType": "| ReactElement\n| ((\n props: HTMLProps,\n state: Toast.Arrow.State,\n ) => ReactElement)"
18+
}
19+
},
20+
"dataAttributes": {
21+
"data-uncentered": {
22+
"description": "Present when the toast arrow is uncentered."
23+
},
24+
"data-anchor-hidden": {
25+
"description": "Present when the anchor is hidden."
26+
},
27+
"data-align": {
28+
"description": "Indicates how the popup is aligned relative to specified side.",
29+
"type": "'start' | 'center' | 'end'"
30+
},
31+
"data-side": {
32+
"description": "Indicates which side the popup is positioned relative to the anchor.",
33+
"type": "'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'"
34+
}
35+
},
36+
"cssVariables": {}
37+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
{
2+
"name": "ToastPositioner",
3+
"description": "Positions the toast against the anchor.\nRenders a `<div>` element.",
4+
"props": {
5+
"collisionAvoidance": {
6+
"type": "CollisionAvoidance",
7+
"description": "Determines how to handle collisions when positioning the popup.",
8+
"example": "```jsx\n<Positioner\n collisionAvoidance={{\n side: 'shift',\n align: 'shift',\n fallbackAxisSide: 'none',\n }}\n/>\n```",
9+
"detailedType": "| {\n side?: 'none' | 'flip'\n align?: 'none' | 'flip' | 'shift'\n fallbackAxisSide?: 'none' | 'end' | 'start'\n }\n| {\n side?: 'none' | 'shift'\n align?: 'none' | 'shift'\n fallbackAxisSide?: 'none' | 'end' | 'start'\n }\n| undefined"
10+
},
11+
"style": {
12+
"type": "CSSProperties | ((state: Toast.Positioner.State) => CSSProperties | undefined)",
13+
"detailedType": "| React.CSSProperties\n| ((\n state: Toast.Positioner.State,\n ) => CSSProperties | undefined)\n| undefined"
14+
},
15+
"toast": {
16+
"type": "ToastObject<any>",
17+
"required": true,
18+
"description": "The toast object associated with the positioner.",
19+
"detailedType": "id: string\n ref?: RefObject<HTMLElement | null>\n title?: ReactNode\n type?: string\n description?: ReactNode\n timeout?: number\n priority?: 'high' | 'low'\n transitionStatus?: 'starting' | 'ending'\n limited?: boolean\n height?: number\n onClose?: () => void\n onRemove?: () => void\n actionProps?: Omit<\n DetailedHTMLProps<\n ButtonHTMLAttributes<HTMLButtonElement>,\n HTMLButtonElement\n >,\n 'ref'\n >\n positionerProps?: Toast.Positioner.Options\n data?: any\n}"
20+
},
21+
"trigger": {
22+
"type": "Element | null",
23+
"description": "The trigger element for the toast. Falls back to the anchor element when not provided.",
24+
"detailedType": "Element | null | undefined"
25+
},
26+
"align": {
27+
"type": "Align",
28+
"default": "'center'",
29+
"description": "How to align the popup relative to the specified side.",
30+
"detailedType": "'center' | 'end' | 'start' | undefined"
31+
},
32+
"alignOffset": {
33+
"type": "number | OffsetFunction",
34+
"default": "0",
35+
"description": "Additional offset along the alignment axis in pixels.\nAlso accepts a function that returns the offset to read the dimensions of the anchor\nand positioner elements, along with its side and alignment.\n\nThe function takes a `data` object parameter with the following properties:\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\n- `data.side`: which side of the anchor element the positioner is aligned against.\n- `data.align`: how the positioner is aligned relative to the specified side.",
36+
"example": "```jsx\n<Positioner\n alignOffset={({ side, align, anchor, positioner }) => {\n return side === 'top' || side === 'bottom'\n ? anchor.width\n : anchor.height;\n }}\n/>\n```",
37+
"detailedType": "| number\n| ((data: {\n side: Side\n align: Align\n anchor: { width: number; height: number }\n positioner: { width: number; height: number }\n }) => number)\n| undefined"
38+
},
39+
"side": {
40+
"type": "Side",
41+
"default": "'top'",
42+
"description": "Which side of the anchor element to align the toast against.\nMay automatically change to avoid collisions.",
43+
"detailedType": "| 'left'\n| 'right'\n| 'bottom'\n| 'top'\n| 'inline-end'\n| 'inline-start'\n| undefined"
44+
},
45+
"sideOffset": {
46+
"type": "number | OffsetFunction",
47+
"default": "0",
48+
"description": "Distance between the anchor and the popup in pixels.\nAlso accepts a function that returns the distance to read the dimensions of the anchor\nand positioner elements, along with its side and alignment.\n\nThe function takes a `data` object parameter with the following properties:\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\n- `data.side`: which side of the anchor element the positioner is aligned against.\n- `data.align`: how the positioner is aligned relative to the specified side.",
49+
"example": "```jsx\n<Positioner\n sideOffset={({ side, align, anchor, positioner }) => {\n return side === 'top' || side === 'bottom'\n ? anchor.height\n : anchor.width;\n }}\n/>\n```",
50+
"detailedType": "| number\n| ((data: {\n side: Side\n align: Align\n anchor: { width: number; height: number }\n positioner: { width: number; height: number }\n }) => number)\n| undefined"
51+
},
52+
"arrowPadding": {
53+
"type": "number",
54+
"default": "5",
55+
"description": "Minimum distance to maintain between the arrow and the edges of the popup.\n\nUse it to prevent the arrow element from hanging out of the rounded corners of a popup.",
56+
"detailedType": "number | undefined"
57+
},
58+
"anchor": {
59+
"type": "Element | null",
60+
"description": "An element to position the toast against.",
61+
"detailedType": "Element | null | undefined"
62+
},
63+
"collisionBoundary": {
64+
"type": "Boundary",
65+
"default": "'clipping-ancestors'",
66+
"description": "An element or a rectangle that delimits the area that the popup is confined to.",
67+
"detailedType": "| Element\n| 'clipping-ancestors'\n| Element[]\n| Rect\n| undefined"
68+
},
69+
"collisionPadding": {
70+
"type": "Padding",
71+
"default": "5",
72+
"description": "Additional space to maintain from the edge of the collision boundary.",
73+
"detailedType": "| {\n top?: number\n right?: number\n bottom?: number\n left?: number\n }\n| number\n| undefined"
74+
},
75+
"sticky": {
76+
"type": "boolean",
77+
"default": "false",
78+
"description": "Whether to maintain the popup in the viewport after\nthe anchor element was scrolled out of view.",
79+
"detailedType": "boolean | undefined"
80+
},
81+
"positionMethod": {
82+
"type": "'fixed' | 'absolute'",
83+
"default": "'absolute'",
84+
"description": "Determines which CSS `position` property to use.",
85+
"detailedType": "'fixed' | 'absolute' | undefined"
86+
},
87+
"trackAnchor": {
88+
"type": "boolean",
89+
"default": "true",
90+
"description": "Whether the popup tracks any layout shift of its positioning anchor.",
91+
"detailedType": "boolean | undefined"
92+
},
93+
"className": {
94+
"type": "string | ((state: Toast.Positioner.State) => string | undefined)",
95+
"description": "CSS class applied to the element, or a function that\nreturns a class based on the component’s state.",
96+
"detailedType": "| string\n| ((state: Toast.Positioner.State) => string | undefined)"
97+
},
98+
"render": {
99+
"type": "ReactElement | ((props: HTMLProps, state: Toast.Positioner.State) => ReactElement)",
100+
"description": "Allows you to replace the component’s HTML element\nwith a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render.",
101+
"detailedType": "| ReactElement\n| ((\n props: HTMLProps,\n state: Toast.Positioner.State,\n ) => ReactElement)"
102+
}
103+
},
104+
"dataAttributes": {},
105+
"cssVariables": {}
106+
}

docs/reference/generated/toast-root.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"type": "Toast.Root.ToastObject<any>",
1717
"required": true,
1818
"description": "The toast to render.",
19-
"detailedType": "id: string\n ref?: RefObject<HTMLElement | null>\n title?: ReactNode\n type?: string\n description?: ReactNode\n timeout?: number\n priority?: 'high' | 'low'\n transitionStatus?: 'starting' | 'ending'\n limited?: boolean\n height?: number\n onClose?: () => void\n onRemove?: () => void\n actionProps?: Omit<\n DetailedHTMLProps<\n ButtonHTMLAttributes<HTMLButtonElement>,\n HTMLButtonElement\n >,\n 'ref'\n >\n data?: any\n}"
19+
"detailedType": "id: string\n ref?: RefObject<HTMLElement | null>\n title?: ReactNode\n type?: string\n description?: ReactNode\n timeout?: number\n priority?: 'high' | 'low'\n transitionStatus?: 'starting' | 'ending'\n limited?: boolean\n height?: number\n onClose?: () => void\n onRemove?: () => void\n actionProps?: Omit<\n DetailedHTMLProps<\n ButtonHTMLAttributes<HTMLButtonElement>,\n HTMLButtonElement\n >,\n 'ref'\n >\n positionerProps?: Toast.Positioner.Options\n data?: any\n}"
2020
},
2121
"className": {
2222
"type": "string | ((state: Toast.Root.State) => string | undefined)",

docs/reference/generated/tooltip-positioner.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,6 @@
9191
}
9292
},
9393
"dataAttributes": {
94-
"data-open": {
95-
"description": "Present when the tooltip is open."
96-
},
97-
"data-closed": {
98-
"description": "Present when the tooltip is closed."
99-
},
10094
"data-anchor-hidden": {
10195
"description": "Present when the anchor is hidden."
10296
},
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
.Button {
2+
box-sizing: border-box;
3+
display: flex;
4+
align-items: center;
5+
justify-content: center;
6+
width: 2.5rem;
7+
height: 2.5rem;
8+
padding: 0;
9+
margin: 0;
10+
outline: 0;
11+
border: 1px solid var(--color-gray-200);
12+
border-radius: 0.375rem;
13+
background-color: var(--color-gray-50);
14+
font-family: inherit;
15+
color: var(--color-gray-900);
16+
user-select: none;
17+
18+
@media (hover: hover) {
19+
&:hover {
20+
background-color: var(--color-gray-100);
21+
}
22+
}
23+
24+
&:active {
25+
background-color: var(--color-gray-100);
26+
}
27+
28+
&:focus-visible {
29+
outline: 2px solid var(--color-blue);
30+
outline-offset: -1px;
31+
}
32+
}
33+
34+
.Toast {
35+
box-sizing: border-box;
36+
font-size: 0.875rem;
37+
line-height: 1.25rem;
38+
display: flex;
39+
flex-direction: column;
40+
width: max-content;
41+
padding: 0.25rem 0.5rem;
42+
border-radius: 0.375rem;
43+
background-color: canvas;
44+
transform-origin: var(--transform-origin);
45+
transition:
46+
transform 150ms,
47+
opacity 150ms;
48+
49+
&[data-starting-style],
50+
&[data-ending-style] {
51+
opacity: 0;
52+
transform: scale(0.9);
53+
}
54+
55+
@media (prefers-color-scheme: light) {
56+
outline: 1px solid var(--color-gray-200);
57+
box-shadow:
58+
0 10px 15px -3px var(--color-gray-200),
59+
0 4px 6px -4px var(--color-gray-200);
60+
}
61+
62+
@media (prefers-color-scheme: dark) {
63+
outline: 1px solid var(--color-gray-300);
64+
outline-offset: -1px;
65+
}
66+
}
67+
68+
.Tooltip {
69+
box-sizing: border-box;
70+
font-size: 0.875rem;
71+
line-height: 1.25rem;
72+
display: flex;
73+
flex-direction: column;
74+
padding: 0.25rem 0.5rem;
75+
border-radius: 0.375rem;
76+
background-color: canvas;
77+
transform-origin: var(--transform-origin);
78+
transition:
79+
transform 150ms,
80+
opacity 150ms;
81+
82+
&[data-starting-style],
83+
&[data-ending-style] {
84+
opacity: 0;
85+
transform: scale(0.9);
86+
}
87+
88+
&[data-instant] {
89+
transition-duration: 0ms;
90+
}
91+
92+
@media (prefers-color-scheme: light) {
93+
outline: 1px solid var(--color-gray-200);
94+
box-shadow:
95+
0 10px 15px -3px var(--color-gray-200),
96+
0 4px 6px -4px var(--color-gray-200);
97+
}
98+
99+
@media (prefers-color-scheme: dark) {
100+
outline: 1px solid var(--color-gray-300);
101+
outline-offset: -1px;
102+
}
103+
}
104+
105+
.Arrow {
106+
display: flex;
107+
108+
&[data-side='top'] {
109+
bottom: -8px;
110+
rotate: 180deg;
111+
}
112+
113+
&[data-side='bottom'] {
114+
top: -8px;
115+
rotate: 0deg;
116+
}
117+
118+
&[data-side='left'] {
119+
right: -13px;
120+
rotate: 90deg;
121+
}
122+
123+
&[data-side='right'] {
124+
left: -13px;
125+
rotate: -90deg;
126+
}
127+
}
128+
129+
.ArrowFill {
130+
fill: canvas;
131+
}
132+
133+
.ArrowOuterStroke {
134+
@media (prefers-color-scheme: light) {
135+
fill: var(--color-gray-200);
136+
}
137+
}
138+
139+
.ArrowInnerStroke {
140+
@media (prefers-color-scheme: dark) {
141+
fill: var(--color-gray-300);
142+
}
143+
}
144+
145+
.Title {
146+
font-weight: 500;
147+
margin: 0;
148+
}
149+
150+
.Icon {
151+
width: 1.25rem;
152+
height: 1.25rem;
153+
}
154+
155+
.Viewport {
156+
position: fixed;
157+
z-index: 1;
158+
outline: 0;
159+
}

0 commit comments

Comments
 (0)