-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
50 changed files
with
1,358 additions
and
6 deletions.
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
Binary file added
BIN
+18.4 KB
.yarn/cache/@floating-ui-react-dom-npm-2.1.1-d717a99d8f-6d1a023e6b.zip
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
examples/stackflow-spa/src/activities/ActivityHelpBubble.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import "@seed-design/stylesheet/helpBubble.css"; | ||
|
||
import type { ActivityComponentType } from "@stackflow/react"; | ||
|
||
import { usePopover } from "@seed-design/react-popover"; | ||
import { AppScreen } from "@stackflow/plugin-basic-ui"; | ||
import { BoxButton } from "../design-system/components"; | ||
import { helpBubble } from "@seed-design/recipe/helpBubble"; | ||
import { forwardRef } from "react"; | ||
|
||
export interface HelpBubbleArrowProps extends React.ComponentPropsWithRef<"svg"> { | ||
width: number; | ||
|
||
height: number; | ||
|
||
tipRadius: number; | ||
} | ||
|
||
const HelpBubbleArrow = forwardRef<SVGSVGElement, HelpBubbleArrowProps>((props, ref) => { | ||
const { width, height, tipRadius, ...otherProps } = props; | ||
const pathData = `M0,0 | ||
H${width} | ||
L${width / 2 + tipRadius},${height - tipRadius} | ||
Q${width / 2},${height} ${width / 2 - tipRadius},${height - tipRadius} | ||
Z`; | ||
|
||
return ( | ||
<svg | ||
aria-hidden="true" | ||
width={width} | ||
height={width} | ||
viewBox={`0 0 ${width} ${height > width ? height : width}`} | ||
ref={ref} | ||
{...otherProps} | ||
> | ||
<path stroke="none" d={pathData} /> | ||
</svg> | ||
); | ||
}); | ||
|
||
const ActivityHelpBubble: ActivityComponentType = () => { | ||
const api = usePopover({ | ||
defaultOpen: true, | ||
placement: "top", | ||
gutter: 4, | ||
overflowPadding: 16, | ||
arrowPadding: 14, | ||
}); | ||
const classNames = helpBubble(); | ||
|
||
const arrowRect = api.rects.arrow; | ||
|
||
return ( | ||
<AppScreen appBar={{ title: "HelpBubble" }}> | ||
<div style={{ overflowY: "auto", height: "200vh" }}> | ||
<div style={{ display: "flex", paddingTop: "20vh", justifyContent: "center" }}> | ||
<BoxButton ref={api.refs.trigger} {...api.triggerProps}> | ||
Wow | ||
</BoxButton> | ||
</div> | ||
{api.open && ( | ||
<div ref={api.refs.positioner} {...api.positionerProps} className={classNames.positioner}> | ||
<div className={classNames.content}> | ||
<div ref={api.refs.arrow} {...api.arrowProps} className={classNames.arrow}> | ||
<HelpBubbleArrow | ||
width={arrowRect?.width ?? 0} | ||
height={arrowRect?.height ?? 0} | ||
tipRadius={1} | ||
/> | ||
</div> | ||
<span className={classNames.title}>Title</span> | ||
<span className={classNames.description}>Description</span> | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
</AppScreen> | ||
); | ||
}; | ||
|
||
export default ActivityHelpBubble; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
base: | ||
enabled: | ||
root: | ||
cornerRadius: $radii[1.5] | ||
paddingX: $unit[3] | ||
paddingY: $unit[2] | ||
arrow: | ||
size: $unit[2.5] | ||
title: | ||
fontSize: $font-size[75] | ||
fontWeight: $font-weight.bold | ||
description: | ||
fontSize: $font-size[75] | ||
fontWeight: $font-weight.regular | ||
variant=non-modal: | ||
enabled: | ||
root: | ||
color: $color.bg.neutral-inverted | ||
arrow: | ||
color: $color.bg.neutral-inverted | ||
title: | ||
color: $color.fg.neutral-inverted | ||
description: | ||
color: $color.fg.neutral-inverted | ||
variant=modal: | ||
enabled: | ||
root: | ||
color: $color.bg.static-white | ||
arrow: | ||
color: $color.bg.static-white | ||
title: | ||
color: $color.fg.static-black | ||
description: | ||
color: $color.fg.static-black | ||
backdrop: | ||
color: $color.bg.overlay |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/lib/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"extends": "../tsconfig.json", | ||
"compilerOptions": { | ||
"rootDir": "../src", | ||
"module": "nodenext", | ||
"moduleResolution": "nodenext" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"extends": "./build.json", | ||
"include": [ | ||
"../src/**/*.ts", | ||
"../src/**/*.cts", | ||
"../src/**/*.tsx", | ||
"../src/**/*.json" | ||
], | ||
"exclude": [ | ||
"../src/**/*.mts", | ||
"../src/package.json" | ||
], | ||
"compilerOptions": { | ||
"outDir": "../.tshy-build/commonjs" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"extends": "./build.json", | ||
"include": [ | ||
"../src/**/*.ts", | ||
"../src/**/*.mts", | ||
"../src/**/*.tsx", | ||
"../src/**/*.json" | ||
], | ||
"exclude": [ | ||
"../src/package.json" | ||
], | ||
"compilerOptions": { | ||
"outDir": "../.tshy-build/esm" | ||
} | ||
} |
175 changes: 175 additions & 0 deletions
175
packages/react-headless/popover/dist/commonjs/floating.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import { | ||
arrow, | ||
autoUpdate, | ||
flip, | ||
limitShift, | ||
offset, | ||
shift, | ||
useFloating, | ||
type ElementRects, | ||
type Middleware, | ||
type Placement, | ||
type Rect, | ||
} from "@floating-ui/react"; | ||
import { useMemo, useState } from "react"; | ||
|
||
export interface PositioningOptions { | ||
/** | ||
* The strategy to use for positioning | ||
* @default "absolute" | ||
*/ | ||
strategy?: "absolute" | "fixed"; | ||
/** | ||
* The initial placement of the floating element | ||
* @default "bottom" | ||
*/ | ||
placement?: Placement; | ||
/** | ||
* The gutter between the floating element and the reference element | ||
*/ | ||
gutter?: number; | ||
/** | ||
* Whether to flip the placement | ||
* @default true | ||
*/ | ||
flip?: boolean | Placement[]; | ||
/** | ||
* Whether the popover should slide when it overflows. | ||
* @default true | ||
*/ | ||
slide?: boolean; | ||
/** | ||
* The virtual padding around the viewport edges to check for overflow | ||
* @default 8 | ||
*/ | ||
overflowPadding?: number; | ||
/** | ||
* The minimum padding between the arrow and the floating element's corner. | ||
* @default 4 | ||
*/ | ||
arrowPadding?: number; | ||
} | ||
|
||
const defaultPositioningOptions: PositioningOptions = { | ||
strategy: "absolute", | ||
placement: "bottom", | ||
flip: true, | ||
slide: true, | ||
overflowPadding: 8, | ||
arrowPadding: 4, | ||
}; | ||
|
||
function getArrowRect(arrowElement: HTMLElement | null): Rect | null { | ||
return arrowElement?.getClientRects().item(0) ?? null; | ||
} | ||
|
||
function getArrowMiddleware(arrowElement: HTMLElement | null, opts: PositioningOptions) { | ||
if (!arrowElement) return; | ||
return arrow({ element: arrowElement, padding: opts.arrowPadding }); | ||
} | ||
|
||
function getOffsetMiddleware(arrowOffset: number, opts: PositioningOptions) { | ||
const offsetMainAxis = (opts.gutter ?? 0) + arrowOffset; | ||
return offset(offsetMainAxis); | ||
} | ||
|
||
function getFlipMiddleware(opts: PositioningOptions) { | ||
if (!opts.flip) return; | ||
return flip({ | ||
padding: opts.overflowPadding, | ||
fallbackPlacements: opts.flip === true ? undefined : opts.flip, | ||
}); | ||
} | ||
|
||
function getShiftMiddleware(opts: PositioningOptions) { | ||
if (!opts.slide) return; | ||
return shift({ | ||
mainAxis: opts.slide, | ||
padding: opts.overflowPadding, | ||
limiter: limitShift(), | ||
}); | ||
} | ||
|
||
const rectMiddleware: Middleware = { | ||
name: "rects", | ||
fn({ rects }) { | ||
return { | ||
data: rects, | ||
}; | ||
}, | ||
}; | ||
|
||
export interface UsePositionedFloatingProps extends PositioningOptions { | ||
/** | ||
* Whether the floating element is initially open | ||
*/ | ||
defaultOpen?: boolean; | ||
/** | ||
* Whether the floating element is open | ||
*/ | ||
open?: boolean; | ||
/** | ||
* Callback when the floating element is opened or closed | ||
*/ | ||
onOpenChange?: (open: boolean) => void; | ||
} | ||
|
||
const ARROW_FLOATING_STYLE = { | ||
top: "", | ||
right: "rotate(90deg)", | ||
bottom: "rotate(180deg)", | ||
left: "rotate(270deg)", | ||
} as const; | ||
|
||
export function usePositionedFloating(props: UsePositionedFloatingProps) { | ||
const options = { ...defaultPositioningOptions, ...props }; | ||
|
||
const [uncontrolledOpen, setUncontrolledOpen] = useState(props.defaultOpen); | ||
const [arrowEl, setArrowEl] = useState<HTMLElement | null>(null); | ||
const arrowRect = useMemo(() => getArrowRect(arrowEl), [arrowEl]); | ||
const arrowOffset = arrowRect?.height ?? 0; | ||
|
||
const open = props.open ?? uncontrolledOpen; | ||
const onOpenChange = props.onOpenChange ?? setUncontrolledOpen; | ||
|
||
const { refs, context, floatingStyles, middlewareData } = useFloating({ | ||
strategy: options.strategy, | ||
open, | ||
placement: options.placement, | ||
onOpenChange: onOpenChange, | ||
whileElementsMounted: autoUpdate, | ||
middleware: [ | ||
getOffsetMiddleware(arrowOffset, options), | ||
getFlipMiddleware(options), | ||
getShiftMiddleware(options), | ||
getArrowMiddleware(arrowEl, options), | ||
rectMiddleware, | ||
], | ||
}); | ||
|
||
const side = context.placement.split("-")[0] as "top" | "bottom" | "left" | "right"; | ||
|
||
const arrowStyles = { | ||
position: "absolute", | ||
left: middlewareData.arrow?.x, | ||
top: middlewareData.arrow?.y, | ||
[side]: "100%", | ||
transform: ARROW_FLOATING_STYLE[side], | ||
} as const; | ||
|
||
return { | ||
open, | ||
refs: { | ||
...refs, | ||
arrow: arrowEl, | ||
setArrow: setArrowEl, | ||
}, | ||
rects: { | ||
...(middlewareData.rects as ElementRects), | ||
arrow: arrowRect, | ||
}, | ||
context, | ||
floatingStyles, | ||
arrowStyles, | ||
}; | ||
} |
Oops, something went wrong.