Skip to content

Commit

Permalink
feat(injected-css): optionally export css for consumer to import
Browse files Browse the repository at this point in the history
  • Loading branch information
Shayne Preston committed Sep 20, 2024
1 parent 660060a commit 7f99e62
Show file tree
Hide file tree
Showing 10 changed files with 1,509 additions and 1,429 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ cypress/screenshots

# So devs can maintain their own todo lists in the project
TODO.md

# For those using yalc to test publishes locally
.yalc
yalc.lock
13 changes: 13 additions & 0 deletions .yarn/versions/d754df84.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
releases:
"@radix-ui/react-context-menu": patch
"@radix-ui/react-dropdown-menu": patch
"@radix-ui/react-hover-card": patch
"@radix-ui/react-menu": patch
"@radix-ui/react-menubar": patch
"@radix-ui/react-popover": patch
"@radix-ui/react-popper": minor
"@radix-ui/react-select": minor
"@radix-ui/react-tooltip": patch

declined:
- primitives
10 changes: 10 additions & 0 deletions build.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { globSync } from 'glob';
import * as esbuild from 'esbuild';
import * as tsup from 'tsup';
import { basename } from 'path';
import { copyFileSync } from 'fs';

async function build(path) {
const file = `${path}/src/index.ts`;
Expand Down Expand Up @@ -43,6 +45,14 @@ async function build(path) {
external: [/@radix-ui\/.+/],
});
console.log(`Built ${path}/dist/index.d.ts`);

const cssFiles = globSync(`${path}/src/**/*.css`);
cssFiles.forEach((cssFile) => {
const fileName = basename(cssFile);
const dest = `${dist}/${fileName}`;
copyFileSync(cssFile, dest);
console.log(`Copied ${cssFile} to ${dest}`);
});
}

globSync('packages/*/*').forEach(build);
4 changes: 4 additions & 0 deletions packages/react/popper/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./styles": {
"import": "./dist/popperStyles.css",
"require": "./dist/popperStyles.css"
}
},
"source": "./src/index.ts",
Expand Down
72 changes: 45 additions & 27 deletions packages/react/popper/src/Popper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -224,33 +224,56 @@ const PopperContent = React.forwardRef<PopperContentElement, PopperContentProps>
const arrowY = middlewareData.arrow?.y;
const cannotCenterArrow = middlewareData.arrow?.centerOffset !== 0;

const [contentZIndex, setContentZIndex] = React.useState<string>();
const updateStyleVariables = React.useCallback(() => {
if (refs.floating.current) {
if (content) {
refs.floating.current.style.zIndex = window.getComputedStyle(content).zIndex;
}

/* if the PopperContent hasn't been placed yet (not all measurements done)
* we prevent animations so user's animation don't kick in too early referring wrong sides
*/
refs.floating.current.style.setProperty(
'--popper-content-animation',
isPositioned ? 'none' : 'unset'
);
/* Keep off page when measuring */
refs.floating.current.style.transform = isPositioned
? (floatingStyles.transform as string)
: 'translate(0, -200%)';

refs.floating.current.style.setProperty(
'--radix-popper-transform-origin',
`${middlewareData.transformOrigin?.x} ${middlewareData.transformOrigin?.y}`
);

/* hide the content if using the hid emiddleware and should be hidden
* set visibility to hidden and disable pointer events so the UI behaves
* as if the PopperContent isn't there at all
*/
refs.floating.current.style.visibility = middlewareData.hide?.referenceHidden
? 'hidden'
: 'unset';
refs.floating.current.style.pointerEvents = middlewareData.hide?.referenceHidden
? 'none'
: 'unset';

refs.floating.current.style.position = floatingStyles.position as string;
refs.floating.current.style.left = floatingStyles.left as string;
refs.floating.current.style.top = floatingStyles.top as string;
}
}, [content, floatingStyles, isPositioned, middlewareData, refs.floating]);

useLayoutEffect(() => {
if (content) setContentZIndex(window.getComputedStyle(content).zIndex);
}, [content]);
updateStyleVariables();
}, [updateStyleVariables]);

debugger;

return (
<div
ref={refs.setFloating}
data-radix-popper-content-wrapper=""
style={{
...floatingStyles,
transform: isPositioned ? floatingStyles.transform : 'translate(0, -200%)', // keep off the page when measuring
minWidth: 'max-content',
zIndex: contentZIndex,
['--radix-popper-transform-origin' as any]: [
middlewareData.transformOrigin?.x,
middlewareData.transformOrigin?.y,
].join(' '),

// hide the content if using the hide middleware and should be hidden
// set visibility to hidden and disable pointer events so the UI behaves
// as if the PopperContent isn't there at all
...(middlewareData.hide?.referenceHidden && {
visibility: 'hidden',
pointerEvents: 'none',
}),
}}
// Floating UI interally calculates logical alignment based the `dir` attribute on
// the reference/floating node, we must add this attribute here to ensure
// this is calculated when portalled as well as inline.
Expand All @@ -265,16 +288,11 @@ const PopperContent = React.forwardRef<PopperContentElement, PopperContentProps>
shouldHideArrow={cannotCenterArrow}
>
<Primitive.div
data-radix-popper-content=""
data-side={placedSide}
data-align={placedAlign}
{...contentProps}
ref={composedRefs}
style={{
...contentProps.style,
// if the PopperContent hasn't been placed yet (not all measurements done)
// we prevent animations so that users's animation don't kick in too early referring wrong sides
animation: !isPositioned ? 'none' : undefined,
}}
/>
</PopperContentProvider>
</div>
Expand Down
11 changes: 11 additions & 0 deletions packages/react/popper/src/popperStyles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[data-radix-popper-content-wrapper] {
--popper-content-wrapper-z-index: 0;
--popper-content-animation: unset;

min-width: max-content;
z-index: var(--popper-content-wrapper-z-index);
}

[data-radix-popper-content] {
animation: var(--popper-content-animation);
}
4 changes: 4 additions & 0 deletions packages/react/select/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./styles": {
"import": "./dist/selectStyles.css",
"require": "./dist/selectStyles.css"
}
},
"source": "./src/index.ts",
Expand Down
86 changes: 20 additions & 66 deletions packages/react/select/src/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -338,13 +338,7 @@ const SelectValue = React.forwardRef<SelectValueElement, SelectValueProps>(
}, [onValueNodeHasChildrenChange, hasChildren]);

return (
<Primitive.span
{...valueProps}
ref={composedRefs}
// we don't want events from the portalled `SelectValue` children to bubble
// through the item they came from
style={{ pointerEvents: 'none' }}
>
<Primitive.span data-radix-select-value="" {...valueProps} ref={composedRefs}>
{shouldShowPlaceholder(context.value) ? <>{placeholder}</> : children}
</Primitive.span>
);
Expand Down Expand Up @@ -716,6 +710,7 @@ const SelectContentImpl = React.forwardRef<SelectContentImplElement, SelectConte
onDismiss={() => context.onOpenChange(false)}
>
<SelectPosition
data-radix-select-content-impl=""
role="listbox"
id={context.contentId}
data-state={context.open ? 'open' : 'closed'}
Expand All @@ -725,14 +720,6 @@ const SelectContentImpl = React.forwardRef<SelectContentImplElement, SelectConte
{...popperContentProps}
onPlaced={() => setIsPositioned(true)}
ref={composedRefs}
style={{
// flex layout so we can place the scroll buttons properly
display: 'flex',
flexDirection: 'column',
// reset the outline by default as the content MAY get focused
outline: 'none',
...contentProps.style,
}}
onKeyDown={composeEventHandlers(contentProps.onKeyDown, (event) => {
const isModifierKey = event.ctrlKey || event.altKey || event.metaKey;

Expand Down Expand Up @@ -927,11 +914,11 @@ const SelectItemAlignedPosition = React.forwardRef<

useLayoutEffect(() => position(), [position]);

// copy z-index from content to wrapper
const [contentZIndex, setContentZIndex] = React.useState<string>();
useLayoutEffect(() => {
if (content) setContentZIndex(window.getComputedStyle(content).zIndex);
}, [content]);
if (content && contentWrapper) {
contentWrapper.style.zIndex = window.getComputedStyle(content).zIndex;
}
}, [content, contentWrapper]);

// When the viewport becomes scrollable at the top, the scroll up button will mount.
// Because it is part of the normal flow, it will push down the viewport, thus throwing our
Expand All @@ -955,26 +942,11 @@ const SelectItemAlignedPosition = React.forwardRef<
shouldExpandOnScrollRef={shouldExpandOnScrollRef}
onScrollButtonChange={handleScrollButtonChange}
>
<div
ref={setContentWrapper}
style={{
display: 'flex',
flexDirection: 'column',
position: 'fixed',
zIndex: contentZIndex,
}}
>
<div data-radix-select-item-aligned-position="" ref={setContentWrapper}>
<Primitive.div
{...popperProps}
data-radix-select-item-aligned-popper=""
ref={composedRefs}
style={{
// When we get the height of the content, it includes borders. If we were to set
// the height without having `boxSizing: 'border-box'` it would be too big.
boxSizing: 'border-box',
// We need to ensure the content doesn't get taller than the wrapper
maxHeight: '100%',
...popperProps.style,
}}
/>
</div>
</SelectViewportProvider>
Expand Down Expand Up @@ -1007,24 +979,12 @@ const SelectPopperPosition = React.forwardRef<

return (
<PopperPrimitive.Content
data-radix-select-popper-position=""
{...popperScope}
{...popperProps}
ref={forwardedRef}
align={align}
collisionPadding={collisionPadding}
style={{
// Ensure border-box for floating-ui calculations
boxSizing: 'border-box',
...popperProps.style,
// re-namespace exposed content custom properties
...{
'--radix-select-content-transform-origin': 'var(--radix-popper-transform-origin)',
'--radix-select-content-available-width': 'var(--radix-popper-available-width)',
'--radix-select-content-available-height': 'var(--radix-popper-available-height)',
'--radix-select-trigger-width': 'var(--radix-popper-anchor-width)',
'--radix-select-trigger-height': 'var(--radix-popper-anchor-height)',
},
}}
/>
);
});
Expand All @@ -1050,39 +1010,33 @@ type SelectViewportElement = React.ElementRef<typeof Primitive.div>;
type PrimitiveDivProps = React.ComponentPropsWithoutRef<typeof Primitive.div>;
interface SelectViewportProps extends PrimitiveDivProps {
nonce?: string;
skipStyleInjection?: boolean;
}

const SelectViewport = React.forwardRef<SelectViewportElement, SelectViewportProps>(
(props: ScopedProps<SelectViewportProps>, forwardedRef) => {
const { __scopeSelect, nonce, ...viewportProps } = props;
const { __scopeSelect, nonce, skipStyleInjection, ...viewportProps } = props;
const contentContext = useSelectContentContext(VIEWPORT_NAME, __scopeSelect);
const viewportContext = useSelectViewportContext(VIEWPORT_NAME, __scopeSelect);
const composedRefs = useComposedRefs(forwardedRef, contentContext.onViewportChange);
const prevScrollTopRef = React.useRef(0);
return (
<>
{/* Hide scrollbars cross-browser and enable momentum scroll for touch devices */}
<style
dangerouslySetInnerHTML={{
__html: `[data-radix-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-select-viewport]::-webkit-scrollbar{display:none}`,
}}
nonce={nonce}
/>
{!skipStyleInjection && (
<style
dangerouslySetInnerHTML={{
__html: `[data-radix-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-select-viewport]::-webkit-scrollbar{display:none}`,
}}
nonce={nonce}
/>
)}
<Collection.Slot scope={__scopeSelect}>
<Primitive.div
data-radix-select-viewport=""
role="presentation"
{...viewportProps}
ref={composedRefs}
style={{
// we use position: 'relative' here on the `viewport` so that when we call
// `selectedItem.offsetTop` in calculations, the offset is relative to the viewport
// (independent of the scrollUpButton).
position: 'relative',
flex: 1,
overflow: 'auto',
...viewportProps.style,
}}
onScroll={composeEventHandlers(viewportProps.onScroll, (event) => {
const viewport = event.currentTarget;
const { contentWrapper, shouldExpandOnScrollRef } = viewportContext;
Expand Down Expand Up @@ -1509,9 +1463,9 @@ const SelectScrollButtonImpl = React.forwardRef<
return (
<Primitive.div
aria-hidden
data-radix-select-scroll-button-impl=""
{...scrollIndicatorProps}
ref={forwardedRef}
style={{ flexShrink: 0, ...scrollIndicatorProps.style }}
onPointerDown={composeEventHandlers(scrollIndicatorProps.onPointerDown, () => {
if (autoScrollTimerRef.current === null) {
autoScrollTimerRef.current = window.setInterval(onAutoScroll, 50);
Expand Down
Loading

0 comments on commit 7f99e62

Please sign in to comment.