Skip to content

Commit

Permalink
add ref forwarding (#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
charkour authored Jul 4, 2022
1 parent cbcbc51 commit 10bfabf
Show file tree
Hide file tree
Showing 29 changed files with 543 additions and 451 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ npm i @charkour/react-reactions
- [x] Add ability to pass **custom icons**
- [x] Fixed security vulnerabilities
- [x] CJS and ESM support
- [x] Add [ref forwarding](https://reactjs.org/docs/forwarding-refs.html) support
- [x] Zero dependencies
- [x] Built in Typescript and modern React (with [TSDX](https://github.com/formium/tsdx))
- [x] Works with React 16.8+ and Next 10
Expand Down
65 changes: 38 additions & 27 deletions src/components/custom/ReactionBarSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,45 @@ export interface ReactionBarSelectorProps {
style?: React.CSSProperties;
}

export const ReactionBarSelector: React.FC<ReactionBarSelectorProps> = ({
iconSize = defaultProps.iconSize,
reactions = defaultProps.reactions,
onSelect = defaultProps.onSelect,
style = defaultProps.style,
}) => {
const emojiStyle = React.useMemo(() => {
return {
width: `${iconSize + 10}px`,
height: `${iconSize + 10}px`,
display: 'flex',
alignItems: 'center',
fontSize: iconSize,
};
}, [iconSize]);
export const ReactionBarSelector = React.forwardRef<
HTMLDivElement,
ReactionBarSelectorProps
>(
(
{
iconSize = defaultProps.iconSize,
reactions = defaultProps.reactions,
onSelect = defaultProps.onSelect,
style = defaultProps.style,
},
ref
) => {
const emojiStyle = React.useMemo(() => {
return {
width: `${iconSize + 10}px`,
height: `${iconSize + 10}px`,
display: 'flex',
alignItems: 'center',
fontSize: iconSize,
};
}, [iconSize]);

return (
<div style={{ ...wrapStyle, ...style }}>
{reactions.map((reaction: Reaction) => {
return (
<div style={emojiStyle} key={reaction.key ?? reaction.label}>
<ReactionBarSelectorEmoji reaction={reaction} onSelect={onSelect} />
</div>
);
})}
</div>
);
};
return (
<div ref={ref} style={{ ...wrapStyle, ...style }}>
{reactions.map((reaction: Reaction) => {
return (
<div style={emojiStyle} key={reaction.key ?? reaction.label}>
<ReactionBarSelectorEmoji
reaction={reaction}
onSelect={onSelect}
/>
</div>
);
})}
</div>
);
}
);

export const defaultProps: Required<ReactionBarSelectorProps> = {
style: {},
Expand Down
12 changes: 6 additions & 6 deletions src/components/custom/ReactionBarSelectorEmoji.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ export interface ReactionBarSelectorEmojiProps {
onSelect: (label: string) => void;
}

export const ReactionBarSelectorEmoji: React.FC<ReactionBarSelectorEmojiProps> = ({
reaction,
onSelect,
}) => {
export const ReactionBarSelectorEmoji = React.forwardRef<
HTMLDivElement,
ReactionBarSelectorEmojiProps
>(({ reaction, onSelect }, ref) => {
const { node, label, key } = reaction;

const handleClick = () => {
onSelect && onSelect(key ?? label);
};

return (
<Hover style={wrapStyle}>
<Hover ref={ref} style={wrapStyle}>
<HoverStyle hoverStyle={labelStyleHover} style={labelStyle}>
{label}
</HoverStyle>
Expand All @@ -30,7 +30,7 @@ export const ReactionBarSelectorEmoji: React.FC<ReactionBarSelectorEmojiProps> =
</HoverStyle>
</Hover>
);
};
});

const wrapStyle: React.CSSProperties = {
padding: '5px',
Expand Down
129 changes: 69 additions & 60 deletions src/components/custom/ReactionCounter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,71 +18,80 @@ export interface ReactionCounterProps
style?: React.CSSProperties;
}

export const ReactionCounter: React.FC<ReactionCounterProps> = ({
reactions,
user,
important,
className,
onClick,
iconSize = 24,
bg = '#FFF',
showReactsOnly = false,
showTotalOnly = false,
showOthersAlways = true,
style,
}) => {
const groups = groupBy(reactions, 'label');
const names = reactions.map(({ by }: ReactionCounterObject) => {
return by;
});
export const ReactionCounter = React.forwardRef<
HTMLDivElement,
ReactionCounterProps
>(
(
{
reactions,
user,
important,
className,
onClick,
iconSize = 24,
bg = '#FFF',
showReactsOnly = false,
showTotalOnly = false,
showOthersAlways = true,
style,
},
ref
) => {
const groups = groupBy(reactions, 'label');
const names = reactions.map(({ by }: ReactionCounterObject) => {
return by;
});

const nameString = [];
if (user && names.includes(user)) {
nameString.push('You');
}
if (important?.length) {
if (names.includes(important[0])) {
nameString.push(important[0]);
const nameString = [];
if (user && names.includes(user)) {
nameString.push('You');
}
if (names.includes(important[1])) {
nameString.push(important[1]);
if (important?.length) {
if (names.includes(important[0])) {
nameString.push(important[0]);
}
if (names.includes(important[1])) {
nameString.push(important[1]);
}
}
const othersCount = names.length - nameString.length;
if (showOthersAlways || othersCount > 0) {
nameString.push(`${othersCount} other${othersCount === 1 ? '' : 's'}`);
}
}
const othersCount = names.length - nameString.length;
if (showOthersAlways || othersCount > 0) {
nameString.push(`${othersCount} other${othersCount === 1 ? '' : 's'}`);
}

const nameStyle: React.CSSProperties = React.useMemo(() => {
return {
fontSize: `${iconSize - 8}px`,
marginLeft: '8px',
};
}, [iconSize]);
const nameStyle: React.CSSProperties = React.useMemo(() => {
return {
fontSize: `${iconSize - 8}px`,
marginLeft: '8px',
};
}, [iconSize]);

return (
<div
style={{ ...counterStyle, ...style }}
className={className}
onClick={onClick}
>
{Object.keys(groups).map((reaction, i, reactions) => (
<ReactionCounterEmoji
key={i}
bg={bg}
iconSize={iconSize}
index={reactions.length - i}
node={groups[reaction][0].node}
/>
))}
{!showReactsOnly ? (
<div style={nameStyle}>
{showTotalOnly ? names.length : listOfNames(nameString)}
</div>
) : null}
</div>
);
};
return (
<div
ref={ref}
style={{ ...counterStyle, ...style }}
className={className}
onClick={onClick}
>
{Object.keys(groups).map((reaction, i, reactions) => (
<ReactionCounterEmoji
key={i}
bg={bg}
iconSize={iconSize}
index={reactions.length - i}
node={groups[reaction][0].node}
/>
))}
{!showReactsOnly ? (
<div style={nameStyle}>
{showTotalOnly ? names.length : listOfNames(nameString)}
</div>
) : null}
</div>
);
}
);

const counterStyle = {
display: 'flex',
Expand Down
14 changes: 6 additions & 8 deletions src/components/custom/ReactionCounterEmoji.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ export interface ReactionCounterEmojiProps {
index: number;
}

export const ReactionCounterEmoji: React.FC<ReactionCounterEmojiProps> = ({
node,
bg,
iconSize,
index,
}) => {
export const ReactionCounterEmoji = React.forwardRef<
HTMLDivElement,
ReactionCounterEmojiProps
>(({ node, bg, iconSize, index }, ref) => {
const emojiContainerStyle: React.CSSProperties = React.useMemo(() => {
return {
zIndex: index,
Expand All @@ -33,12 +31,12 @@ export const ReactionCounterEmoji: React.FC<ReactionCounterEmojiProps> = ({
}, [iconSize]);

return (
<div style={emojiContainerStyle}>
<div ref={ref} style={emojiContainerStyle}>
{React.cloneElement(node, {
style: { ...emojiStyle, ...node.props.style },
})}
</div>
);
};
});

export default ReactionCounterEmoji;
96 changes: 52 additions & 44 deletions src/components/facebook/FacebookCounter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,54 +12,62 @@ export interface FacebookCounterProps {
alwaysShowOthers?: boolean;
}

export const FacebookCounter: React.FC<FacebookCounterProps> = ({
counters = defaultProps.counters,
user = defaultProps.user,
important = defaultProps.important,
onClick = defaultProps.onClick,
bg = defaultProps.bg,
variant = defaultProps.variant,
alwaysShowOthers = defaultProps.alwaysShowOthers,
}) => {
const groups = groupBy(counters, 'emoji');
const names = counters.map(({ by }: CounterObject) => {
return by;
});
export const FacebookCounter = React.forwardRef<
HTMLDivElement,
FacebookCounterProps
>(
(
{
counters = defaultProps.counters,
user = defaultProps.user,
important = defaultProps.important,
onClick = defaultProps.onClick,
bg = defaultProps.bg,
variant = defaultProps.variant,
alwaysShowOthers = defaultProps.alwaysShowOthers,
},
ref
) => {
const groups = groupBy(counters, 'emoji');
const names = counters.map(({ by }: CounterObject) => {
return by;
});

const nameString = [];
if (names.includes(user)) {
nameString.push('You');
}
if (important?.length) {
if (names.includes(important[0])) {
nameString.push(important[0]);
const nameString = [];
if (names.includes(user)) {
nameString.push('You');
}
if (names.includes(important[1])) {
nameString.push(important[1]);
if (important?.length) {
if (names.includes(important[0])) {
nameString.push(important[0]);
}
if (names.includes(important[1])) {
nameString.push(important[1]);
}
}
const othersCount = names.length - nameString.length;
if (alwaysShowOthers || othersCount > 0) {
nameString.push(`${othersCount} other${othersCount === 1 ? '' : 's'}`);
}
}
const othersCount = names.length - nameString.length;
if (alwaysShowOthers || othersCount > 0) {
nameString.push(`${othersCount} other${othersCount === 1 ? '' : 's'}`);
}

return (
<div style={counterStyle} onClick={onClick}>
{Object.keys(groups).map((reaction, i, reactions) => {
return (
<FacebookCounterReaction
key={i}
reaction={reaction}
index={reactions.length - i}
bg={bg}
variant={variant}
/>
);
})}
<div style={nameStyle}>{listOfNames(nameString)}</div>
</div>
);
};
return (
<div ref={ref} style={counterStyle} onClick={onClick}>
{Object.keys(groups).map((reaction, i, reactions) => {
return (
<FacebookCounterReaction
key={i}
reaction={reaction}
index={reactions.length - i}
bg={bg}
variant={variant}
/>
);
})}
<div style={nameStyle}>{listOfNames(nameString)}</div>
</div>
);
}
);

export const defaultProps: Required<FacebookCounterProps> = {
important: [],
Expand Down
Loading

0 comments on commit 10bfabf

Please sign in to comment.