diff --git a/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewDescription.tsx b/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewDescription.tsx
index da48d0c3ef..198e6aba22 100644
--- a/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewDescription.tsx
+++ b/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewDescription.tsx
@@ -1,3 +1,4 @@
+import { css } from '@rocket.chat/css-in-js';
import type { ReactNode } from 'react';
export type MessageGenericPreviewDescriptionProps = {
@@ -5,17 +6,37 @@ export type MessageGenericPreviewDescriptionProps = {
clamp?: boolean;
};
+const baseStyle = css`
+ font-family: var(--f-family-body);
+ font-size: var(--f-font-size-c1, 12px);
+ font-weight: var(--f-font-weight-c1, 400);
+ line-height: var(--f-line-height-c1, 16px);
+ margin-block-end: 4px;
+ white-space: normal;
+ color: var(--fontDefault);
+`;
+
+const truncatedStyle = css`
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+`;
+
+const clampStyle = css`
+ display: -webkit-box;
+ overflow: hidden;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+`;
+
const MessageGenericPreviewDescription = ({
children,
clamp = false,
}: MessageGenericPreviewDescriptionProps) => (
{children}
diff --git a/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewFooter.tsx b/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewFooter.tsx
index 28181126f1..615838e307 100644
--- a/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewFooter.tsx
+++ b/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewFooter.tsx
@@ -1,14 +1,33 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../../primitives';
export type MessageGenericPreviewFooterProps = {
children?: ReactNode;
clamp?: boolean;
};
+const FooterFrame = styled(RcxText, {
+ name: 'MessageGenericPreviewFooter',
+ fontFamily: '$body',
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ width: '100%',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+ color: '$fontSecondaryInfo',
+});
+
const MessageGenericPreviewFooter = ({
children,
}: MessageGenericPreviewFooterProps) => (
-
{children}
+
{children}
);
export default MessageGenericPreviewFooter;
diff --git a/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewIcon.tsx b/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewIcon.tsx
index 87107e623e..c3f6949546 100644
--- a/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewIcon.tsx
+++ b/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewIcon.tsx
@@ -1,19 +1,54 @@
+import { styled } from '@tamagui/core';
+
+import { RcxText, RcxView } from '../../../primitives';
import { Icon, type IconProps } from '../../Icon';
export type MessageGenericPreviewIconProps = IconProps & {
type: string;
};
+const IconContainer = styled(RcxView, {
+ name: 'MessageGenericPreviewIcon',
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: 'center',
+ flexShrink: 0,
+ alignSelf: 'center',
+ width: 48,
+ height: 52,
+ marginBlock: 12,
+ marginInlineStart: 16,
+ borderRadius: '$x4',
+ backgroundColor: '$surfaceNeutral',
+});
+
+const IconTitle = styled(RcxText, {
+ name: 'MessageGenericPreviewIconTitle',
+ fontFamily: '$body',
+ fontSize: '$micro',
+ fontWeight: '$micro',
+ lineHeight: '$micro',
+ letterSpacing: '$micro',
+ maxWidth: 40,
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+ color: '$fontDefault',
+});
+
const MessageGenericPreviewIcon = ({
name = 'attachment-file',
size = 32,
color = 'default',
type = 'file',
}: MessageGenericPreviewIconProps) => (
-
+
{type}
+
);
export default MessageGenericPreviewIcon;
diff --git a/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewImage.tsx b/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewImage.tsx
index f23bf7fcb2..baa39ae46f 100644
--- a/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewImage.tsx
+++ b/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewImage.tsx
@@ -1,7 +1,5 @@
import type { ImgHTMLAttributes } from 'react';
-import { prependClassName } from '../../../helpers/prependClassName';
-
export type MessageGenericPreviewImageProps = {
url: string;
className?: string;
@@ -9,15 +7,18 @@ export type MessageGenericPreviewImageProps = {
const MessageGenericPreviewImage = ({
url,
- className,
+ className: _className,
...props
}: MessageGenericPreviewImageProps) => (

diff --git a/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewThumb.tsx b/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewThumb.tsx
index 682ebf327f..3f2bdb9f12 100644
--- a/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewThumb.tsx
+++ b/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewThumb.tsx
@@ -1,9 +1,19 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../../primitives';
export type MessageGenericPreviewThumbProps = HTMLAttributes
;
+const ThumbFrame = styled(RcxView, {
+ name: 'MessageGenericPreviewThumb',
+ width: 96,
+ height: 96,
+ flexShrink: 0,
+});
+
const MessageGenericPreviewThumb = (props: MessageGenericPreviewThumbProps) => (
-
+
);
export default MessageGenericPreviewThumb;
diff --git a/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewTitle.tsx b/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewTitle.tsx
index 00ecce712e..1d3ac4b375 100644
--- a/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewTitle.tsx
+++ b/packages/fuselage/src/components/Message/MessageGenericPreview/MessageGenericPreviewTitle.tsx
@@ -1,10 +1,30 @@
import type { AnchorHTMLAttributes, HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../../primitives';
export type MessageGenericPreviewTitleProps = {
externalUrl?: string;
} & HTMLAttributes &
AnchorHTMLAttributes;
+const TitleFrame = styled(RcxText, {
+ name: 'MessageGenericPreviewTitle',
+ fontFamily: '$body',
+ fontSize: '$p2',
+ fontWeight: '$p2',
+ lineHeight: '$p2',
+ letterSpacing: '$p2',
+ display: 'block',
+ marginBlockEnd: 4,
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+ color: '$fontDefault',
+});
+
const MessageGenericPreviewTitle = ({
externalUrl,
children,
@@ -13,17 +33,29 @@ const MessageGenericPreviewTitle = ({
if (externalUrl) {
return (
{children}
);
}
- return ;
+ return {children};
};
export default MessageGenericPreviewTitle;
diff --git a/packages/fuselage/src/components/Message/MessageHeader.tsx b/packages/fuselage/src/components/Message/MessageHeader.tsx
index 22945dbb5c..40cec904f0 100644
--- a/packages/fuselage/src/components/Message/MessageHeader.tsx
+++ b/packages/fuselage/src/components/Message/MessageHeader.tsx
@@ -1,13 +1,35 @@
+import { styled } from '@tamagui/core';
import type { HTMLAttributes } from 'react';
+import { RcxView } from '../../primitives';
+
export type MessageHeaderProps = HTMLAttributes;
+const MessageHeaderFrame = styled(RcxView, {
+ name: 'MessageHeader',
+ display: 'flex',
+ flexDirection: 'row',
+ flexGrow: 0,
+ flexShrink: 1,
+ minWidth: 1,
+});
+
+const MessageHeaderWrapper = styled(RcxView, {
+ name: 'MessageHeaderWrapper',
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ flexWrap: 'wrap',
+ flexGrow: 1,
+ flexShrink: 1,
+ minWidth: 1,
+ gap: '$x4',
+});
+
const MessageHeader = ({ children, ...props }: MessageHeaderProps) => (
-
+
+ {children}
+
);
export default MessageHeader;
diff --git a/packages/fuselage/src/components/Message/MessageHighlight.tsx b/packages/fuselage/src/components/Message/MessageHighlight.tsx
index 422e1b3934..37b832fdba 100644
--- a/packages/fuselage/src/components/Message/MessageHighlight.tsx
+++ b/packages/fuselage/src/components/Message/MessageHighlight.tsx
@@ -1,7 +1,6 @@
+import { css } from '@rocket.chat/css-in-js';
import type { ElementType, HTMLAttributes, ReactNode } from 'react';
-import { prependClassName } from '../../helpers/prependClassName';
-
export type MessageHighlightProps = {
is?: ElementType;
clickable?: boolean;
@@ -11,6 +10,59 @@ export type MessageHighlightProps = {
title?: string;
} & HTMLAttributes;
+const variantStyles = {
+ critical: css`
+ color: var(--fontPureWhite);
+ &::before {
+ background-color: var(--badgeLevel4);
+ }
+ `,
+ relevant: css`
+ color: var(--fontPureWhite);
+ &::before {
+ background-color: var(--badgeLevel3);
+ }
+ `,
+ other: css`
+ color: var(--fontDefault);
+ &::before {
+ background-color: var(--badgeLevel0);
+ }
+ `,
+ link: css`
+ color: var(--fontInfo);
+ &::before {
+ background-color: var(--badgeLevel0);
+ }
+ `,
+};
+
+const baseStyle = css`
+ position: relative;
+ z-index: 1;
+ display: inline-block;
+ padding-inline: 2px;
+ white-space: nowrap;
+ word-break: keep-all;
+ font-weight: 500;
+ &::before {
+ position: absolute;
+ z-index: -1;
+ width: 100%;
+ height: 18px;
+ content: '';
+ transform: translateY(1px) translateX(-2px);
+ border-radius: var(--rcx-border-radius-medium, 4px);
+ }
+`;
+
+const clickableStyle = css`
+ cursor: pointer;
+ &:hover {
+ text-decoration: underline;
+ }
+`;
+
function MessageHighlight({
is: Tag = 'span',
variant = 'other',
@@ -18,17 +70,17 @@ function MessageHighlight({
clickable,
...props
}: MessageHighlightProps) {
- const modifiers = [variant, clickable && 'clickable']
- .filter(Boolean)
- .map((modifier) => `rcx-message__highlight--${modifier}`)
- .join(' ');
-
return (
);
diff --git a/packages/fuselage/src/components/Message/MessageLeftContainer.tsx b/packages/fuselage/src/components/Message/MessageLeftContainer.tsx
index 1fb2541b62..82ccd7e733 100644
--- a/packages/fuselage/src/components/Message/MessageLeftContainer.tsx
+++ b/packages/fuselage/src/components/Message/MessageLeftContainer.tsx
@@ -1,12 +1,23 @@
+import { styled } from '@tamagui/core';
import type { HTMLAttributes } from 'react';
+import { RcxView } from '../../primitives';
+
export type MessageLeftContainerProps = HTMLAttributes;
+const MessageLeftContainerFrame = styled(RcxView, {
+ name: 'MessageLeftContainer',
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'flex-end',
+ flexGrow: 0,
+ flexShrink: 0,
+ width: '$x36',
+ marginInline: '$x4',
+});
+
const MessageLeftContainer = (props: MessageLeftContainerProps) => (
-
+
);
export default MessageLeftContainer;
diff --git a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetrics.styles.scss b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetrics.styles.scss
deleted file mode 100644
index 0316258fb0..0000000000
--- a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetrics.styles.scss
+++ /dev/null
@@ -1,57 +0,0 @@
-@use '../../../styles/lengths.scss';
-@use '../../../styles/colors.scss';
-@use '../../../styles/typography.scss';
-
-.rcx-message-metrics {
- &__content,
- &__content-item {
- display: flex;
-
- margin-block: lengths.margin(4);
-
- & + & {
- margin-inline-start: lengths.margin(4);
- }
- }
-
- &__content-wrapper {
- display: flex;
-
- margin-inline: lengths.margin(-4);
- }
-
- &__item {
- @include typography.use-font-scale(micro);
-
- display: flex;
- justify-content: center;
- align-items: center;
-
- margin-inline: lengths.margin(4);
-
- color: colors.font(default);
-
- &-label {
- margin-inline-start: lengths.margin(4);
- }
-
- &__follow-badge {
- position: absolute;
- top: 0;
- right: 0;
-
- transform: translate(40%, -40%);
- }
- }
-
- &__avatar-row {
- display: flex;
- flex-direction: row;
-
- margin-inline: lengths.margin(-2);
-
- &__content {
- margin-inline: lengths.margin(2);
- }
- }
-}
diff --git a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetrics.tsx b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetrics.tsx
index 911fc91332..28c734c68a 100644
--- a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetrics.tsx
+++ b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetrics.tsx
@@ -1,13 +1,36 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../../primitives';
import MessageMetricsContentItem from './MessageMetricsContentItem';
+import MessageMetricsFollowing from './MessageMetricsFollowing';
+import { MessageMetricsItem } from './MessageMetricsItem';
+import MessageMetricsReply from './MessageMetricsReply';
export type MessageMetricsProps = HTMLAttributes;
-const MessageMetrics = (props: MessageMetricsProps) => (
-
-
-
+const MetricsContentWrapper = styled(RcxView, {
+ name: 'MessageMetricsContentWrapper',
+ display: 'flex',
+ flexDirection: 'row',
+ marginInline: -4,
+});
+
+const MessageMetrics = Object.assign(
+ (props: MessageMetricsProps) => (
+
+
+
+ ),
+ {
+ /** @deprecated prefer using named imports */
+ Reply: MessageMetricsReply,
+ /** @deprecated prefer using named imports */
+ Item: MessageMetricsItem,
+ /** @deprecated prefer using named imports */
+ Following: MessageMetricsFollowing,
+ },
);
export default MessageMetrics;
diff --git a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsContent.tsx b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsContent.tsx
new file mode 100644
index 0000000000..249bbf01a7
--- /dev/null
+++ b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsContent.tsx
@@ -0,0 +1,15 @@
+import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../../primitives';
+
+const MetricsContentFrame = styled(RcxView, {
+ name: 'MessageMetricsContent',
+ display: 'flex',
+ flexDirection: 'row',
+ marginBlock: 4,
+});
+
+export const MessageMetricsContent = (
+ props: HTMLAttributes,
+) => ;
diff --git a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsContentItem.tsx b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsContentItem.tsx
index cc38c21ca3..0bd944ee1b 100644
--- a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsContentItem.tsx
+++ b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsContentItem.tsx
@@ -1,9 +1,19 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../../primitives';
export type MessageMetricsContentItemProps = HTMLAttributes;
+const MetricsContentItemFrame = styled(RcxView, {
+ name: 'MessageMetricsContentItem',
+ display: 'flex',
+ flexDirection: 'row',
+ marginBlock: 4,
+});
+
const MessageMetricsContentItem = (props: MessageMetricsContentItemProps) => (
-
+
);
export default MessageMetricsContentItem;
diff --git a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsFollowing.tsx b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsFollowing.tsx
index d8857e82c3..b440e0153a 100644
--- a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsFollowing.tsx
+++ b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsFollowing.tsx
@@ -1,5 +1,7 @@
import type { ReactElement } from 'react';
+import { styled } from '@tamagui/core';
+import { RcxView } from '../../../primitives';
import { IconButton, type IconButtonProps } from '../../Button';
export type MessageMetricsFollowingProps = {
@@ -7,6 +9,15 @@ export type MessageMetricsFollowingProps = {
badge?: ReactElement;
} & Omit;
+const FollowBadge = styled(RcxView, {
+ name: 'MessageMetricsFollowBadge',
+ position: 'absolute' as any,
+ top: 0,
+ right: 0,
+ // @ts-ignore
+ transform: 'translate(40%, -40%)',
+});
+
const MessageMetricsFollowing = ({
name,
badge,
@@ -19,9 +30,7 @@ const MessageMetricsFollowing = ({
small
icon={name}
>
- {badge && (
- {badge}
- )}
+ {badge && {badge}}
);
diff --git a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItem.tsx b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItem.tsx
index f6b1021966..c2828a5038 100644
--- a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItem.tsx
+++ b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItem.tsx
@@ -1,17 +1,33 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
-import { prependClassName } from '../../../../helpers/prependClassName';
+import { RcxView } from '../../../../primitives';
+
+import MessageMetricsItemIcon from './MessageMetricsItemIcon';
+import MessageMetricsItemLabel from './MessageMetricsItemLabel';
export type MessageMetricsItemProps = HTMLAttributes;
-const MessageMetricsItem = ({
- className,
- ...props
-}: MessageMetricsItemProps) => (
-
+const MetricsItemFrame = styled(RcxView, {
+ name: 'MessageMetricsItem',
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginInline: 4,
+ color: '$fontDefault',
+});
+
+const MessageMetricsItem = Object.assign(
+ ({ className: _className, ...props }: MessageMetricsItemProps) => (
+
+ ),
+ {
+ /** @deprecated prefer using named imports */
+ Icon: MessageMetricsItemIcon,
+ /** @deprecated prefer using named imports */
+ Label: MessageMetricsItemLabel,
+ },
);
export default MessageMetricsItem;
diff --git a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItemAvatarRow.tsx b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItemAvatarRow.tsx
index fe7f67dbf6..7acaa368f0 100644
--- a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItemAvatarRow.tsx
+++ b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItemAvatarRow.tsx
@@ -1,17 +1,22 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
-import { prependClassName } from '../../../../helpers/prependClassName';
+import { RcxView } from '../../../../primitives';
export type MessageMetricsItemProps = HTMLAttributes;
+const AvatarRowFrame = styled(RcxView, {
+ name: 'MessageMetricsItemAvatarRow',
+ display: 'flex',
+ flexDirection: 'row',
+ marginInline: -2,
+});
+
const MessageMetricsItemAvatarRow = ({
- className,
+ className: _className,
...props
}: MessageMetricsItemProps) => (
-
+
);
export default MessageMetricsItemAvatarRow;
diff --git a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItemAvatarRowContent.tsx b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItemAvatarRowContent.tsx
index c3d864ba63..e2a71150c1 100644
--- a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItemAvatarRowContent.tsx
+++ b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItemAvatarRowContent.tsx
@@ -1,21 +1,21 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
-import { prependClassName } from '../../../../helpers/prependClassName';
+import { RcxView } from '../../../../primitives';
export type MessageMetricsItemAvatarRowContentProps =
HTMLAttributes;
+const AvatarRowContentFrame = styled(RcxView, {
+ name: 'MessageMetricsItemAvatarRowContent',
+ marginInline: 2,
+});
+
const MessageMetricsItemAvatarRowContent = ({
- className,
+ className: _className,
...props
}: MessageMetricsItemAvatarRowContentProps) => (
-
+
);
export default MessageMetricsItemAvatarRowContent;
diff --git a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItemLabel.tsx b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItemLabel.tsx
index 90a1205512..42c0837ace 100644
--- a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItemLabel.tsx
+++ b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetricsItem/MessageMetricsItemLabel.tsx
@@ -1,9 +1,23 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../../../primitives';
export type MessageMetricsItemLabelProps = HTMLAttributes;
+const MetricsItemLabelFrame = styled(RcxText, {
+ name: 'MessageMetricsItemLabel',
+ fontFamily: '$body',
+ fontSize: '$micro',
+ fontWeight: '$micro',
+ lineHeight: '$micro',
+ letterSpacing: '$micro',
+ marginInlineStart: 4,
+ overflowWrap: 'normal',
+});
+
const MessageMetricsItemLabel = (props: MessageMetricsItemLabelProps) => (
-
+
);
export default MessageMetricsItemLabel;
diff --git a/packages/fuselage/src/components/Message/MessageName.tsx b/packages/fuselage/src/components/Message/MessageName.tsx
index a36f13e042..3bc1587fb5 100644
--- a/packages/fuselage/src/components/Message/MessageName.tsx
+++ b/packages/fuselage/src/components/Message/MessageName.tsx
@@ -1,9 +1,28 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../primitives';
export type MessageNameProps = HTMLAttributes;
+const MessageNameFrame = styled(RcxText, {
+ name: 'MessageName',
+ fontFamily: '$body',
+ fontSize: '$h5',
+ fontWeight: '$h5',
+ lineHeight: '$h5',
+ letterSpacing: '$h5',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+ flexShrink: 1,
+ color: '$fontDefault',
+});
+
const MessageName = (props: MessageNameProps) => (
-
+
);
export default MessageName;
diff --git a/packages/fuselage/src/components/Message/MessageNameContainer.tsx b/packages/fuselage/src/components/Message/MessageNameContainer.tsx
index 6ea5cbb5a0..3db48e6f02 100644
--- a/packages/fuselage/src/components/Message/MessageNameContainer.tsx
+++ b/packages/fuselage/src/components/Message/MessageNameContainer.tsx
@@ -1,19 +1,26 @@
import type { HTMLAttributes } from 'react';
import { forwardRef } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../primitives';
export type MessageNameContainerProps = HTMLAttributes;
+const MessageNameContainerFrame = styled(RcxText, {
+ name: 'MessageNameContainer',
+ display: 'inline',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+});
+
const MessageNameContainer = forwardRef<
HTMLSpanElement,
MessageNameContainerProps
>(function MessageNameContainer(props, ref) {
- return (
-
- );
+ return ;
});
export default MessageNameContainer;
diff --git a/packages/fuselage/src/components/Message/MessageReactions/MessageReaction.tsx b/packages/fuselage/src/components/Message/MessageReactions/MessageReaction.tsx
index 63c0285ceb..215fb730bd 100644
--- a/packages/fuselage/src/components/Message/MessageReactions/MessageReaction.tsx
+++ b/packages/fuselage/src/components/Message/MessageReactions/MessageReaction.tsx
@@ -1,8 +1,11 @@
import type { HTMLAttributes, ReactNode } from 'react';
import { forwardRef } from 'react';
+import { styled } from '@tamagui/core';
-import MessageReactionCounter from './MessageReactionCounter';
-import MessageReactionEmoji from './MessageReactionEmoji';
+import { RcxInteractive } from '../../../primitives';
+
+import { MessageReactionCounter } from './MessageReactionCounter';
+import { MessageReactionEmoji } from './MessageReactionEmoji';
export type MessageReactionProps = {
name?: string;
@@ -11,34 +14,54 @@ export type MessageReactionProps = {
children?: ReactNode;
} & HTMLAttributes;
-const MessageReaction = forwardRef(
+const ReactionFrame = styled(RcxInteractive, {
+ name: 'MessageReaction',
+ display: 'inline-flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ margin: 2,
+ padding: 2,
+ cursor: 'pointer',
+ color: '$fontHint',
+ borderWidth: 1,
+ borderStyle: 'solid',
+ borderColor: '$strokeLight',
+ borderRadius: '$x4',
+ backgroundColor: '$surfaceTint',
+ role: 'button',
+ hoverStyle: {
+ borderColor: '$strokeDark',
+ backgroundColor: '$surfaceHover',
+ },
+ focusVisibleStyle: {
+ borderColor: '$strokeExtraDark',
+ boxShadow: '0 0 0 2px var(--shadowHighlight)',
+ },
+ variants: {
+ mine: {
+ true: {
+ color: '$fontDefault',
+ borderColor: '$strokeDark',
+ backgroundColor: '$surfaceSelected',
+ },
+ },
+ } as const,
+});
+
+export const MessageReaction = forwardRef(
function Reaction(
- { name, counter, mine, children, className, ...props },
+ { name, counter, mine, children, className: _className, ...props },
ref,
) {
return (
-
+
{children || (
<>
{name && }
{counter && }
>
)}
-
+
);
},
);
-
-export default MessageReaction;
diff --git a/packages/fuselage/src/components/Message/MessageReactions/MessageReactionAction.tsx b/packages/fuselage/src/components/Message/MessageReactions/MessageReactionAction.tsx
index 576c87d01b..f7e2429713 100644
--- a/packages/fuselage/src/components/Message/MessageReactions/MessageReactionAction.tsx
+++ b/packages/fuselage/src/components/Message/MessageReactions/MessageReactionAction.tsx
@@ -1,26 +1,37 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+import { RcxInteractive } from '../../../primitives';
import { Icon } from '../../Icon';
export type MessageReactionActionProps = HTMLAttributes;
-const MessageReactionAction = ({
- className,
+const ReactionActionFrame = styled(RcxInteractive, {
+ name: 'MessageReactionAction',
+ display: 'inline-flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ margin: 2,
+ padding: 2,
+ cursor: 'pointer',
+ borderWidth: 1,
+ borderStyle: 'solid',
+ borderColor: '$strokeLight',
+ borderRadius: '$x4',
+ backgroundColor: '$surfaceTint',
+ role: 'button',
+ opacity: 0,
+ '$group-message-hover': {
+ display: 'inline-flex' as any,
+ opacity: 1,
+ },
+});
+
+export const MessageReactionAction = ({
+ className: _className,
...props
}: MessageReactionActionProps) => (
-
+
-
+
);
-
-export default MessageReactionAction;
diff --git a/packages/fuselage/src/components/Message/MessageReactions/MessageReactionCounter.tsx b/packages/fuselage/src/components/Message/MessageReactions/MessageReactionCounter.tsx
index d0c48161a2..6253d9a174 100644
--- a/packages/fuselage/src/components/Message/MessageReactions/MessageReactionCounter.tsx
+++ b/packages/fuselage/src/components/Message/MessageReactions/MessageReactionCounter.tsx
@@ -1,13 +1,26 @@
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../../primitives';
+
export type MessageReactionCounterProps = {
counter: number;
className?: string;
};
-const MessageReactionCounter = ({
+const CounterFrame = styled(RcxText, {
+ name: 'MessageReactionCounter',
+ fontFamily: '$body',
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ marginInline: 2,
+ overflowWrap: 'normal',
+});
+
+export const MessageReactionCounter = ({
counter,
- className,
+ className: _className,
}: MessageReactionCounterProps) => (
- {counter}
+ {counter}
);
-
-export default MessageReactionCounter;
diff --git a/packages/fuselage/src/components/Message/MessageReactions/MessageReactionEmoji.tsx b/packages/fuselage/src/components/Message/MessageReactions/MessageReactionEmoji.tsx
index 591ad700ad..f169bbe507 100644
--- a/packages/fuselage/src/components/Message/MessageReactions/MessageReactionEmoji.tsx
+++ b/packages/fuselage/src/components/Message/MessageReactions/MessageReactionEmoji.tsx
@@ -3,14 +3,13 @@ import MessageEmojiBase from '../MessageEmojiBase';
export type MessageReactionEmojiProps = MessageEmojiBaseProps;
-const MessageReactionEmoji = ({
+export const MessageReactionEmoji = ({
className,
...props
}: MessageReactionEmojiProps) => (
);
-
-export default MessageReactionEmoji;
diff --git a/packages/fuselage/src/components/Message/MessageReactions/MessageReactions.styles.scss b/packages/fuselage/src/components/Message/MessageReactions/MessageReactions.styles.scss
deleted file mode 100644
index 40468951fa..0000000000
--- a/packages/fuselage/src/components/Message/MessageReactions/MessageReactions.styles.scss
+++ /dev/null
@@ -1,83 +0,0 @@
-@use '../../../styles/lengths.scss';
-@use '../../../styles/colors.scss';
-@use '../../../styles/typography.scss';
-@use '../../../styles/mixins/size.scss';
-@use '../../../styles/mixins/templates.scss';
-@use '../../../styles/variables/buttons.scss';
-@use '../../../styles/primitives/button.scss';
-@use '../mixins.scss';
-
-.rcx-message-reactions {
- &__container {
- display: flex;
- flex-flow: row wrap;
- justify-content: flex-start;
-
- margin: lengths.margin(-2);
- }
-
- &__reaction {
- @include typography.use-font-scale(c1);
- display: inline-flex;
- align-items: center;
-
- margin: lengths.margin(2);
-
- padding: lengths.padding(2);
-
- cursor: pointer;
-
- color: colors.font(hint);
- border: lengths.border-width(default) solid colors.stroke(light);
- border-radius: theme(
- 'message-reaction-border-radius',
- lengths.border-radius(medium)
- );
- background-color: theme(
- 'message-reaction-hover-background-color',
- colors.surface(tint)
- );
-
- &:hover {
- border-color: theme(
- 'message-reaction-hover-border-color',
- colors.stroke(dark)
- );
- background-color: theme(
- 'message-reaction-hover-background-color',
- colors.surface(hover)
- );
- }
-
- &--action {
- @include mixins.message-focus-visible {
- display: inline-flex;
- }
- padding: lengths.padding(2);
- }
-
- &--mine {
- color: theme('message-reaction-color', colors.font(default));
- border-width: lengths.border-width(default);
- border-color: theme('message-reaction-border-color', colors.stroke(dark));
- background-color: theme(
- 'message-reaction-background-color',
- colors.surface(selected)
- );
- }
-
- @include templates.focus-state {
- border-color: colors.stroke(light);
- }
- }
-
- &__emoji {
- display: block;
- @include size.square(lengths.size(16));
- }
-
- &__counter {
- @include typography.use-font-scale(c1);
- margin-inline: lengths.margin(2);
- }
-}
diff --git a/packages/fuselage/src/components/Message/MessageReactions/MessageReactions.tsx b/packages/fuselage/src/components/Message/MessageReactions/MessageReactions.tsx
index df0fbd5d9d..53736349a7 100644
--- a/packages/fuselage/src/components/Message/MessageReactions/MessageReactions.tsx
+++ b/packages/fuselage/src/components/Message/MessageReactions/MessageReactions.tsx
@@ -1,19 +1,26 @@
import type { HTMLAttributes } from 'react';
import { forwardRef } from 'react';
+import { styled } from '@tamagui/core';
+import { RcxView } from '../../../primitives';
import MessageBlock from '../MessageBlock';
export type MessageReactionsProps = HTMLAttributes;
+const ReactionsContainer = styled(RcxView, {
+ name: 'MessageReactionsContainer',
+ display: 'flex',
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ justifyContent: 'flex-start',
+ margin: -2,
+});
+
const MessageReactions = forwardRef(
function MessageReactions(props, ref) {
return (
-
-
+
+
);
},
diff --git a/packages/fuselage/src/components/Message/MessageReactions/index.ts b/packages/fuselage/src/components/Message/MessageReactions/index.ts
index 0097bbeef2..2e1b911ff7 100644
--- a/packages/fuselage/src/components/Message/MessageReactions/index.ts
+++ b/packages/fuselage/src/components/Message/MessageReactions/index.ts
@@ -1,24 +1,31 @@
+import { MessageReaction } from './MessageReaction';
+import { MessageReactionAction } from './MessageReactionAction';
+import { MessageReactionCounter } from './MessageReactionCounter';
+import { MessageReactionEmoji } from './MessageReactionEmoji';
import MessageReactions, {
type MessageReactionsProps,
} from './MessageReactions';
-export default MessageReactions;
-
-export { type MessageReactionsProps, MessageReactions };
+export default Object.assign(MessageReactions, {
+ /**
+ * @deprecated prefer using named imports
+ * */
+ Reaction: MessageReaction,
+ /**
+ * @deprecated prefer using named imports
+ * */
+ Action: MessageReactionAction,
+});
export {
- default as MessageReaction,
+ MessageReaction,
type MessageReactionProps,
-} from './MessageReaction';
-export {
- default as MessageReactionAction,
+ type MessageReactionsProps,
+ MessageReactionAction,
type MessageReactionActionProps,
-} from './MessageReactionAction';
-export {
- default as MessageReactionCounter,
+ MessageReactions,
+ MessageReactionCounter,
type MessageReactionCounterProps,
-} from './MessageReactionCounter';
-export {
- default as MessageReactionEmoji,
+ MessageReactionEmoji,
type MessageReactionEmojiProps,
-} from './MessageReactionEmoji';
+};
diff --git a/packages/fuselage/src/components/Message/MessageRole.tsx b/packages/fuselage/src/components/Message/MessageRole.tsx
index b76aeddb11..2770b78844 100644
--- a/packages/fuselage/src/components/Message/MessageRole.tsx
+++ b/packages/fuselage/src/components/Message/MessageRole.tsx
@@ -4,7 +4,7 @@ import { Tag } from '../Tag';
export type MessageRoleProps = TagProps;
const MessageRole = (props: MessageRoleProps) => (
-
+
);
export default MessageRole;
diff --git a/packages/fuselage/src/components/Message/MessageRoles.tsx b/packages/fuselage/src/components/Message/MessageRoles.tsx
index b77b19a582..6975d3d773 100644
--- a/packages/fuselage/src/components/Message/MessageRoles.tsx
+++ b/packages/fuselage/src/components/Message/MessageRoles.tsx
@@ -1,9 +1,23 @@
+import { styled } from '@tamagui/core';
import type { HTMLAttributes } from 'react';
+import { RcxView } from '../../primitives';
+
export type MessageRolesProps = HTMLAttributes;
+const MessageRolesFrame = styled(RcxView, {
+ name: 'MessageRoles',
+ display: 'flex',
+ flexDirection: 'row',
+ flexShrink: 1,
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ gap: '$x4',
+});
+
const MessageRoles = (props: MessageRolesProps) => (
-
+
);
export default MessageRoles;
diff --git a/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicator.styles.scss b/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicator.styles.scss
deleted file mode 100644
index 4137b4affb..0000000000
--- a/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicator.styles.scss
+++ /dev/null
@@ -1,37 +0,0 @@
-@use '../../../styles/lengths.scss';
-@use '../../../styles/functions.scss';
-@use '../../../styles/colors.scss';
-@use '../../../styles/typography.scss';
-
-$variants: (
- 'success': colors.status-font(on-success),
- 'danger': colors.status-font(on-danger),
- 'warning': colors.status-font(on-warning),
- 'primary': colors.status-font(on-primary),
-);
-
-.rcx-message-status-indicator {
- margin-block: lengths.margin(2);
-
- user-select: none;
-
- &:empty {
- display: none;
- }
-
- &__text {
- white-space: nowrap;
-
- color: colors.font(secondary-info);
- @include typography.use-font-scale(c1);
- }
-
- &__item {
- color: colors.font(secondary-info);
- @each $name, $color in $variants {
- &--#{$name} {
- color: functions.theme('message-status-variant-color-#{$name}', $color);
- }
- }
- }
-}
diff --git a/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicator.tsx b/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicator.tsx
index 3bd758325d..c36366f718 100644
--- a/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicator.tsx
+++ b/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicator.tsx
@@ -1,15 +1,23 @@
import type { AllHTMLAttributes } from 'react';
import { forwardRef } from 'react';
+import { styled } from '@tamagui/core';
-import './MessageStatusIndicator.styles.scss';
+import { RcxView } from '../../../primitives';
export type MessageStatusIndicatorProps = AllHTMLAttributes;
+const StatusIndicatorFrame = styled(RcxView, {
+ name: 'MessageStatusIndicator',
+ marginBlock: 2,
+ // @ts-ignore
+ userSelect: 'none',
+});
+
const MessageStatusIndicator = forwardRef<
HTMLDivElement,
MessageStatusIndicatorProps
>(function MessageStatusIndicator(props, ref) {
- return ;
+ return ;
});
export default MessageStatusIndicator;
diff --git a/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicatorItem.tsx b/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicatorItem.tsx
index 3193e8094c..d5b95ce617 100644
--- a/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicatorItem.tsx
+++ b/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicatorItem.tsx
@@ -7,6 +7,13 @@ export type MessageStatusIndicatorItemProps = {
variant?: 'success' | 'danger' | 'warning' | 'primary';
} & Omit, 'is'>;
+const variantColorMap: Record = {
+ success: 'var(--statusFontOnSuccess)',
+ danger: 'var(--statusFontOnDanger)',
+ warning: 'var(--statusFontOnWarning)',
+ primary: 'var(--statusFontOnInfo)',
+};
+
const MessageStatusIndicatorItem = ({
name,
variant,
@@ -15,12 +22,8 @@ const MessageStatusIndicatorItem = ({
);
diff --git a/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicatorText.tsx b/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicatorText.tsx
index 84a0fc43d9..93a53b1626 100644
--- a/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicatorText.tsx
+++ b/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusIndicatorText.tsx
@@ -1,15 +1,28 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../../primitives';
export type MessageStatusIndicatorTextProps = {
children: ReactNode;
};
+const StatusTextFrame = styled(RcxText, {
+ name: 'MessageStatusIndicatorText',
+ fontFamily: '$body',
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ whiteSpace: 'nowrap',
+ color: '$fontSecondaryInfo',
+ overflowWrap: 'normal',
+});
+
const MessageStatusIndicatorText = ({
children,
}: MessageStatusIndicatorTextProps) => (
-
- {children}
-
+ {children}
);
export default MessageStatusIndicatorText;
diff --git a/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusPrivateIndicator.tsx b/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusPrivateIndicator.tsx
index 5ba9a6bc5f..893959f9eb 100644
--- a/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusPrivateIndicator.tsx
+++ b/packages/fuselage/src/components/Message/MessageStatusIndicator/MessageStatusPrivateIndicator.tsx
@@ -7,17 +7,22 @@ export type MessageStatusPrivateIndicatorProps = {
variant?: MessageStatusIndicatorItemProps['variant'];
};
+const variantColorMap: Record = {
+ success: 'var(--statusFontOnSuccess)',
+ danger: 'var(--statusFontOnDanger)',
+ warning: 'var(--statusFontOnWarning)',
+ primary: 'var(--statusFontOnInfo)',
+};
+
const MessageStatusPrivateIndicator = ({
children,
variant,
}: MessageStatusPrivateIndicatorProps) => (
{children}
diff --git a/packages/fuselage/src/components/Message/MessageSystem/MessageSystem.styles.scss b/packages/fuselage/src/components/Message/MessageSystem/MessageSystem.styles.scss
deleted file mode 100644
index b22c2429e7..0000000000
--- a/packages/fuselage/src/components/Message/MessageSystem/MessageSystem.styles.scss
+++ /dev/null
@@ -1,69 +0,0 @@
-@use '../../../styles/lengths.scss';
-@use '../../../styles/functions.scss';
-@use '../../../styles/colors.scss';
-@use '../../../styles/typography.scss';
-@use '../../../styles/mixins/templates.scss';
-@use '../mixins.scss';
-
-$message-system-background-color-selected: functions.theme(
- 'message-system-background-color-selected',
- colors.surface(selected)
-);
-
-.rcx-message-system {
- @include typography.use-font-scale(c1);
- @include typography.use-with-truncated-text();
- display: flex;
- flex-direction: row;
- align-items: flex-start;
-
- margin-inline: lengths.margin(2);
-
- padding-block: lengths.padding(8);
- padding-inline: lengths.padding(20);
-
- color: colors.font(default);
-
- &--selected {
- background: $message-system-background-color-selected !important;
- }
-
- &__container {
- @include typography.use-with-truncated-text();
- display: flex;
- flex-direction: column;
- flex-shrink: 1;
- align-self: center;
-
- width: 100%;
- margin-block: lengths.margin(-4);
- }
-
- &__body {
- @include typography.use-font-scale(p2);
- @include typography.use-with-truncated-text();
- margin-inline: lengths.margin(2);
- }
-
- &__name {
- @include typography.use-font-scale(p2b);
- @include typography.use-with-truncated-text();
- flex-shrink: 0;
- }
-
- &__time {
- @include typography.use-with-truncated-text();
- @include typography.use-font-scale(c1);
- flex-shrink: 0;
-
- margin-inline: lengths.margin(2);
- }
-
- &__block {
- display: flex;
- flex-direction: row;
- align-items: center;
- }
-
- @include templates.focus-state;
-}
diff --git a/packages/fuselage/src/components/Message/MessageSystem/MessageSystem.tsx b/packages/fuselage/src/components/Message/MessageSystem/MessageSystem.tsx
index e0f62e50a8..0a7ad139a4 100644
--- a/packages/fuselage/src/components/Message/MessageSystem/MessageSystem.tsx
+++ b/packages/fuselage/src/components/Message/MessageSystem/MessageSystem.tsx
@@ -3,8 +3,9 @@ import type {
MouseEvent as ReactMouseEvent,
AllHTMLAttributes,
} from 'react';
+import { styled } from '@tamagui/core';
-import './MessageSystem.styles.scss';
+import { RcxView } from '../../../primitives';
export type MessageSystemProps = {
children?: ReactNode;
@@ -13,24 +14,45 @@ export type MessageSystemProps = {
onClick?: (e: ReactMouseEvent) => void;
} & AllHTMLAttributes;
+const MessageSystemFrame = styled(RcxView, {
+ name: 'MessageSystem',
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'flex-start',
+ marginInline: 2,
+ paddingBlock: 8,
+ paddingInline: 20,
+ color: '$fontDefault',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ focusVisibleStyle: {
+ borderColor: '$strokeExtraDark',
+ boxShadow: '0 0 0 2px var(--shadowHighlight)',
+ },
+ variants: {
+ isSelected: {
+ true: {
+ backgroundColor: '$surfaceSelected',
+ },
+ },
+ } as const,
+});
+
const MessageSystem = ({
children,
title,
isSelected,
...props
}: MessageSystemProps) => (
-
{children}
-
+
);
export default MessageSystem;
diff --git a/packages/fuselage/src/components/Message/MessageSystem/MessageSystemBlock.tsx b/packages/fuselage/src/components/Message/MessageSystem/MessageSystemBlock.tsx
index c3d31f332e..f735ca345d 100644
--- a/packages/fuselage/src/components/Message/MessageSystem/MessageSystemBlock.tsx
+++ b/packages/fuselage/src/components/Message/MessageSystem/MessageSystemBlock.tsx
@@ -1,11 +1,21 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../../primitives';
export type MessageSystemBlockProps = {
children?: ReactNode;
};
+const SystemBlockFrame = styled(RcxView, {
+ name: 'MessageSystemBlock',
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+});
+
const MessageSystemBlock = (props: MessageSystemBlockProps) => (
-
+
);
export default MessageSystemBlock;
diff --git a/packages/fuselage/src/components/Message/MessageSystem/MessageSystemBody.tsx b/packages/fuselage/src/components/Message/MessageSystem/MessageSystemBody.tsx
index a9c53951bd..991442e950 100644
--- a/packages/fuselage/src/components/Message/MessageSystem/MessageSystemBody.tsx
+++ b/packages/fuselage/src/components/Message/MessageSystem/MessageSystemBody.tsx
@@ -1,9 +1,27 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../../primitives';
export type MessageSystemBodyProps = HTMLAttributes;
+const SystemBodyFrame = styled(RcxText, {
+ name: 'MessageSystemBody',
+ fontFamily: '$body',
+ fontSize: '$p2',
+ fontWeight: '$p2',
+ lineHeight: '$p2',
+ letterSpacing: '$p2',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+ marginInline: 2,
+});
+
const MessageSystemBody = (props: MessageSystemBodyProps) => (
-
+
);
export default MessageSystemBody;
diff --git a/packages/fuselage/src/components/Message/MessageSystem/MessageSystemContainer.tsx b/packages/fuselage/src/components/Message/MessageSystem/MessageSystemContainer.tsx
index 6e97162e15..3b3619b50f 100644
--- a/packages/fuselage/src/components/Message/MessageSystem/MessageSystemContainer.tsx
+++ b/packages/fuselage/src/components/Message/MessageSystem/MessageSystemContainer.tsx
@@ -1,14 +1,28 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../../primitives';
export type MessageSystemContainerProps = {
children?: ReactNode;
};
+const SystemContainerFrame = styled(RcxView, {
+ name: 'MessageSystemContainer',
+ display: 'flex',
+ flexDirection: 'column',
+ flexShrink: 1,
+ alignSelf: 'center',
+ width: '100%',
+ marginBlock: -4,
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+});
+
const MessageSystemContainer = (props: MessageSystemContainerProps) => (
-
+
);
export default MessageSystemContainer;
diff --git a/packages/fuselage/src/components/Message/MessageSystem/MessageSystemName.tsx b/packages/fuselage/src/components/Message/MessageSystem/MessageSystemName.tsx
index 0fb90c2248..c214537d2d 100644
--- a/packages/fuselage/src/components/Message/MessageSystem/MessageSystemName.tsx
+++ b/packages/fuselage/src/components/Message/MessageSystem/MessageSystemName.tsx
@@ -1,9 +1,27 @@
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../../primitives';
import type { MessageNameProps } from '../MessageName';
export type MessageSystemNameProps = MessageNameProps;
+const SystemNameFrame = styled(RcxText, {
+ name: 'MessageSystemName',
+ fontFamily: '$body',
+ fontSize: '$p2b',
+ fontWeight: '$p2b',
+ lineHeight: '$p2b',
+ letterSpacing: '$p2b',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+ flexShrink: 0,
+});
+
const MessageSystemName = (props: MessageSystemNameProps) => (
-
+
);
export default MessageSystemName;
diff --git a/packages/fuselage/src/components/Message/MessageSystem/MessageSystemTimestamp.tsx b/packages/fuselage/src/components/Message/MessageSystem/MessageSystemTimestamp.tsx
index 902acdd8c4..44e3a7e39a 100644
--- a/packages/fuselage/src/components/Message/MessageSystem/MessageSystemTimestamp.tsx
+++ b/packages/fuselage/src/components/Message/MessageSystem/MessageSystemTimestamp.tsx
@@ -1,12 +1,31 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../../primitives';
export type MessageSystemTimestampProps = {
children: ReactNode;
title?: string;
};
+const SystemTimestampFrame = styled(RcxText, {
+ name: 'MessageSystemTimestamp',
+ fontFamily: '$body',
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+ flexShrink: 0,
+ marginInline: 2,
+});
+
const MessageSystemTimestamp = (props: MessageSystemTimestampProps) => (
-
+
);
export default MessageSystemTimestamp;
diff --git a/packages/fuselage/src/components/Message/MessageTimestamp.tsx b/packages/fuselage/src/components/Message/MessageTimestamp.tsx
index 0ebef09060..dadd166848 100644
--- a/packages/fuselage/src/components/Message/MessageTimestamp.tsx
+++ b/packages/fuselage/src/components/Message/MessageTimestamp.tsx
@@ -1,9 +1,28 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../primitives';
export type MessageTimestampProps = HTMLAttributes;
+const MessageTimestampFrame = styled(RcxText, {
+ name: 'MessageTimestamp',
+ fontFamily: '$body',
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+ flexShrink: 0,
+ color: '$fontDefault',
+});
+
const MessageTimestamp = (props: MessageTimestampProps) => (
-
+
);
export default MessageTimestamp;
diff --git a/packages/fuselage/src/components/Message/MessageToolbar/MessageToolbar.styles.scss b/packages/fuselage/src/components/Message/MessageToolbar/MessageToolbar.styles.scss
deleted file mode 100644
index 6948630544..0000000000
--- a/packages/fuselage/src/components/Message/MessageToolbar/MessageToolbar.styles.scss
+++ /dev/null
@@ -1,49 +0,0 @@
-@use '../../../styles/lengths.scss';
-@use '../../../styles/colors.scss';
-@use '../../../styles/typography.scss';
-@use '../mixins.scss';
-
-.rcx-message-toolbar {
- margin-inline: lengths.margin(20);
- padding: lengths.padding(2);
-
- border: lengths.border-width(default) solid colors.stroke(extra-light);
- border-radius: theme(
- 'message-toolbar-border-radius',
- lengths.border-radius(medium)
- );
- background: colors.surface(room);
-
- &__wrapper {
- display: none;
-
- .rcx-message:hover &,
- .rcx-message:focus-within & {
- display: inline-block;
- }
-
- &--visible {
- display: inline-block;
-
- .rcx-message-toolbar {
- opacity: 1;
- }
- }
- }
-
- @include mixins.message-focus-visible {
- display: inline-block;
- }
-
- @at-root .rcx-message & {
- position: absolute;
- z-index: 10;
- top: lengths.margin(-24);
- right: 0;
-
- &:dir(rtl) {
- left: 0;
- right: initial;
- }
- }
-}
diff --git a/packages/fuselage/src/components/Message/MessageToolbar/MessageToolbar.tsx b/packages/fuselage/src/components/Message/MessageToolbar/MessageToolbar.tsx
index cf930a535d..95969c3ee1 100644
--- a/packages/fuselage/src/components/Message/MessageToolbar/MessageToolbar.tsx
+++ b/packages/fuselage/src/components/Message/MessageToolbar/MessageToolbar.tsx
@@ -1,16 +1,47 @@
import { forwardRef } from 'react';
+import { styled } from '@tamagui/core';
+import { RcxView } from '../../../primitives';
import { ButtonGroup, type ButtonGroupProps } from '../../ButtonGroup';
+import { Menu } from '../../Menu';
+
+import MessageToolbarItem from './MessageToolbarItem';
+import MessageToolbarWrapper from './MessageToolbarWrapper';
export type MessageToolbarProps = ButtonGroupProps;
-const MessageToolbar = forwardRef(
- function MessageToolbar(props, ref) {
- return (
-
-
-
- );
+const MessageToolbarFrame = styled(RcxView, {
+ name: 'MessageToolbar',
+ position: 'absolute' as any,
+ zIndex: 10,
+ top: -24,
+ right: 0,
+ marginInline: 20,
+ padding: 2,
+ borderWidth: 1,
+ borderStyle: 'solid',
+ borderColor: '$strokeExtraLight',
+ borderRadius: '$x4',
+ backgroundColor: '$surfaceRoom',
+});
+
+const MessageToolbar = Object.assign(
+ forwardRef(
+ function MessageToolbar(props, ref) {
+ return (
+
+
+
+ );
+ },
+ ),
+ {
+ /** @deprecated prefer using named imports */
+ Item: MessageToolbarItem,
+ /** @deprecated prefer using named imports */
+ Wrapper: MessageToolbarWrapper,
+ /** @deprecated prefer using named imports */
+ Menu,
},
);
diff --git a/packages/fuselage/src/components/Message/MessageToolbar/MessageToolbarWrapper.tsx b/packages/fuselage/src/components/Message/MessageToolbar/MessageToolbarWrapper.tsx
index ec7a5c1ab5..a55006b084 100644
--- a/packages/fuselage/src/components/Message/MessageToolbar/MessageToolbarWrapper.tsx
+++ b/packages/fuselage/src/components/Message/MessageToolbar/MessageToolbarWrapper.tsx
@@ -1,25 +1,40 @@
import type { HTMLAttributes } from 'react';
import { forwardRef } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../../primitives';
export type MessageToolbarWrapperProps = HTMLAttributes & {
visible?: boolean;
};
+const MessageToolbarWrapperFrame = styled(RcxView, {
+ name: 'MessageToolbarWrapper',
+ display: 'none',
+ opacity: 0,
+ '$group-message-hover': {
+ display: 'inline-block' as any,
+ opacity: 1,
+ },
+ variants: {
+ visible: {
+ true: {
+ display: 'inline-block',
+ opacity: 1,
+ },
+ },
+ } as const,
+});
+
const MessageToolbarWrapper = forwardRef<
HTMLDivElement,
MessageToolbarWrapperProps
->(function MessageToolbarWrapper({ className, visible, ...props }, ref) {
+>(function MessageToolbarWrapper({ className: _className, visible, ...props }, ref) {
return (
-
);
});
diff --git a/packages/fuselage/src/components/Message/MessageUsername.tsx b/packages/fuselage/src/components/Message/MessageUsername.tsx
index bb5b2195c7..0c57628f67 100644
--- a/packages/fuselage/src/components/Message/MessageUsername.tsx
+++ b/packages/fuselage/src/components/Message/MessageUsername.tsx
@@ -1,12 +1,28 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../primitives';
export type MessageUsernameProps = HTMLAttributes;
+const MessageUsernameFrame = styled(RcxText, {
+ name: 'MessageUsername',
+ fontFamily: '$body',
+ fontSize: '$p2',
+ fontWeight: '$p2',
+ lineHeight: '$p2',
+ letterSpacing: '$p2',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+ flexShrink: 1,
+ color: '$fontDefault',
+});
+
const MessageUsername = (props: MessageUsernameProps) => (
-
+
);
export default MessageUsername;
diff --git a/packages/fuselage/src/components/Message/Messages.styles.scss b/packages/fuselage/src/components/Message/Messages.styles.scss
deleted file mode 100644
index 27f1dc5b0c..0000000000
--- a/packages/fuselage/src/components/Message/Messages.styles.scss
+++ /dev/null
@@ -1,429 +0,0 @@
-@use '../../styles/lengths.scss';
-@use '../../styles/functions.scss';
-@use '../../styles/colors.scss';
-@use '../../styles/mixins/size.scss';
-@use '../../styles/mixins/templates.scss';
-@use '../../styles/typography.scss';
-@use './mixins.scss';
-
-@import './MessageMetrics/MessageMetrics.styles.scss';
-@import './MessageToolbar/MessageToolbar.styles.scss';
-@import './MessageReactions/MessageReactions.styles.scss';
-@import './ThreadMessage/ThreadMessage.styles.scss';
-@import './MessageDivider/MessageDivider.styles.scss';
-@import './MessageStatusIndicator/MessageStatusIndicator.styles.scss';
-@import './MessageSystem/MessageSystem.styles.scss';
-@import './MessageGenericPreview/MessageGenericPreview.styles.scss';
-
-%rcx-margins-header {
- margin-inline: lengths.margin(2);
-}
-%rcx-margins-block {
- margin-block: lengths.margin(2);
-}
-
-$message-background-color: functions.theme(
- 'message-background-color',
- colors.surface(room)
-);
-
-$message-background-color-hover: functions.theme(
- 'message-background-color-hover',
- colors.surface(hover)
-);
-
-$message-background-color-selected: functions.theme(
- 'message-background-color-selected',
- colors.surface(selected)
-);
-
-$message-background-color-editing: functions.theme(
- 'message-background-color-editing',
- colors.status-background(warning-2)
-);
-$message-color-editing: functions.theme(
- 'message-background-color-editing',
- colors.status-font(on-warning-2)
-);
-
-$message-background-color-highlight: functions.theme(
- 'message-background-color-highlight',
- colors.status-background(warning-2)
-);
-
-$message-highlight-colors-critical-color: theme(
- 'message-highlight-colors-critical-color',
- colors.font(pure-white)
-);
-$message-highlight-colors-background-critical-color: theme(
- 'message-highlight-colors-background-critical-color',
- colors.badge('level-4')
-);
-
-$message-highlight-colors-relevant-color: theme(
- 'message-highlight-colors-relevant-color',
- colors.font(pure-white)
-);
-$message-highlight-colors-background-relevant-color: theme(
- 'message-highlight-colors-background-relevant-color',
- colors.badge('level-3')
-);
-
-$message-highlight-colors-other-color: theme(
- 'message-highlight-colors-other-color',
- colors.font(default)
-);
-
-$message-highlight-colors-other-link-color: theme(
- 'message-highlight-colors-other-color',
- colors.font(info)
-);
-
-$message-highlight-colors-background-other-color: theme(
- 'message-highlight-colors-background-other-color',
- colors.badge('level-0')
-);
-
-.rcx-message {
- @include mixins.container();
- position: relative;
-
- display: flex;
- flex-direction: row;
- align-items: flex-start;
-
- margin-inline: lengths.margin(2);
-
- padding-block: lengths.padding(8) lengths.padding(4);
-
- padding-inline: lengths.padding(20);
-
- // background-color: $message-background-color;
-
- &:hover {
- background-color: $message-background-color-hover;
- }
-
- @include templates.focus-state;
-
- &--selected {
- background: $message-background-color-selected !important;
- }
-
- &--editing {
- color: $message-color-editing !important;
- background: $message-background-color-editing !important;
- }
-
- &--highlight {
- animation: background-fade 6s forwards;
- }
-
- &--pending {
- .rcx-message-body {
- opacity: 0.4;
- }
- }
-
- &--sequential {
- padding-block: lengths.padding(4);
- }
-
- @keyframes background-fade {
- 50% {
- background: $message-background-color-highlight;
- }
-
- 100% {
- background: $message-background-color;
- }
- }
-
- &--clickable {
- cursor: pointer;
- }
-
- &-header {
- @extend %rcx-margins-block;
- display: flex;
- flex-direction: row;
- flex-grow: 0;
- flex-shrink: 1;
-
- min-width: 1px;
-
- &__wrapper {
- display: flex;
- flex-direction: row;
- align-items: center;
- flex-grow: 1;
- flex-shrink: 1;
-
- min-width: 1px;
- margin-block: lengths.margin(-4);
- margin-inline: lengths.margin(-2);
- }
-
- &__time {
- @extend %rcx-margins-header;
- @include typography.use-with-truncated-text();
- @include typography.use-font-scale(c1);
- flex-shrink: 0;
-
- color: colors.font(default);
- }
-
- &__name-container {
- display: inline;
-
- @include typography.use-with-truncated-text();
-
- @extend %rcx-margins-header;
-
- @include templates.focus-state;
- flex-shrink: 0;
- }
-
- &__name {
- @include typography.use-font-scale(h5);
- @include typography.use-with-truncated-text();
- flex-shrink: 1;
-
- color: colors.font(default);
- }
-
- &__username {
- @include typography.use-font-scale(p2);
- @include typography.use-with-truncated-text();
- flex-shrink: 1;
-
- color: colors.font(default);
- }
-
- &__roles {
- display: flex;
- flex-shrink: 1;
- @include typography.use-with-truncated-text();
- }
-
- &__role {
- @extend %rcx-margins-header;
- }
-
- & + .rcx-message-block {
- margin-block-start: lengths.margin(4);
- }
- }
-
- &-body {
- @extend %rcx-margins-block;
- @include typography.use-font-scale(p2);
-
- overflow: hidden;
-
- flex-shrink: 1;
-
- transition: opacity 0.3s linear;
-
- word-break: break-word;
-
- opacity: 1;
-
- color: colors.font(default);
-
- & h1 {
- @include typography.use-font-scale(h1);
- }
-
- & h2 {
- @include typography.use-font-scale(h2);
- }
-
- & h3 {
- @include typography.use-font-scale(h3);
- }
-
- & h4 {
- @include typography.use-font-scale(h4);
- }
-
- & ul,
- ol {
- margin: 0;
- padding-block: lengths.padding(4) 0;
- padding-inline: 0;
-
- list-style: none;
- }
-
- & ul li::before {
- padding: 0 lengths.padding(8);
-
- content: '•';
-
- font-weight: bold;
- }
-
- & ol li::before {
- padding: 0 lengths.padding(8);
-
- content: attr(value) '.';
-
- font-weight: bold;
- }
-
- &--clamp {
- display: -webkit-box;
- overflow: hidden;
-
- word-break: break-word;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 2;
-
- &-2 {
- -webkit-line-clamp: 2;
- }
-
- &-3 {
- -webkit-line-clamp: 3;
- }
-
- &-4 {
- -webkit-line-clamp: 3;
- }
- }
-
- & blockquote {
- padding-inline: lengths.padding(8);
-
- border: 1px solid colors.stroke(extra-light);
- border-radius: lengths.border-radius(small);
- background-color: colors.surface(tint);
- border-inline-start-color: colors.stroke(medium);
-
- &:hover,
- &:focus {
- border-color: colors.stroke(light);
- background-color: colors.surface(hover);
- border-inline-start-color: colors.stroke(medium);
- }
- }
-
- & ul.task-list {
- > li::before {
- display: none;
- }
-
- > li
- > .rcx-check-box
- > .rcx-check-box__input:focus
- + .rcx-check-box__fake {
- z-index: 1;
- }
- margin-inline-start: 0;
- padding-inline-start: 0;
-
- list-style: none;
- }
- }
-
- &-block {
- @extend %rcx-margins-block;
- display: flex;
- flex-direction: column;
-
- &--width-fixed {
- flex-grow: 0;
- flex-shrink: 1;
-
- width: 100;
- max-width: functions.theme('message-block-width-fixed', 368px);
- }
- }
-
- &__emoji {
- display: inline-block;
-
- margin-inline: lengths.margin(2);
-
- background-size: contain;
- @include size.square(lengths.size(24));
-
- &--big {
- @include size.square(lengths.size(44));
- }
- }
-
- &__highlight {
- position: relative;
-
- z-index: 1;
-
- display: inline-block;
-
- padding-inline: lengths.padding(2);
-
- white-space: nowrap;
-
- word-break: keep-all;
-
- font-weight: 500;
-
- &--clickable {
- cursor: pointer;
-
- &:hover {
- text-decoration: underline;
- }
- }
-
- &::before {
- position: absolute;
-
- z-index: -1;
-
- width: 100%;
- height: 18px;
-
- content: '';
-
- transform: translateY(lengths.margin(1)) translateX(lengths.margin(-2));
-
- border-radius: theme(
- 'message-highlight-border-radius',
- lengths.border-radius(medium)
- );
- }
-
- &--critical {
- &::before {
- background-color: $message-highlight-colors-background-critical-color;
- }
- color: $message-highlight-colors-critical-color;
- }
-
- &--relevant {
- &::before {
- background-color: $message-highlight-colors-background-relevant-color;
- }
- color: $message-highlight-colors-relevant-color;
- }
-
- &--other,
- &--link {
- &::before {
- background-color: $message-highlight-colors-background-other-color;
- }
- }
-
- &--link {
- color: $message-highlight-colors-other-link-color;
- }
-
- &--other {
- color: $message-highlight-colors-other-color;
- }
-
- @include templates.focus-state;
- }
-}
diff --git a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessage.styles.scss b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessage.styles.scss
deleted file mode 100644
index 10a6cfb293..0000000000
--- a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessage.styles.scss
+++ /dev/null
@@ -1,87 +0,0 @@
-@use '../../../styles/lengths.scss';
-@use '../../../styles/colors.scss';
-@use '../../../styles/typography.scss';
-@use '../../../styles/mixins/size.scss';
-@use '../mixins.scss';
-
-.rcx-message.rcx-message-thread {
- @include typography.use-font-scale(c1);
- @include typography.use-with-truncated-text();
- display: flex;
- flex-direction: column;
-
- padding-block: 0;
-
- color: colors.font(info);
-}
-
-.rcx-message-thread {
- &__container {
- @include typography.use-with-truncated-text();
- display: flex;
- flex-shrink: 1;
-
- width: 100%;
- margin: lengths.margin(4);
- }
-
- &__row {
- @include typography.use-with-truncated-text();
- display: flex;
- flex-direction: row;
- align-items: center;
- flex-shrink: 1;
-
- width: 100%;
-
- cursor: pointer;
- }
-
- &__message {
- color: colors.font(default);
- @include typography.use-with-truncated-text();
- }
-
- &__origin {
- @include typography.use-font-scale(c1);
- @include typography.use-with-truncated-text();
- flex-shrink: 1;
-
- cursor: pointer;
-
- color: colors.font(info);
-
- &--system {
- color: colors.font(default);
-
- &::first-letter {
- text-transform: uppercase;
- }
- }
- }
-
- &__icon {
- color: colors.font(info);
-
- &--unfollow,
- &--follow {
- cursor: pointer;
-
- color: colors.font(secondary-info);
- }
-
- &--unfollow {
- @include mixins.visible-on-hover();
- }
- }
-
- &__emoji {
- display: inline-block;
-
- margin-inline: lengths.margin(2);
-
- background-size: contain;
-
- @include size.square(lengths.size(12));
- }
-}
diff --git a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessage.tsx b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessage.tsx
index 1c80084a5c..1aa571a47a 100644
--- a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessage.tsx
+++ b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessage.tsx
@@ -1,9 +1,62 @@
-import Message, { type MessageProps } from '../Message';
+import type { AllHTMLAttributes } from 'react';
+import { forwardRef } from 'react';
+import { styled } from '@tamagui/core';
-export type ThreadMessageProps = MessageProps;
+import { RcxView } from '../../../primitives';
+import MessageLeftContainer from '../MessageLeftContainer';
-const ThreadMessage = (props: ThreadMessageProps) => (
-
+import ThreadMessageBody from './ThreadMessageBody';
+import ThreadMessageContainer from './ThreadMessageContainer';
+import ThreadMessageFollow from './ThreadMessageFollow';
+import ThreadMessageIconThread from './ThreadMessageIconThread';
+import ThreadMessageOrigin from './ThreadMessageOrigin';
+import ThreadMessageRow from './ThreadMessageRow';
+import ThreadMessageUnfollow from './ThreadMessageUnfollow';
+
+export type ThreadMessageProps = AllHTMLAttributes & {
+ clickable?: boolean;
+ sequential?: boolean;
+ className?: string;
+ isSelected?: boolean;
+ isEditing?: boolean;
+ isPending?: boolean;
+ highlight?: boolean;
+};
+
+const ThreadMessageFrame = styled(RcxView, {
+ name: 'ThreadMessage',
+ display: 'flex',
+ flexDirection: 'column',
+ paddingBlock: 0,
+ color: '$fontInfo',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ marginInline: 2,
+ paddingInline: 20,
+ hoverStyle: {
+ backgroundColor: '$surfaceHover',
+ },
+});
+
+const ThreadMessage = Object.assign(
+ forwardRef(function ThreadMessage(
+ { className: _className, ...props },
+ ref,
+ ) {
+ return ;
+ }),
+ {
+ Row: ThreadMessageRow,
+ Container: ThreadMessageContainer,
+ LeftContainer: MessageLeftContainer,
+ Origin: ThreadMessageOrigin,
+ Message: ThreadMessageBody,
+ Follow: ThreadMessageFollow,
+ Unfollow: ThreadMessageUnfollow,
+ Icon: ThreadMessageIconThread,
+ },
);
export default ThreadMessage;
diff --git a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageBody.tsx b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageBody.tsx
index 8c439c7194..4dfbd6841b 100644
--- a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageBody.tsx
+++ b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageBody.tsx
@@ -1,14 +1,29 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../../primitives';
export type ThreadMessageBodyProps = {
children?: ReactNode;
};
+const BodyFrame = styled(RcxText, {
+ name: 'ThreadMessageBody',
+ fontFamily: '$body',
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ color: '$fontDefault',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+});
+
const ThreadMessageBody = (props: ThreadMessageBodyProps) => (
-
+
);
export default ThreadMessageBody;
diff --git a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageContainer.tsx b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageContainer.tsx
index 1172a8c15b..fab64dedf2 100644
--- a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageContainer.tsx
+++ b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageContainer.tsx
@@ -1,14 +1,27 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../../primitives';
export type ThreadMessageContainerProps = {
children?: ReactNode;
};
+const ContainerFrame = styled(RcxView, {
+ name: 'ThreadMessageContainer',
+ display: 'flex',
+ flexDirection: 'row',
+ flexShrink: 1,
+ width: '100%',
+ margin: 4,
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+});
+
const ThreadMessageContainer = (props: ThreadMessageContainerProps) => (
-
+
);
export default ThreadMessageContainer;
diff --git a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageEmoji.tsx b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageEmoji.tsx
index 7eb7f0b64c..91b29a99b7 100644
--- a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageEmoji.tsx
+++ b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageEmoji.tsx
@@ -8,7 +8,14 @@ const ThreadMessageEmoji = ({
...props
}: ThreadMessageEmojiProps) => (
);
diff --git a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageFollow.tsx b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageFollow.tsx
index 25ff9e1d6c..7a693a555d 100644
--- a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageFollow.tsx
+++ b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageFollow.tsx
@@ -2,8 +2,9 @@ import ThreadMessageIcon from './ThreadMessageIcon';
const ThreadMessageFollow = () => (
);
diff --git a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageIcon.tsx b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageIcon.tsx
index ad52d66e51..671592bd2c 100644
--- a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageIcon.tsx
+++ b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageIcon.tsx
@@ -4,8 +4,8 @@ export type ThreadMessageIconProps = IconProps;
const ThreadMessageIcon = ({ ...props }: IconProps) => (
);
diff --git a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageOrigin.tsx b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageOrigin.tsx
index 7f304f8aca..25dc01392f 100644
--- a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageOrigin.tsx
+++ b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageOrigin.tsx
@@ -1,24 +1,42 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../../primitives';
export type ThreadMessageOriginProps = {
children?: ReactNode;
system?: boolean;
};
+const OriginFrame = styled(RcxText, {
+ name: 'ThreadMessageOrigin',
+ fontFamily: '$body',
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+ flexShrink: 1,
+ cursor: 'pointer',
+ color: '$fontInfo',
+ variants: {
+ system: {
+ true: {
+ color: '$fontDefault',
+ },
+ },
+ } as const,
+});
+
const ThreadMessageOrigin = ({
children,
system,
}: ThreadMessageOriginProps) => (
-
- {children}
-
+ {children}
);
export default ThreadMessageOrigin;
diff --git a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageRow.tsx b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageRow.tsx
index 3f22875dbe..bd36873ef5 100644
--- a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageRow.tsx
+++ b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageRow.tsx
@@ -1,9 +1,26 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../../primitives';
export type ThreadMessageRowProps = HTMLAttributes;
+const RowFrame = styled(RcxView, {
+ name: 'ThreadMessageRow',
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ flexShrink: 1,
+ width: '100%',
+ cursor: 'pointer',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+});
+
const ThreadMessageRow = (props: ThreadMessageRowProps) => (
-
+
);
export default ThreadMessageRow;
diff --git a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageUnfollow.tsx b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageUnfollow.tsx
index e4665762bb..9ee699716b 100644
--- a/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageUnfollow.tsx
+++ b/packages/fuselage/src/components/Message/ThreadMessage/ThreadMessageUnfollow.tsx
@@ -2,8 +2,9 @@ import ThreadMessageIcon from './ThreadMessageIcon';
const ThreadMessageUnfollow = () => (
);
diff --git a/packages/fuselage/src/components/Message/mixins.scss b/packages/fuselage/src/components/Message/mixins.scss
deleted file mode 100644
index 4e9141cc8e..0000000000
--- a/packages/fuselage/src/components/Message/mixins.scss
+++ /dev/null
@@ -1,56 +0,0 @@
-@use '../../styles/lengths.scss';
-@use '../../styles/colors.scss';
-
-@mixin container() {
- &-container {
- display: flex;
- flex-direction: column;
- flex-grow: 1;
- flex-shrink: 1;
-
- min-width: 1px;
- margin-block: lengths.margin(-2);
- margin-inline: lengths.margin(4);
-
- &--fixed {
- align-items: center;
- flex-grow: 0;
- flex-shrink: 0;
- }
-
- &--left {
- align-items: center;
- align-items: flex-end;
- flex-grow: 0;
- flex-shrink: 0;
-
- width: lengths.size(36);
- margin-block: lengths.margin(-2);
- }
- }
-}
-
-@mixin visible-on-hover {
- .rcx-message:not(:hover) & {
- display: none;
- }
-}
-
-@mixin message-focus-visible {
- @content;
-
- opacity: 0;
-
- .rcx-message:hover &,
- .rcx-message:focus-visible & {
- opacity: 1;
- }
-
- /**
- ** This is a hack to get focus within only for keyboard.
- ** :has pseudo class are not stable on some Firefox versions
- **/
- .rcx-message:has(:focus-visible) & {
- opacity: 1;
- }
-}
diff --git a/packages/fuselage/src/components/Modal/Modal.styles.scss b/packages/fuselage/src/components/Modal/Modal.styles.scss
deleted file mode 100644
index 8a6aa4b86b..0000000000
--- a/packages/fuselage/src/components/Modal/Modal.styles.scss
+++ /dev/null
@@ -1,114 +0,0 @@
-@use '../../styles/colors.scss';
-@use '../../styles/lengths.scss';
-@use '../../styles/typography.scss';
-
-$modal-container-margin: theme('modal-container-margin', lengths.size(24));
-
-$modal-margin: theme('modal-margin', auto);
-
-.rcx-modal {
- position: static;
-
- display: flex;
-
- width: 100%;
- max-height: 100%;
- margin: $modal-margin;
-
- background: none;
-
- &__inner {
- display: flex;
- flex-direction: column;
- flex-grow: 1;
-
- width: 100%;
-
- min-width: 0;
- padding: 0;
-
- color: colors.font(default);
- border-radius: theme('modal-border-radius', lengths.border-radius(large));
- background-color: colors.surface(light);
- @include typography.use-font-scale(p2);
- }
-
- &__header {
- margin: $modal-container-margin;
-
- &-text {
- display: flex;
- flex-direction: column;
- flex-grow: 1;
- flex-shrink: 1;
- @include typography.use-text-ellipsis;
- }
- }
-
- &__header-inner {
- display: flex;
- flex-wrap: nowrap;
-
- margin: -4px;
- }
-
- &__title {
- @include typography.use-text-ellipsis;
- flex-grow: 1;
- flex-shrink: 1;
-
- white-space: nowrap;
-
- color: colors.font(default);
- @include typography.use-font-scale(h2);
- }
-
- &__tagline {
- color: colors.font(default);
-
- @include typography.use-font-scale(c2);
- }
-
- &__hero-image {
- display: block;
-
- width: 100%;
- height: auto;
- object-fit: contain;
-
- &-wrapper {
- margin: 0;
- margin-bottom: lengths.size(24);
- margin-inline: -(lengths.size(24));
- }
- }
-
- &__backdrop {
- position: fixed;
-
- z-index: 100;
- inset: 0;
-
- display: flex;
- flex-direction: column;
-
- background-color: colors.surface(overlay);
- }
-
- &__footer {
- display: flex;
- align-items: center;
-
- margin: $modal-container-margin;
-
- &-annotation {
- color: colors.font(secondary-info);
- @include typography.use-font-scale(c1);
- }
- }
-
- @include on-breakpoint(sm) {
- max-width: lengths.size(640);
- padding: lengths.padding(16);
- }
-}
diff --git a/packages/fuselage/src/components/Modal/Modal.tsx b/packages/fuselage/src/components/Modal/Modal.tsx
index 97137fa9e1..cb07937400 100644
--- a/packages/fuselage/src/components/Modal/Modal.tsx
+++ b/packages/fuselage/src/components/Modal/Modal.tsx
@@ -1,8 +1,40 @@
import type { ElementType, ReactNode } from 'react';
import { createElement, forwardRef } from 'react';
+import { styled } from '@tamagui/core';
+import { RcxView } from '../../primitives';
import { Box, type BoxProps } from '../Box';
+const ModalFrame = styled(RcxView, {
+ name: 'Modal',
+ role: 'dialog',
+ position: 'static',
+ display: 'flex',
+ flexDirection: 'row',
+ width: '100%',
+ maxHeight: '100%',
+ margin: 'auto',
+ backgroundColor: 'transparent',
+});
+
+const ModalInnerFrame = styled(RcxView, {
+ name: 'ModalInner',
+ display: 'flex',
+ flexDirection: 'column',
+ flexGrow: 1,
+ width: '100%',
+ minWidth: 0,
+ padding: 0,
+ color: '$fontDefault',
+ borderRadius: '$x8',
+ backgroundColor: '$surfaceLight',
+ borderWidth: 1,
+ borderStyle: 'solid',
+ borderColor: '$shadowElevationBorder',
+ boxShadow:
+ '0px 0px 2px 0px var(--shadowElevation2x), 0px 0px 12px 0px var(--shadowElevation2y)',
+});
+
export type ModalProps = {
wrapperFunction?: (
props: Pick,
@@ -12,18 +44,27 @@ export type ModalProps = {
const Modal = forwardRef(
({ children, wrapper = Box, wrapperFunction, ...props }, ref) => {
- const wrapperProps = {
- children,
- className: 'rcx-modal__inner',
- elevation: '2',
- } as const;
+ if (wrapperFunction || wrapper !== Box) {
+ // Legacy path: use SCSS-based wrapper
+ const wrapperProps = {
+ children,
+ className: 'rcx-modal__inner',
+ elevation: '2',
+ } as const;
+
+ return (
+
+ {wrapperFunction
+ ? wrapperFunction(wrapperProps)
+ : createElement(wrapper, wrapperProps)}
+
+ );
+ }
return (
-
- {wrapperFunction
- ? wrapperFunction(wrapperProps)
- : createElement(wrapper, wrapperProps)}
-
+
+ {children}
+
);
},
);
diff --git a/packages/fuselage/src/components/Modal/ModalBackdrop.tsx b/packages/fuselage/src/components/Modal/ModalBackdrop.tsx
index cb9ab42737..5c710e8485 100644
--- a/packages/fuselage/src/components/Modal/ModalBackdrop.tsx
+++ b/packages/fuselage/src/components/Modal/ModalBackdrop.tsx
@@ -1,9 +1,23 @@
-import { Box, type BoxProps } from '../Box';
+import type { AllHTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
-export type ModalBackdropProps = BoxProps;
+import { RcxView } from '../../primitives';
+
+const ModalBackdropFrame = styled(RcxView, {
+ name: 'ModalBackdrop',
+ position: 'fixed',
+ zIndex: '$x100',
+ // @ts-ignore - inset shorthand
+ inset: 0,
+ display: 'flex',
+ flexDirection: 'column',
+ backgroundColor: '$surfaceOverlay',
+});
+
+export type ModalBackdropProps = AllHTMLAttributes;
const ModalBackdrop = (props: ModalBackdropProps) => (
-
+
);
export default ModalBackdrop;
diff --git a/packages/fuselage/src/components/Modal/ModalFooter.tsx b/packages/fuselage/src/components/Modal/ModalFooter.tsx
index c1741a8be0..b39b0d4645 100644
--- a/packages/fuselage/src/components/Modal/ModalFooter.tsx
+++ b/packages/fuselage/src/components/Modal/ModalFooter.tsx
@@ -1,14 +1,28 @@
-import { Box, type BoxProps } from '../Box';
+import type { AllHTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
-export type ModalFooterProps = BoxProps;
+import { RcxView } from '../../primitives';
+
+const ModalFooterFrame = styled(RcxView, {
+ name: 'ModalFooter',
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ margin: '$x24',
+});
+
+export type ModalFooterProps = {
+ justifyContent?: 'start' | 'center' | 'end' | 'space-between';
+} & AllHTMLAttributes;
const ModalFooter = ({
children,
justifyContent = 'end',
+ ...props
}: ModalFooterProps) => (
-
+
{children}
-
+
);
export default ModalFooter;
diff --git a/packages/fuselage/src/components/Modal/ModalFooterAnnotation.tsx b/packages/fuselage/src/components/Modal/ModalFooterAnnotation.tsx
index 2cdae544ba..3375a2aef9 100644
--- a/packages/fuselage/src/components/Modal/ModalFooterAnnotation.tsx
+++ b/packages/fuselage/src/components/Modal/ModalFooterAnnotation.tsx
@@ -1,9 +1,24 @@
-import { Box, type BoxProps } from '../Box';
+import type { AllHTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
-export type ModalFooterAnnotationProps = BoxProps;
+import { RcxText } from '../../primitives';
+
+const ModalFooterAnnotationFrame = styled(RcxText, {
+ name: 'ModalFooterAnnotation',
+ display: 'block',
+ color: '$fontSecondaryInfo',
+ fontFamily: '$body',
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ overflowWrap: 'normal',
+});
+
+export type ModalFooterAnnotationProps = AllHTMLAttributes;
const ModalFooterAnnotation = ({ children }: ModalFooterAnnotationProps) => (
- {children}
+ {children}
);
export default ModalFooterAnnotation;
diff --git a/packages/fuselage/src/components/Modal/ModalHeader.tsx b/packages/fuselage/src/components/Modal/ModalHeader.tsx
index 9eded5fe40..7708d87dfc 100644
--- a/packages/fuselage/src/components/Modal/ModalHeader.tsx
+++ b/packages/fuselage/src/components/Modal/ModalHeader.tsx
@@ -1,14 +1,31 @@
-import { Box, type BoxProps } from '../Box';
+import type { AllHTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../primitives';
import { Margins } from '../Margins';
-export type ModalHeaderProps = BoxProps;
+const ModalHeaderFrame = styled(RcxView, {
+ name: 'ModalHeader',
+ tag: 'header',
+ margin: '$x24',
+});
+
+const ModalHeaderInner = styled(RcxView, {
+ name: 'ModalHeaderInner',
+ display: 'flex',
+ flexDirection: 'row',
+ flexWrap: 'nowrap',
+ margin: -4,
+});
+
+export type ModalHeaderProps = AllHTMLAttributes;
const ModalHeader = ({ children, ...props }: ModalHeaderProps) => (
-
-
+
+
{children}
-
-
+
+
);
export default ModalHeader;
diff --git a/packages/fuselage/src/components/Modal/ModalHeaderText.tsx b/packages/fuselage/src/components/Modal/ModalHeaderText.tsx
index 6f7541d0ff..e93e741068 100644
--- a/packages/fuselage/src/components/Modal/ModalHeaderText.tsx
+++ b/packages/fuselage/src/components/Modal/ModalHeaderText.tsx
@@ -1,11 +1,24 @@
-import { Box, type BoxProps } from '../Box';
+import type { AllHTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
-export type ModalHeaderTextProps = BoxProps;
+import { RcxView } from '../../primitives';
+
+const ModalHeaderTextFrame = styled(RcxView, {
+ name: 'ModalHeaderText',
+ display: 'flex',
+ flexDirection: 'column',
+ flexGrow: 1,
+ flexShrink: 1,
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+});
+
+export type ModalHeaderTextProps = AllHTMLAttributes;
const ModalHeaderText = ({ children, ...props }: ModalHeaderTextProps) => (
-
+
{children}
-
+
);
export default ModalHeaderText;
diff --git a/packages/fuselage/src/components/Modal/ModalHeroImage.tsx b/packages/fuselage/src/components/Modal/ModalHeroImage.tsx
index 31242f4477..aafd500023 100644
--- a/packages/fuselage/src/components/Modal/ModalHeroImage.tsx
+++ b/packages/fuselage/src/components/Modal/ModalHeroImage.tsx
@@ -1,11 +1,32 @@
-import { Box, type BoxProps } from '../Box';
+import type { AllHTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
-export type ModalHeroImageProps = BoxProps;
+import { RcxView } from '../../primitives';
+
+const ModalHeroImageWrapper = styled(RcxView, {
+ name: 'ModalHeroImageWrapper',
+ tag: 'figure',
+ margin: 0,
+ marginBottom: '$x24',
+ marginInline: -24,
+});
+
+const ModalHeroImageElement = styled(RcxView, {
+ name: 'ModalHeroImage',
+ tag: 'img',
+ display: 'block',
+ width: '100%',
+ height: 'auto',
+ // @ts-ignore - objectFit for img
+ objectFit: 'contain',
+});
+
+export type ModalHeroImageProps = AllHTMLAttributes;
const ModalHeroImage = ({ ...props }: ModalHeroImageProps) => (
-
-
-
+
+
+
);
export default ModalHeroImage;
diff --git a/packages/fuselage/src/components/Modal/ModalTagline.tsx b/packages/fuselage/src/components/Modal/ModalTagline.tsx
index c7078178b0..086644b401 100644
--- a/packages/fuselage/src/components/Modal/ModalTagline.tsx
+++ b/packages/fuselage/src/components/Modal/ModalTagline.tsx
@@ -1,11 +1,26 @@
-import { Box, type BoxProps } from '../Box';
+import type { AllHTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
-export type ModalTaglineProps = BoxProps;
+import { RcxText } from '../../primitives';
+
+const ModalTaglineFrame = styled(RcxText, {
+ name: 'ModalTagline',
+ display: 'block',
+ color: '$fontDefault',
+ fontFamily: '$body',
+ fontSize: '$c2',
+ fontWeight: '$c2',
+ lineHeight: '$c2',
+ letterSpacing: '$c2',
+ overflowWrap: 'normal',
+});
+
+export type ModalTaglineProps = AllHTMLAttributes;
const ModalTagline = ({ children, ...props }: ModalTaglineProps) => (
-
+
{children}
-
+
);
export default ModalTagline;
diff --git a/packages/fuselage/src/components/Modal/ModalTitle.tsx b/packages/fuselage/src/components/Modal/ModalTitle.tsx
index 29f9b474ed..fd1fc8303f 100644
--- a/packages/fuselage/src/components/Modal/ModalTitle.tsx
+++ b/packages/fuselage/src/components/Modal/ModalTitle.tsx
@@ -1,11 +1,32 @@
-import { Box, type BoxProps } from '../Box';
+import type { AllHTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
-export type ModalTitleProps = BoxProps;
+import { RcxText } from '../../primitives';
+
+const ModalTitleFrame = styled(RcxText, {
+ name: 'ModalTitle',
+ tag: 'h2',
+ display: 'block',
+ flexGrow: 1,
+ flexShrink: 1,
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ color: '$fontDefault',
+ fontFamily: '$body',
+ fontSize: '$h2',
+ fontWeight: '$h2',
+ lineHeight: '$h2',
+ letterSpacing: '$h2',
+ overflowWrap: 'normal',
+});
+
+export type ModalTitleProps = AllHTMLAttributes;
const ModalTitle = ({ children, ...props }: ModalTitleProps) => (
-
+
{children}
-
+
);
export default ModalTitle;
diff --git a/packages/fuselage/src/components/NavBar/NavBar.styles.scss b/packages/fuselage/src/components/NavBar/NavBar.styles.scss
deleted file mode 100644
index 13e74ffa5a..0000000000
--- a/packages/fuselage/src/components/NavBar/NavBar.styles.scss
+++ /dev/null
@@ -1,24 +0,0 @@
-@use '../../styles/colors.scss';
-@use '../../styles/lengths.scss';
-
-.rcx-navbar {
- display: flex;
- justify-content: space-between;
- align-items: center;
-
- width: 100%;
- padding: lengths.padding(8) lengths.padding(16);
-
- border-bottom: lengths.border-width(default) solid colors.stroke(light);
-
- background-color: colors.surface(sidebar);
-
- &-section {
- display: flex;
- align-items: center;
- }
-
- &-divider {
- border-color: colors.stroke(medium);
- }
-}
diff --git a/packages/fuselage/src/components/NavBar/NavBar.tsx b/packages/fuselage/src/components/NavBar/NavBar.tsx
index ee7d8fcad9..a135df4cb1 100644
--- a/packages/fuselage/src/components/NavBar/NavBar.tsx
+++ b/packages/fuselage/src/components/NavBar/NavBar.tsx
@@ -1,9 +1,26 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../primitives';
+
+const NavBarFrame = styled(RcxView, {
+ name: 'NavBar',
+ tag: 'nav',
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ width: '100%',
+ paddingBlock: '$x8',
+ paddingInline: '$x16',
+ borderBottomWidth: 1,
+ borderBottomStyle: 'solid',
+ borderBottomColor: '$strokeLight',
+ backgroundColor: '$surfaceSidebar',
+});
export type NavBarProps = HTMLAttributes;
-const NavBar = (props: NavBarProps) => (
-
-);
+const NavBar = (props: NavBarProps) => ;
export default NavBar;
diff --git a/packages/fuselage/src/components/NavBar/NavBarDivider.tsx b/packages/fuselage/src/components/NavBar/NavBarDivider.tsx
index 5a0e37363e..8f9e7ba60a 100644
--- a/packages/fuselage/src/components/NavBar/NavBarDivider.tsx
+++ b/packages/fuselage/src/components/NavBar/NavBarDivider.tsx
@@ -4,7 +4,7 @@ import { Divider } from '../Divider';
export type NavBarDividerProps = DividerProps;
const NavBarDivider = (props: NavBarDividerProps) => (
-
+
);
export default NavBarDivider;
diff --git a/packages/fuselage/src/components/NavBar/NavBarSection.tsx b/packages/fuselage/src/components/NavBar/NavBarSection.tsx
index 785633261b..87048af1f8 100644
--- a/packages/fuselage/src/components/NavBar/NavBarSection.tsx
+++ b/packages/fuselage/src/components/NavBar/NavBarSection.tsx
@@ -1,9 +1,19 @@
import type { ComponentType, HTMLAttributes, ReactElement } from 'react';
import { Fragment, Children, isValidElement } from 'react';
+import { styled } from '@tamagui/core';
+import { RcxView } from '../../primitives';
import NavBarDivider from './NavBarDivider';
import NavBarGroup from './NavBarGroup';
+const NavBarSectionFrame = styled(RcxView, {
+ name: 'NavBarSection',
+ tag: 'span',
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+});
+
type ComponentWithDisplayName = {
displayName?: string;
props?: Record;
@@ -23,14 +33,14 @@ const NavBarSection = ({ children, ...props }: NavbarSectionProps) => {
const validChildren = Children.toArray(children).filter(isNavBarGroup);
return (
-
+
{Children.toArray(children).map((child, index) => (
{child}
{index < validChildren.length - 1 && }
))}
-
+
);
};
diff --git a/packages/fuselage/src/components/Option/Option.styles.scss b/packages/fuselage/src/components/Option/Option.styles.scss
deleted file mode 100644
index 080e192969..0000000000
--- a/packages/fuselage/src/components/Option/Option.styles.scss
+++ /dev/null
@@ -1,167 +0,0 @@
-@use '../../styles/colors.scss';
-@use '../../styles/lengths.scss';
-@use '../../styles/typography.scss';
-
-$variants: (
- 'success': colors.status-font(on-success),
- 'danger': colors.status-font(on-danger),
- 'warning': colors.status-font(on-warning),
- 'primary': colors.status-font(on-primary),
-);
-
-%column {
- flex: 0 0 auto;
-
- margin-inline: lengths.margin(4);
-}
-
-.rcx-option {
- @include clickable;
- @include typography.use-font-scale(p2);
-
- display: list-item;
-
- padding-block: lengths.padding(4);
- padding-inline: lengths.padding(12) lengths.padding(24);
-
- list-style: none;
-
- color: colors.font(default);
-
- &__title {
- @include typography.use-font-scale(c2);
-
- padding-block: lengths.padding(8) lengths.padding(4);
- padding-inline: lengths.padding(12) lengths.padding(24);
-
- color: colors.font(default);
- }
-
- &__wrapper {
- display: flex;
- align-items: center;
-
- margin-inline: lengths.margin(-2);
-
- &--align-top {
- align-items: flex-start !important;
- }
- }
-
- &__icon {
- color: inherit;
- }
-
- &__avatar {
- @extend %column;
- }
-
- &__content {
- @include typography.use-text-ellipsis;
- @extend %column;
- flex: 1 1 100%;
-
- text-align: start;
-
- white-space: nowrap;
- }
-
- &__header {
- @include typography.use-font-scale(micro);
-
- padding-block: lengths.padding(8);
- padding-inline: lengths.padding(16);
-
- text-transform: uppercase;
-
- font-weight: 400;
- }
-
- &__menu-wrapper {
- flex-shrink: 0;
-
- width: 0;
-
- height: 100%;
-
- opacity: 0;
-
- &:has(> [aria-expanded='true']) {
- width: lengths.size(28);
-
- opacity: 1;
- }
- }
-
- &__column {
- @extend %column;
- display: flex;
-
- justify-content: center;
- align-items: center;
-
- min-width: lengths.size(20);
- min-height: lengths.size(20);
- }
-
- &__input {
- display: flex;
-
- justify-content: flex-end;
- align-items: center;
-
- min-width: lengths.size(20);
- min-height: lengths.size(20);
- margin-inline: lengths.margin(16) lengths.margin(-12);
- }
-
- &__description {
- @include typography.use-font-scale(p2);
- @extend %column;
- display: inline;
-
- color: colors.font(secondary-info);
- }
-
- &__description-block {
- @include typography.use-font-scale(p2);
-
- padding: lengths.margin(4);
-
- white-space: normal;
- word-break: break-word;
-
- color: colors.font(secondary-info);
- }
-
- &:hover,
- &--focus {
- background: colors.surface(hover);
- }
-
- &--selected {
- background: colors.surface(selected);
- }
-
- &--disabled {
- cursor: not-allowed;
-
- color: colors.font(disabled);
- }
-
- &:hover &__menu-wrapper,
- &:focus-within &__menu-wrapper {
- display: flex;
- align-items: center;
-
- width: lengths.size(28);
-
- opacity: 1;
- }
-
- @each $name, $color in $variants {
- &--#{$name} {
- color: theme('option-color-variant-#{$name}', $color);
- }
- }
-}
diff --git a/packages/fuselage/src/components/Option/Option.tsx b/packages/fuselage/src/components/Option/Option.tsx
index c73e58d41a..c4625f1210 100644
--- a/packages/fuselage/src/components/Option/Option.tsx
+++ b/packages/fuselage/src/components/Option/Option.tsx
@@ -1,7 +1,9 @@
import type { Ref, ReactNode, MouseEvent, AllHTMLAttributes } from 'react';
import { forwardRef, memo } from 'react';
+import { styled } from '@tamagui/core';
import { prevent } from '../../helpers/prevent';
+import { RcxText, RcxView } from '../../primitives';
import type { BoxProps } from '../Box';
import type { IconProps } from '../Icon';
@@ -10,6 +12,88 @@ import OptionColumn from './OptionColumn';
import OptionContent from './OptionContent';
import OptionIcon from './OptionIcon';
+// .rcx-option
+const OptionFrame = styled(RcxView, {
+ name: 'OptionFrame',
+
+ display: 'list-item',
+ margin: 0,
+ cursor: 'pointer',
+
+ paddingBlock: '$x4',
+ paddingInlineStart: '$x12',
+ paddingInlineEnd: '$x24',
+
+ listStyleType: 'none',
+
+ color: '$fontDefault',
+
+ fontFamily: '$body',
+ fontSize: '$p2',
+ fontWeight: '$p2',
+ lineHeight: '$p2',
+ letterSpacing: '$p2',
+
+ hoverStyle: {
+ backgroundColor: '$surfaceHover',
+ },
+
+ variants: {
+ focus: {
+ true: {
+ backgroundColor: '$surfaceHover',
+ },
+ },
+
+ selected: {
+ true: {
+ backgroundColor: '$surfaceSelected',
+ },
+ },
+
+ isDisabled: {
+ true: {
+ cursor: 'not-allowed',
+ color: '$fontDisabled',
+ },
+ },
+
+ variant: {
+ success: {
+ color: '$statusFontOnSuccess',
+ },
+ danger: {
+ color: '$statusFontOnDanger',
+ },
+ warning: {
+ color: '$statusFontOnWarning',
+ },
+ primary: {
+ color: '$statusFontOnInfo',
+ },
+ },
+ } as const,
+});
+
+// .rcx-option__wrapper
+const OptionWrapper = styled(RcxText, {
+ name: 'OptionWrapper',
+
+ display: 'flex',
+ alignItems: 'center',
+
+ marginInline: -2,
+ paddingInline: '$x4',
+
+ variants: {
+ alignTop: {
+ true: {
+ alignItems: 'flex-start',
+ },
+ },
+ } as const,
+});
+
export type OptionProps = {
is?: BoxProps['is'];
id?: string;
@@ -53,7 +137,7 @@ export type OptionProps = {
*/
const Option = forwardRef(function Option(
{
- is: Tag = 'li',
+ is: Tag,
id,
children,
label,
@@ -73,11 +157,13 @@ const Option = forwardRef(function Option(
ref,
) {
return (
- (function Option(
]
.filter(Boolean)
.join(' ')}
+ focus={focus || undefined}
+ selected={selected || undefined}
+ isDisabled={disabled || undefined}
+ variant={variant}
>
-
+
{avatar && {avatar}}
{icon && }
{gap && }
{label && {label}}
{label !== children && children}
-
-
+
+
);
});
diff --git a/packages/fuselage/src/components/Option/OptionAvatar.tsx b/packages/fuselage/src/components/Option/OptionAvatar.tsx
index e30db4e92d..b87f52b37d 100644
--- a/packages/fuselage/src/components/Option/OptionAvatar.tsx
+++ b/packages/fuselage/src/components/Option/OptionAvatar.tsx
@@ -1,11 +1,25 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../primitives';
+
+// .rcx-option__avatar — extends %column
+const StyledOptionAvatar = styled(RcxView, {
+ name: 'OptionAvatar',
+
+ flexGrow: 0,
+ flexShrink: 0,
+ flexBasis: 'auto',
+
+ marginInline: '$x4',
+});
export type OptionAvatarProps = {
children?: ReactNode;
};
const OptionAvatar = (props: OptionAvatarProps) => (
-
+
);
export default OptionAvatar;
diff --git a/packages/fuselage/src/components/Option/OptionColumn.tsx b/packages/fuselage/src/components/Option/OptionColumn.tsx
index 77df1f7753..140041a5b6 100644
--- a/packages/fuselage/src/components/Option/OptionColumn.tsx
+++ b/packages/fuselage/src/components/Option/OptionColumn.tsx
@@ -1,11 +1,31 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../primitives';
+
+// .rcx-option__column — extends %column + flex display
+const StyledOptionColumn = styled(RcxView, {
+ name: 'OptionColumn',
+
+ display: 'flex',
+ flexGrow: 0,
+ flexShrink: 0,
+ flexBasis: 'auto',
+
+ justifyContent: 'center',
+ alignItems: 'center',
+
+ minWidth: '$x20',
+ minHeight: '$x20',
+ marginInline: '$x4',
+});
export type OptionColumnProps = {
children?: ReactNode;
};
const OptionColumn = (props: OptionColumnProps) => (
-
+
);
export default OptionColumn;
diff --git a/packages/fuselage/src/components/Option/OptionContent.tsx b/packages/fuselage/src/components/Option/OptionContent.tsx
index 5d1023a311..723e35c1b0 100644
--- a/packages/fuselage/src/components/Option/OptionContent.tsx
+++ b/packages/fuselage/src/components/Option/OptionContent.tsx
@@ -1,9 +1,38 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../primitives';
+
+// .rcx-option__content — extends %column + text-ellipsis
+const StyledOptionContent = styled(RcxText, {
+ name: 'OptionContent',
+
+ display: 'block',
+
+ flexGrow: 1,
+ flexShrink: 1,
+ flexBasis: '100%',
+
+ marginInline: '$x4',
+
+ textAlign: 'start',
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+
+ color: 'inherit',
+
+ fontFamily: '$body',
+ fontSize: '$p2',
+ fontWeight: '$p2',
+ lineHeight: '$p2',
+ letterSpacing: '$p2',
+});
export type OptionContentProps = HTMLAttributes;
const OptionContent = (props: OptionContentProps) => (
-
+
);
export default OptionContent;
diff --git a/packages/fuselage/src/components/Option/OptionDescription.tsx b/packages/fuselage/src/components/Option/OptionDescription.tsx
index 1b83348a4a..46da331b6e 100644
--- a/packages/fuselage/src/components/Option/OptionDescription.tsx
+++ b/packages/fuselage/src/components/Option/OptionDescription.tsx
@@ -1,11 +1,35 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../primitives';
+
+// .rcx-option__description — extends %column + inline display
+const StyledOptionDescription = styled(RcxText, {
+ name: 'OptionDescription',
+
+ display: 'inline',
+
+ flexGrow: 0,
+ flexShrink: 0,
+ flexBasis: 'auto',
+
+ marginInline: '$x4',
+
+ color: '$fontSecondaryInfo',
+
+ fontFamily: '$body',
+ fontSize: '$p2',
+ fontWeight: '$p2',
+ lineHeight: '$p2',
+ letterSpacing: '$p2',
+});
export type OptionDescriptionProps = {
children?: ReactNode;
};
const OptionDescription = (props: OptionDescriptionProps) => (
-
+
);
export default OptionDescription;
diff --git a/packages/fuselage/src/components/Option/OptionDescriptionBlock.tsx b/packages/fuselage/src/components/Option/OptionDescriptionBlock.tsx
index 16b69d0594..1a1e5f4034 100644
--- a/packages/fuselage/src/components/Option/OptionDescriptionBlock.tsx
+++ b/packages/fuselage/src/components/Option/OptionDescriptionBlock.tsx
@@ -1,11 +1,33 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../primitives';
+
+// .rcx-option__description-block
+const StyledOptionDescriptionBlock = styled(RcxText, {
+ name: 'OptionDescriptionBlock',
+
+ display: 'block',
+
+ padding: '$x4',
+
+ whiteSpace: 'normal',
+
+ color: '$fontSecondaryInfo',
+
+ fontFamily: '$body',
+ fontSize: '$p2',
+ fontWeight: '$p2',
+ lineHeight: '$p2',
+ letterSpacing: '$p2',
+});
export type OptionDescriptionBlockProps = {
children?: ReactNode;
};
const OptionDescriptionBlock = (props: OptionDescriptionBlockProps) => (
-
+
);
export default OptionDescriptionBlock;
diff --git a/packages/fuselage/src/components/Option/OptionHeader.tsx b/packages/fuselage/src/components/Option/OptionHeader.tsx
index 93b2809987..f7006794b4 100644
--- a/packages/fuselage/src/components/Option/OptionHeader.tsx
+++ b/packages/fuselage/src/components/Option/OptionHeader.tsx
@@ -1,11 +1,35 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../primitives';
+
+// .rcx-option__header
+const StyledOptionHeader = styled(RcxText, {
+ name: 'OptionHeader',
+
+ display: 'block',
+
+ paddingBlock: '$x8',
+ paddingInline: '$x16',
+
+ textTransform: 'uppercase',
+
+ color: '$fontDefault',
+
+ fontFamily: '$body',
+ fontSize: '$micro',
+ lineHeight: '$micro',
+ letterSpacing: '$micro',
+ // SCSS: font-weight: 400 (overrides micro's 700)
+ fontWeight: '400',
+});
export type OptionHeaderProps = {
children: ReactNode;
};
const OptionHeader = ({ children }: OptionHeaderProps) => (
- {children}
+ {children}
);
export default OptionHeader;
diff --git a/packages/fuselage/src/components/Option/OptionIcon.tsx b/packages/fuselage/src/components/Option/OptionIcon.tsx
index 2540f5312a..64b40d7d37 100644
--- a/packages/fuselage/src/components/Option/OptionIcon.tsx
+++ b/packages/fuselage/src/components/Option/OptionIcon.tsx
@@ -4,9 +4,10 @@ import OptionColumn from './OptionColumn';
export type OptionIconProps = IconProps;
+// .rcx-option__icon — color: inherit
const OptionIcon = (props: OptionIconProps) => (
-
+
);
diff --git a/packages/fuselage/src/components/Option/OptionInput.tsx b/packages/fuselage/src/components/Option/OptionInput.tsx
index 694bc5ca60..118ac60d46 100644
--- a/packages/fuselage/src/components/Option/OptionInput.tsx
+++ b/packages/fuselage/src/components/Option/OptionInput.tsx
@@ -1,11 +1,29 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../primitives';
+
+// .rcx-option__input
+const StyledOptionInput = styled(RcxView, {
+ name: 'OptionInput',
+
+ display: 'flex',
+
+ justifyContent: 'flex-end',
+ alignItems: 'center',
+
+ minWidth: '$x20',
+ minHeight: '$x20',
+ marginInlineStart: '$x16',
+ marginInlineEnd: -12,
+});
export type OptionInputProps = {
children?: ReactNode;
};
const OptionInput = (props: OptionInputProps) => (
-
+
);
export default OptionInput;
diff --git a/packages/fuselage/src/components/Option/OptionMenu.tsx b/packages/fuselage/src/components/Option/OptionMenu.tsx
index 4d31308c73..9886ff3c8c 100644
--- a/packages/fuselage/src/components/Option/OptionMenu.tsx
+++ b/packages/fuselage/src/components/Option/OptionMenu.tsx
@@ -1,9 +1,26 @@
import type { HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../primitives';
+
+// .rcx-option__menu-wrapper
+const StyledOptionMenu = styled(RcxView, {
+ name: 'OptionMenu',
+
+ className: 'rcx-box rcx-box--animated',
+
+ flexShrink: 0,
+
+ width: 0,
+ height: '100%',
+
+ opacity: 0,
+});
export type OptionMenuProps = HTMLAttributes;
const OptionMenu = (props: OptionMenuProps) => (
-
+
);
export default OptionMenu;
diff --git a/packages/fuselage/src/components/Option/OptionTitle.tsx b/packages/fuselage/src/components/Option/OptionTitle.tsx
index 8cd74b0ae5..783eb13639 100644
--- a/packages/fuselage/src/components/Option/OptionTitle.tsx
+++ b/packages/fuselage/src/components/Option/OptionTitle.tsx
@@ -1,11 +1,34 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../primitives';
+
+// .rcx-option__title
+const StyledOptionTitle = styled(RcxText, {
+ name: 'OptionTitle',
+
+ display: 'block',
+
+ paddingBlockStart: '$x8',
+ paddingBlockEnd: '$x4',
+ paddingInlineStart: '$x12',
+ paddingInlineEnd: '$x24',
+
+ color: '$fontDefault',
+
+ fontFamily: '$body',
+ fontSize: '$c2',
+ fontWeight: '$c2',
+ lineHeight: '$c2',
+ letterSpacing: '$c2',
+});
export type OptionTitleProps = {
children?: ReactNode;
};
const OptionTitle = (props: OptionTitleProps) => (
-
+
);
export default OptionTitle;
diff --git a/packages/fuselage/src/components/Options/Options.styles.scss b/packages/fuselage/src/components/Options/Options.styles.scss
deleted file mode 100644
index 7b3568c872..0000000000
--- a/packages/fuselage/src/components/Options/Options.styles.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-.rcx-options {
- &:hover {
- .rcx-option--focus:not(.rcx-option--selected):not(:hover) {
- background: initial;
- }
- }
-}
diff --git a/packages/fuselage/src/components/Options/Options.tsx b/packages/fuselage/src/components/Options/Options.tsx
index f912f9f4ef..e519e14661 100644
--- a/packages/fuselage/src/components/Options/Options.tsx
+++ b/packages/fuselage/src/components/Options/Options.tsx
@@ -7,9 +7,11 @@ import type {
SyntheticEvent,
} from 'react';
import { forwardRef, useLayoutEffect, useMemo, useRef } from 'react';
+import { styled } from '@tamagui/core';
import { prevent } from '../../helpers/prevent';
-import { Box, type BoxProps } from '../Box';
+import { RcxView } from '../../primitives';
+import { type BoxProps } from '../Box';
import { Option, OptionHeader, OptionDivider } from '../Option';
import { Scrollable } from '../Scrollable';
import { Tile } from '../Tile';
@@ -17,6 +19,12 @@ import { Tile } from '../Tile';
import type { OptionType } from './OptionType';
import OptionsEmpty from './OptionsEmpty';
+// .rcx-options — on hover, clears focus bg from non-hovered non-selected items
+// This behavior is handled via CSS class still applied to children.
+const OptionsFrame = styled(RcxView, {
+ name: 'OptionsFrame',
+});
+
export type OptionsProps = Omit<
BoxProps,
'onSelect'
@@ -109,7 +117,7 @@ const Options = forwardRef(function Options(
);
return (
-
+
(function Options(
-
+
);
}) as ForwardRefExoticComponent<
PropsWithoutRef & RefAttributes
diff --git a/packages/fuselage/src/components/Pagination/Pagination.styles.scss b/packages/fuselage/src/components/Pagination/Pagination.styles.scss
deleted file mode 100644
index 119b202566..0000000000
--- a/packages/fuselage/src/components/Pagination/Pagination.styles.scss
+++ /dev/null
@@ -1,139 +0,0 @@
-@use '../../styles/colors.scss';
-@use '../../styles/lengths.scss';
-@use '../../styles/typography.scss';
-
-.rcx-pagination {
- display: flex;
- flex-flow: column-reverse nowrap;
-
- align-items: center;
-
- padding: lengths.padding(12) lengths.padding(24);
-
- @include on-breakpoint(sm) {
- flex-direction: column;
- }
-
- @include on-breakpoint(md) {
- flex-direction: row;
- }
-
- &--divider {
- position: relative;
-
- &::before {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
-
- height: 1px;
-
- content: '';
-
- border-radius: theme(
- 'pagination-border-radius',
- lengths.border-radius(small)
- );
- background-color: colors.stroke(extra-light);
- }
- }
-}
-
-.rcx-pagination__left,
-.rcx-pagination__right {
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
- flex: 0 1 auto;
-}
-
-.rcx-pagination__left {
- justify-content: center;
-
- margin-inline-start: lengths.margin(none);
-
- @include on-breakpoint(sm) {
- margin-inline-start: auto;
- }
-
- @include on-breakpoint(md) {
- margin-inline: lengths.margin(none) auto;
- }
-}
-
-.rcx-pagination__right {
- flex-flow: column nowrap;
-
- align-items: center;
-
- margin-inline-start: lengths.margin(none);
-
- @include on-breakpoint(sm) {
- flex-flow: row nowrap;
- align-items: center;
-
- margin-inline-start: auto;
- }
-}
-
-.rcx-pagination__label {
- @include typography.use-font-scale(c1);
-
- color: colors.font(secondary-info);
-}
-
-.rcx-pagination__list {
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
-
- margin-inline: lengths.margin(4);
-}
-
-.rcx-pagination__list-item {
- @include typography.use-font-scale(c1);
- display: flex;
-
- margin-inline: lengths.margin(2);
- padding: lengths.padding(4);
-
- color: colors.font(secondary-info);
-}
-
-.rcx-pagination__link {
- @include typography.use-font-scale(c1);
- @include clickable;
- display: inline-flex;
-
- color: colors.font(info);
- background: transparent;
-
- &:hover:not(.disabled):not(:disabled),
- &:focus:not(.disabled):not(:disabled) {
- text-decoration: underline;
- }
-
- &.disabled,
- &:disabled {
- @include typography.use-font-scale(c2);
- cursor: default;
-
- color: colors.font(default);
- }
-}
-
-.rcx-pagination__back,
-.rcx-pagination__forward {
- @include typography.use-font-scale(c1);
- @include clickable;
- display: inline-flex;
-
- color: colors.font(secondary-info);
- background: transparent;
-
- &.disabled,
- &:disabled {
- color: colors.font(secondary-info);
- }
-}
diff --git a/packages/fuselage/src/components/Pagination/Pagination.tsx b/packages/fuselage/src/components/Pagination/Pagination.tsx
index d63f49347d..29c51b6c7e 100644
--- a/packages/fuselage/src/components/Pagination/Pagination.tsx
+++ b/packages/fuselage/src/components/Pagination/Pagination.tsx
@@ -1,12 +1,150 @@
-import type { Dispatch, SetStateAction } from 'react';
+import { useMediaQuery } from '@rocket.chat/fuselage-hooks';
+import type { ComponentPropsWithoutRef, Dispatch, ReactNode, SetStateAction } from 'react';
import { useMemo } from 'react';
+import { styled } from '@tamagui/core';
-import { Box, type BoxProps } from '../Box';
+import { RcxInteractiveText, RcxText, RcxView } from '../../primitives';
import { Chevron } from '../Chevron';
type ItemsPerPage = 25 | 50 | 100;
-export type PaginationProps = BoxProps & {
+// --- Styled components ---
+
+const PaginationNav = styled(RcxView, {
+ name: 'Pagination',
+ role: 'navigation',
+ display: 'flex',
+ flexDirection: 'column-reverse',
+ flexWrap: 'nowrap',
+ alignItems: 'center',
+ paddingBlock: 12,
+ paddingInline: 24,
+});
+
+const PaginationDividerLine = styled(RcxView, {
+ name: 'PaginationDividerLine',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ height: 1,
+ borderRadius: '$x2',
+ backgroundColor: '$strokeExtraLight',
+});
+
+const PaginationLeft = styled(RcxView, {
+ name: 'PaginationLeft',
+ display: 'flex',
+ flexDirection: 'row',
+ flexWrap: 'nowrap',
+ alignItems: 'center',
+ flexGrow: 0,
+ flexShrink: 1,
+ justifyContent: 'center',
+ marginInlineStart: 0,
+});
+
+const PaginationRight = styled(RcxView, {
+ name: 'PaginationRight',
+ display: 'flex',
+ flexDirection: 'column',
+ flexWrap: 'nowrap',
+ alignItems: 'center',
+ flexGrow: 0,
+ flexShrink: 1,
+ marginInlineStart: 0,
+});
+
+const PaginationLabel = styled(RcxText, {
+ name: 'PaginationLabel',
+ display: 'block',
+ fontFamily: '$body',
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ color: '$fontSecondaryInfo',
+ overflowWrap: 'normal',
+});
+
+const PaginationList = styled(RcxView, {
+ name: 'PaginationList',
+ role: 'list',
+ display: 'flex',
+ flexDirection: 'row',
+ flexWrap: 'nowrap',
+ alignItems: 'center',
+ marginInline: 4,
+});
+
+const PaginationListItem = styled(RcxText, {
+ name: 'PaginationListItem',
+ role: 'listitem',
+ display: 'flex',
+ marginInline: 2,
+ padding: 4,
+ fontFamily: '$body',
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ color: '$fontSecondaryInfo',
+ overflowWrap: 'normal',
+});
+
+const PaginationLink = styled(RcxInteractiveText, {
+ name: 'PaginationLink',
+ role: 'button',
+ display: 'inline-flex',
+ fontFamily: '$body',
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ color: '$fontInfo',
+ backgroundColor: 'transparent',
+ borderWidth: 0,
+ overflowWrap: 'normal',
+
+ hoverStyle: {
+ textDecorationLine: 'underline',
+ },
+
+ focusVisibleStyle: {
+ textDecorationLine: 'underline',
+ },
+
+ disabledStyle: {
+ fontWeight: '$c2',
+ color: '$fontDefault',
+ cursor: 'default',
+ textDecorationLine: 'none',
+ },
+});
+
+const PaginationNavButton = styled(RcxInteractiveText, {
+ name: 'PaginationNavButton',
+ role: 'button',
+ display: 'inline-flex',
+ fontFamily: '$body',
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ color: '$fontSecondaryInfo',
+ backgroundColor: 'transparent',
+ borderWidth: 0,
+ overflowWrap: 'normal',
+
+ disabledStyle: {
+ color: '$fontSecondaryInfo',
+ cursor: 'not-allowed',
+ },
+});
+
+// --- Component ---
+
+export type PaginationProps = ComponentPropsWithoutRef & {
count: number;
current?: number;
divider?: boolean;
@@ -23,6 +161,7 @@ export type PaginationProps = BoxProps & {
}) => string;
onSetCurrent?: Dispatch>;
onSetItemsPerPage?: Dispatch>;
+ children?: ReactNode;
};
const defaultItemsPerPageLabel = () => 'Items per page:';
@@ -54,26 +193,25 @@ const Pagination = ({
divider,
...props
}: PaginationProps) => {
+ const isSm = useMediaQuery('(min-width: 600px)');
+ const isMd = useMediaQuery('(min-width: 768px)');
+
const hasItemsPerPageSelection = itemsPerPageOptions.length > 1;
const currentPage = Math.floor(current / itemsPerPage);
const pages = Math.ceil(count / itemsPerPage);
const displayedPages = useMemo(() => {
if (pages <= 7) {
- // 0 1 2 3 4 5 6
return Array.from({ length: pages }, (_, i) => i);
}
if (currentPage < 5) {
- // 0 1 2 3 4 ... N
return [0, 1, 2, 3, 4, '⋯', pages - 1];
}
if (currentPage > pages - 5) {
- // 0 ... N-4 N-3 N-2 N-1 N
return [0, '⋯', pages - 5, pages - 4, pages - 3, pages - 2, pages - 1];
}
- // 0 ... x-1 x x-2 ... N
return [
0,
'⋯',
@@ -96,74 +234,88 @@ const Pagination = ({
onSetCurrent?.(page * itemsPerPage);
};
+ // Responsive direction: column-reverse default, column at sm, row at md
+ const flexDirection = isMd ? 'row' : isSm ? 'column' : 'column-reverse';
+
+ // Responsive left section margin
+ const leftMarginInlineStart = isSm ? 'auto' : 0;
+ const leftMarginInlineEnd = isMd ? 'auto' : undefined;
+
+ // Responsive right section
+ const rightFlexDirection = isSm ? 'row' : 'column';
+ const rightMarginInlineStart = isSm ? 'auto' : 0;
+
return (
-
+
+ {divider && }
{hasItemsPerPageSelection && (
-
-
+
+
{itemsPerPageLabel(renderingContext)}
-
-
+
+
{itemsPerPageOptions.map((itemsPerPageOption) => (
-
-
+
{itemsPerPageOption}
-
-
+
+
))}
-
-
+
+
)}
-
-
+
+
{showingResultsLabel(renderingContext)}
-
-
-
-
+
+
+
-
-
+
+
{displayedPages.map((page, i) => (
-
+
{page === '⋯' ? (
'⋯'
) : (
-
{(page as number) + 1}
-
+
)}
-
+
))}
-
-
+
-
-
-
-
-
+
+
+
+
+
);
};
diff --git a/packages/fuselage/src/components/ProgressBar/ProgressBar.styles.scss b/packages/fuselage/src/components/ProgressBar/ProgressBar.styles.scss
deleted file mode 100644
index 87268f42fc..0000000000
--- a/packages/fuselage/src/components/ProgressBar/ProgressBar.styles.scss
+++ /dev/null
@@ -1,70 +0,0 @@
-@use '../../styles/colors.scss';
-@use '../../styles/lengths.scss';
-@use '../../styles/typography.scss';
-
-$progress-bar-color-shine: theme(
- 'progress-bar-color-shine',
- colors.surface(light)
-);
-$progress-bar-color-background: theme(
- 'progress-bar-color-background',
- colors.surface(neutral)
-);
-
-$progress-bar-border-radius: theme(
- 'progress-bar-border-radius',
- lengths.border-radius(large)
-);
-
-.rcx-progress-bar {
- display: block;
-
- overflow: hidden;
-
- width: 100%;
-
- height: 8px;
-
- border-radius: $progress-bar-border-radius;
- background-color: $progress-bar-color-background;
-}
-
-.rcx-progress-bar__fill {
- display: block;
-
- height: 8px;
-
- border-radius: $progress-bar-border-radius;
-
- &--animated::before {
- position: absolute;
- inset: 0;
-
- width: inherit;
-
- content: '';
- animation: rcx-progress-bar__animation 2s ease-out infinite;
-
- opacity: 0;
- border-radius: $progress-bar-border-radius;
- background: $progress-bar-color-shine;
- }
-}
-
-@keyframes rcx-progress-bar__animation {
- 0% {
- width: 0;
-
- opacity: 0;
- }
-
- 50% {
- opacity: 0.5;
- }
-
- 100% {
- width: inherit;
-
- opacity: 0;
- }
-}
diff --git a/packages/fuselage/src/components/ProgressBar/ProgressBar.tsx b/packages/fuselage/src/components/ProgressBar/ProgressBar.tsx
index 49510c1e6d..429b0cfb4c 100644
--- a/packages/fuselage/src/components/ProgressBar/ProgressBar.tsx
+++ b/packages/fuselage/src/components/ProgressBar/ProgressBar.tsx
@@ -1,22 +1,77 @@
import type { AllHTMLAttributes } from 'react';
import { forwardRef } from 'react';
-import { Box } from '../Box';
+import { css, keyframes } from '@rocket.chat/css-in-js';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../primitives';
const getWidth = (percentage: number): string =>
`${Math.min(Math.max(0, percentage), 100).toFixed(1)}%`;
+const progressBarAnimation = keyframes`
+ 0% {
+ width: 0;
+ opacity: 0;
+ }
+ 50% {
+ opacity: 0.5;
+ }
+ 100% {
+ width: inherit;
+ opacity: 0;
+ }
+`;
+
+const animatedClass = css`
+ position: relative;
+
+ &::before {
+ position: absolute;
+ inset: 0;
+
+ width: inherit;
+
+ content: '';
+ animation: ${progressBarAnimation} 2s ease-out infinite;
+
+ opacity: 0;
+ border-radius: 0.5rem;
+ background: var(--surfaceLight);
+ }
+`;
+
+const ProgressBarTrack = styled(RcxView, {
+ name: 'ProgressBar',
+
+ display: 'block',
+ overflow: 'hidden',
+ width: '100%' as any,
+ height: 8,
+ borderRadius: '$x8',
+ backgroundColor: '$surfaceNeutral',
+});
+
+const ProgressBarFill = styled(RcxView, {
+ name: 'ProgressBarFill',
+
+ display: 'block',
+ height: 8,
+ borderRadius: '$x8',
+});
+
const colors = {
- info: 'status-font-on-info',
- success: 'status-font-on-success',
- warning: 'status-font-on-warning',
- danger: 'status-font-on-danger',
+ info: '$statusFontOnInfo',
+ success: '$statusFontOnSuccess',
+ warning: '$statusFontOnWarning',
+ danger: '$statusFontOnDanger',
};
+
const lightColors = {
- info: 'status-background-info',
- success: 'status-background-success',
- warning: 'status-background-warning',
- danger: 'status-background-danger',
+ info: '$statusBgInfo',
+ success: '$statusBgSuccess',
+ warning: '$statusBgWarning',
+ danger: '$statusBgDanger',
};
const getColor = (
@@ -45,21 +100,22 @@ const ProgressBar = forwardRef(function ProgressBar(
{ percentage, variant = 'info', error, animated, light = false, ...props },
ref,
) {
+ const fillColor = getColor(light, variant, error);
+
return (
-
-
-
+
);
});
diff --git a/packages/fuselage/src/components/RadioButton/RadioButton.styles.scss b/packages/fuselage/src/components/RadioButton/RadioButton.styles.scss
deleted file mode 100644
index 91075848e9..0000000000
--- a/packages/fuselage/src/components/RadioButton/RadioButton.styles.scss
+++ /dev/null
@@ -1,32 +0,0 @@
-@use '../../styles/lengths.scss';
-
-.rcx-radio-button {
- @include is-selection-button($checked: 'primary', $unchecked: 'empty');
-
- &__input {
- @extend %selection-button__input;
- }
-
- &__fake {
- @extend %selection-button__fake;
- display: flex;
- justify-content: center;
- align-items: center;
-
- border-radius: lengths.border-radius(full);
- inline-size: lengths.size(20);
- }
-
- &__input:checked + &__fake::before {
- display: block;
-
- width: 0.3 * lengths.size(20);
- height: 0.3 * lengths.size(20);
-
- content: '';
-
- border-radius: lengths.border-radius(full);
-
- background-color: currentColor;
- }
-}
diff --git a/packages/fuselage/src/components/RadioButton/RadioButton.tsx b/packages/fuselage/src/components/RadioButton/RadioButton.tsx
index 93ebedcb64..1f9247774b 100644
--- a/packages/fuselage/src/components/RadioButton/RadioButton.tsx
+++ b/packages/fuselage/src/components/RadioButton/RadioButton.tsx
@@ -1,27 +1,224 @@
import type { AllHTMLAttributes, ReactNode } from 'react';
-import { forwardRef } from 'react';
+import { forwardRef, useState } from 'react';
+import { styled } from '@tamagui/core';
-import { Box, type BoxProps } from '../Box';
+import { RcxView } from '../../primitives';
-export type RadioButtonProps = BoxProps &
- AllHTMLAttributes & {
- labelChildren?: ReactNode;
- };
+// --- Styled components ---
+
+const RadioButtonWrapper = styled(RcxView, {
+ name: 'RadioButton',
+ tag: 'label',
+
+ position: 'relative',
+ display: 'inline-flex',
+ flexDirection: 'row',
+ verticalAlign: 'middle',
+ cursor: 'pointer',
+
+ variants: {
+ isDisabled: {
+ true: {
+ cursor: 'not-allowed',
+ },
+ },
+ } as const,
+});
+
+const RadioButtonFake = styled(RcxView, {
+ name: 'RadioButtonFake',
+
+ position: 'relative',
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'center',
+ alignItems: 'center',
+
+ width: 20,
+ height: 20,
+
+ borderWidth: 1,
+ borderStyle: 'solid',
+ borderRadius: '$full',
+
+ // Default unchecked (empty) state
+ backgroundColor: '$surfaceLight',
+ borderColor: '$strokeDark',
+ color: '$fontWhite',
+
+ variants: {
+ state: {
+ unchecked: {
+ backgroundColor: '$surfaceLight',
+ borderColor: '$strokeDark',
+ color: '$fontWhite',
+ },
+ checked: {
+ backgroundColor: '$buttonPrimaryBg',
+ borderColor: '$buttonPrimaryBg',
+ color: '$buttonPrimaryColor',
+ },
+ },
+
+ isHovered: {
+ true: {},
+ },
+
+ isActive: {
+ true: {},
+ },
+
+ isFocused: {
+ true: {},
+ },
+
+ isDisabled: {
+ true: {},
+ },
+ } as const,
+
+ compoundVariants: [
+ // Unchecked + hovered
+ {
+ state: 'unchecked',
+ isHovered: true,
+ backgroundColor: '$surfaceLight',
+ borderColor: '$strokeExtraDark',
+ boxShadow: 'none',
+ },
+ // Unchecked + active
+ {
+ state: 'unchecked',
+ isActive: true,
+ backgroundColor: '$surfaceLight',
+ borderColor: '$strokeExtraDark',
+ boxShadow: 'none',
+ },
+ // Unchecked + focused
+ {
+ state: 'unchecked',
+ isFocused: true,
+ backgroundColor: '$surfaceLight',
+ borderColor: '$strokeExtraDark',
+ boxShadow: '0 0 0 2px var(--shadowHighlight)',
+ },
+ // Unchecked + disabled
+ {
+ state: 'unchecked',
+ isDisabled: true,
+ backgroundColor: '$buttonSecondaryDisabledBg',
+ borderColor: '$strokeLight',
+ color: '$fontWhite',
+ cursor: 'not-allowed',
+ },
+
+ // Checked + hovered
+ {
+ state: 'checked',
+ isHovered: true,
+ backgroundColor: '$buttonPrimaryHoverBg',
+ borderColor: '$buttonPrimaryHoverBg',
+ boxShadow: 'none',
+ },
+ // Checked + active
+ {
+ state: 'checked',
+ isActive: true,
+ backgroundColor: '$buttonPrimaryPressBg',
+ borderColor: '$buttonPrimaryPressBg',
+ boxShadow: 'none',
+ },
+ // Checked + focused
+ {
+ state: 'checked',
+ isFocused: true,
+ backgroundColor: '$buttonPrimaryFocusBg',
+ borderColor: '$strokeExtraDark',
+ boxShadow: '0 0 0 2px var(--shadowHighlight)',
+ },
+ // Checked + disabled
+ {
+ state: 'checked',
+ isDisabled: true,
+ backgroundColor: '$buttonPrimaryDisabledBg',
+ borderColor: '$buttonPrimaryDisabledBg',
+ color: '$buttonPrimaryDisabledColor',
+ cursor: 'not-allowed',
+ },
+ ],
+});
+
+// Radio dot: 0.3 * 20 = 6px circle
+const RadioDot = styled(RcxView, {
+ name: 'RadioDot',
+
+ width: 6,
+ height: 6,
+ borderRadius: '$full',
+ backgroundColor: 'currentColor',
+});
+
+// --- Types ---
+
+export type RadioButtonProps = {
+ labelChildren?: ReactNode;
+ className?: string;
+} & Omit, 'is'>;
+
+// --- Component ---
const RadioButton = forwardRef(
- function RadioButton({ className, labelChildren, ...props }, ref) {
+ function RadioButton({ className, labelChildren, checked, disabled, ...props }, ref) {
+ const [isFocused, setIsFocused] = useState(false);
+ const [isHovered, setIsHovered] = useState(false);
+ const [isActive, setIsActive] = useState(false);
+
+ const state = checked ? 'checked' : 'unchecked';
+
return (
-
+ setIsHovered(true)}
+ onMouseLeave={() => {
+ setIsHovered(false);
+ setIsActive(false);
+ }}
+ onMouseDown={() => setIsActive(true)}
+ onMouseUp={() => setIsActive(false)}
+ >
{labelChildren}
- setIsFocused(true)}
+ onBlur={() => setIsFocused(false)}
+ style={{
+ position: 'absolute',
+ width: 1,
+ height: 1,
+ padding: 0,
+ margin: -1,
+ overflow: 'hidden',
+ clip: 'rect(0, 0, 0, 0)',
+ whiteSpace: 'nowrap',
+ borderWidth: 0,
+ }}
{...props}
/>
-
-
+
+ {state === 'checked' && }
+
+
);
},
);
diff --git a/packages/fuselage/src/components/Select/Select.styles.scss b/packages/fuselage/src/components/Select/Select.styles.scss
deleted file mode 100644
index 97acaebcb0..0000000000
--- a/packages/fuselage/src/components/Select/Select.styles.scss
+++ /dev/null
@@ -1,88 +0,0 @@
-@use '../../styles/lengths.scss';
-@use '../../styles/typography.scss';
-
-.rcx-select {
- @extend %rcx-input-box;
- position: relative;
-
- align-items: center;
-
- min-height: lengths.size(40);
-
- // TODO move to .rcx-input-box
- &__item {
- @include typography.use-text-ellipsis;
- }
-
- &__focus,
- &__placeholder {
- display: inline-block;
-
- min-width: auto;
-
- user-select: none;
-
- text-align: start;
- vertical-align: middle;
- white-space: nowrap;
- text-decoration: none;
-
- background: inherit;
-
- appearance: none;
-
- @include clickable;
- @include typography.use-text-ellipsis;
- }
-
- &__addon {
- @extend .rcx-input-box__addon;
- @include clickable;
- padding: initial;
- }
-
- &__wrapper {
- align-items: center;
- flex-grow: 1;
- flex-shrink: 1;
-
- min-width: 0;
-
- user-select: none;
- white-space: nowrap;
-
- opacity: 1;
-
- & > .rcx-select__focus {
- flex-shrink: 1;
- }
-
- &--hidden {
- & > .rcx-select__focus {
- width: 0;
-
- transition: none;
-
- opacity: 0;
- }
- }
- }
-
- &:invalid,
- &.invalid {
- @include with-colors(
- $color: $input-colors-invalid-color,
- $placeholder-color: $input-colors-invalid-placeholder-color,
- $focus-caret-color: $input-colors-invalid-focus-caret-color,
- $active-caret-color: $input-colors-invalid-active-caret-color,
- $disabled-color: $input-colors-invalid-disabled-color
- );
-
- @include with-icon-addon-colors(
- $color: $input-colors-invalid-color,
- $focus-caret-color: $input-colors-invalid-focus-caret-color,
- $focus-icon-color: $input-colors-invalid-focus-icon-color,
- $disabled-color: $input-colors-invalid-disabled-color
- );
- }
-}
diff --git a/packages/fuselage/src/components/Select/SelectAddon.tsx b/packages/fuselage/src/components/Select/SelectAddon.tsx
index 460180092a..18f123d6e5 100644
--- a/packages/fuselage/src/components/Select/SelectAddon.tsx
+++ b/packages/fuselage/src/components/Select/SelectAddon.tsx
@@ -1,12 +1,33 @@
import { forwardRef } from 'react';
-import { Box, type BoxProps } from '../Box';
+import { styled } from '@tamagui/core';
-type AddonProps = BoxProps;
+import { RcxText } from '../../primitives';
-const SelectAddon = forwardRef(
+// .rcx-select__addon extends .rcx-input-box__addon + clickable
+const SelectAddonFrame = styled(RcxText, {
+ name: 'SelectAddon',
+
+ display: 'flex',
+ flexDirection: 'row',
+ flexWrap: 'nowrap',
+ alignItems: 'flex-start',
+ flexGrow: 0,
+ flexShrink: 0,
+
+ cursor: 'pointer',
+ outline: 0,
+ padding: 0,
+});
+
+type SelectAddonProps = {
+ children?: React.ReactNode;
+ [key: string]: any;
+};
+
+const SelectAddon = forwardRef(
function SelectAddon(props, ref) {
- return ;
+ return ;
},
);
diff --git a/packages/fuselage/src/components/Select/SelectAria.tsx b/packages/fuselage/src/components/Select/SelectAria.tsx
index 5cc7d9f2c6..0d7b32ed57 100644
--- a/packages/fuselage/src/components/Select/SelectAria.tsx
+++ b/packages/fuselage/src/components/Select/SelectAria.tsx
@@ -5,7 +5,9 @@ import { forwardRef } from 'react';
import { useSelect, HiddenSelect, mergeProps, useFocusRing } from 'react-aria';
import { useSelectState } from 'react-stately';
-import { Box } from '../Box';
+import { styled } from '@tamagui/core';
+
+import { RcxText } from '../../primitives';
import { Icon } from '../Icon';
import { OptionContainer } from '../Options';
import { Popover } from '../Popover';
@@ -15,6 +17,33 @@ import { SelectTrigger } from './SelectTrigger';
export { Item } from 'react-stately';
+// Value text display — replaces
+const SelectValueText = styled(RcxText, {
+ name: 'SelectValue',
+
+ fontFamily: '$body',
+ fontSize: '$p2',
+ fontWeight: '$p2',
+ lineHeight: '$p2',
+ letterSpacing: '$p2',
+
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+
+ variants: {
+ small: {
+ true: {
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ },
+ },
+ } as const,
+});
+
export type SelectAriaProps = AriaSelectProps & {
error?: string;
placeholder?: string;
@@ -75,14 +104,13 @@ export const SelectAria = forwardRef>(
name={props.name}
isDisabled={isDisabled}
/>
-
{state.selectedItem ? state.selectedItem.rendered : placeholder}
-
+
);
});
diff --git a/packages/fuselage/src/components/Select/SelectFocus.tsx b/packages/fuselage/src/components/Select/SelectFocus.tsx
index bcafdc862e..624a2360cb 100644
--- a/packages/fuselage/src/components/Select/SelectFocus.tsx
+++ b/packages/fuselage/src/components/Select/SelectFocus.tsx
@@ -1,20 +1,55 @@
import { forwardRef } from 'react';
-import { Box, type BoxProps } from '../Box';
+import { styled } from '@tamagui/core';
+
+import { RcxInteractiveText } from '../../primitives';
+import type { BoxProps } from '../Box';
+
+// .rcx-select__focus — inline-block button that shows selected value or placeholder
+const SelectFocusFrame = styled(RcxInteractiveText, {
+ name: 'SelectFocus',
+ role: 'button',
+
+ display: 'inline-block',
+ minWidth: 'auto',
+
+ userSelect: 'none',
+ textAlign: 'start',
+ verticalAlign: 'middle',
+ whiteSpace: 'nowrap',
+ textDecoration: 'none',
+
+ backgroundColor: 'inherit',
+ // @ts-expect-error appearance is valid CSS
+ appearance: 'none',
+
+ fontFamily: '$body',
+ fontSize: '$p2m',
+ fontWeight: '$p2m',
+ lineHeight: '$p2m',
+ letterSpacing: '$p2m',
+
+ color: '$fontHint',
+
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ overflowWrap: 'normal',
+
+ borderWidth: 0,
+ borderStyle: 'solid',
+ borderColor: 'transparent',
+});
type SelectFocusProps = BoxProps;
const SelectFocus = forwardRef(
function SelectFocus(props, ref) {
return (
-
);
},
diff --git a/packages/fuselage/src/components/Select/SelectLegacy.tsx b/packages/fuselage/src/components/Select/SelectLegacy.tsx
index ee72c52d02..348c8ed3b3 100644
--- a/packages/fuselage/src/components/Select/SelectLegacy.tsx
+++ b/packages/fuselage/src/components/Select/SelectLegacy.tsx
@@ -7,11 +7,13 @@ import {
import type { DependencyList, ElementType, ReactNode } from 'react';
import { useState, useRef, useEffect, forwardRef, useMemo } from 'react';
+import { styled } from '@tamagui/core';
+
import { isForwardRefType } from '../../helpers/isForwardRefType';
+import { RcxView, RcxText } from '../../primitives';
import { AnimatedVisibility } from '../AnimatedVisibility';
-import { Box, type BoxProps } from '../Box';
+import type { BoxProps } from '../Box';
import { Icon, type IconProps } from '../Icon';
-import { Margins } from '../Margins';
import type { OptionType } from '../Options';
import { Options, useCursor } from '../Options';
import { PositionAnimated } from '../PositionAnimated';
@@ -26,10 +28,114 @@ export type SelectOption = readonly [
selected?: boolean,
];
-type WrapperProps = BoxProps;
+// .rcx-select — extends %rcx-input-box which extends %input
+// This is the outer container that provides border/bg/focus states
+const SelectFrame = styled(RcxView, {
+ name: 'SelectLegacy',
+
+ position: 'relative',
+ display: 'inline-flex',
+ flexDirection: 'row',
+ flexWrap: 'nowrap',
+ alignItems: 'center',
+ flexGrow: 1,
+
+ minWidth: 144,
+ minHeight: 40,
+
+ paddingBlock: 8,
+ paddingInline: 15, // 16 - 1px border
+
+ // %input styles
+ borderWidth: 1,
+ borderStyle: 'solid',
+ borderColor: '$strokeLight',
+ borderRadius: '$x4',
+ backgroundColor: '$surfaceLight',
+ boxShadow: 'none',
+
+ cursor: 'pointer',
+
+ hoverStyle: {
+ borderColor: '$strokeLight',
+ },
+
+ variants: {
+ invalid: {
+ true: {
+ borderColor: '$strokeError',
+ hoverStyle: {
+ borderColor: '$strokeError',
+ },
+ },
+ },
+ focused: {
+ true: {
+ borderColor: '$strokeHighlight',
+ boxShadow: '0 0 0 2px var(--shadowHighlight)',
+ },
+ },
+ } as const,
+
+ disabledStyle: {
+ borderColor: '$strokeLight',
+ backgroundColor: '$surfaceDisabled',
+ cursor: 'not-allowed',
+ pointerEvents: 'none',
+ },
+});
+
+// .rcx-select__wrapper — inner flex row container
+const SelectWrapperFrame = styled(RcxView, {
+ name: 'SelectWrapper',
+
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ flexGrow: 1,
+ flexShrink: 1,
+
+ minWidth: 0,
+
+ userSelect: 'none',
+ whiteSpace: 'nowrap',
+ opacity: 1,
+
+ variants: {
+ hidden: {
+ true: {},
+ },
+ } as const,
+});
+
+// .rcx-select__item — text display of selected value
+const SelectItemText = styled(RcxText, {
+ name: 'SelectItem',
+
+ flexGrow: 1,
+ marginInline: 4,
+
+ fontFamily: '$body',
+ fontSize: '$p2',
+ fontWeight: '$p2',
+ lineHeight: '$p2',
+ letterSpacing: '$p2',
+
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+});
+
+type WrapperProps = {
+ hidden?: boolean;
+ children?: ReactNode;
+ display?: string;
+ [key: string]: any;
+};
const Wrapper = forwardRef((props, ref) => (
-
+
));
const useDidUpdate = (func: () => void, deps: DependencyList | undefined) => {
@@ -161,21 +267,16 @@ const SelectLegacy = forwardRef(
});
return (
- [error && 'invalid', disabled && 'disabled'],
- [error, disabled],
- )}
- {...props}
+ onPress={handleClick}
+ invalid={!!error || undefined}
+ {...(props as any)}
>
{visibleText &&
(RenderSelected ? (
@@ -187,16 +288,11 @@ const SelectLegacy = forwardRef(
onClick={internalChangedByClick}
/>
) : (
-
{visibleText}
-
+
))}
{renderAnchor({
ref: anchorRef,
@@ -207,20 +303,19 @@ const SelectLegacy = forwardRef(
onKeyDown: handleKeyDown,
onKeyUp: handleKeyUp,
})}
-
-
- }
- />
-
+
+ }
+ />
<_Options
@@ -234,7 +329,7 @@ const SelectLegacy = forwardRef(
customEmpty={customEmpty}
/>
-
+
);
},
);
diff --git a/packages/fuselage/src/components/Select/SelectTrigger.tsx b/packages/fuselage/src/components/Select/SelectTrigger.tsx
index a85797e021..3647e302e5 100644
--- a/packages/fuselage/src/components/Select/SelectTrigger.tsx
+++ b/packages/fuselage/src/components/Select/SelectTrigger.tsx
@@ -2,7 +2,81 @@ import type { AllHTMLAttributes, RefObject } from 'react';
import { forwardRef } from 'react';
import { useButton, type AriaButtonProps } from 'react-aria';
-import { Box } from '../Box';
+import { styled } from '@tamagui/core';
+
+import { RcxInteractive } from '../../primitives';
+
+// .rcx-select extends %rcx-input-box which extends %input
+// Layout: inline-flex row, centered, with border/padding from input primitive
+const SelectTriggerFrame = styled(RcxInteractive, {
+ name: 'SelectTrigger',
+ role: 'button',
+
+ display: 'inline-flex',
+ flexDirection: 'row',
+ flexWrap: 'nowrap',
+ alignItems: 'center',
+ flexGrow: 1,
+ justifyContent: 'space-between',
+
+ minWidth: 144,
+ minHeight: 40,
+
+ paddingBlock: 8,
+ paddingInline: 15, // 16 - 1px border
+
+ // %input styles
+ borderWidth: 1,
+ borderStyle: 'solid',
+ borderColor: '$strokeLight',
+ borderRadius: '$x4',
+ backgroundColor: '$surfaceLight',
+ boxShadow: 'none',
+
+ hoverStyle: {
+ borderColor: '$strokeLight',
+ },
+ focusVisibleStyle: {
+ borderColor: '$strokeHighlight',
+ boxShadow: '0 0 0 2px var(--shadowHighlight)',
+ },
+ disabledStyle: {
+ borderColor: '$strokeLight',
+ backgroundColor: '$surfaceDisabled',
+ cursor: 'not-allowed',
+ pointerEvents: 'none',
+ },
+
+ variants: {
+ invalid: {
+ true: {
+ borderColor: '$strokeError',
+ hoverStyle: {
+ borderColor: '$strokeError',
+ },
+ focusVisibleStyle: {
+ borderColor: '$strokeError',
+ boxShadow: '0 0 0 2px var(--shadowDanger)',
+ },
+ },
+ },
+ focused: {
+ true: {
+ borderColor: '$strokeHighlight',
+ boxShadow: '0 0 0 2px var(--shadowHighlight)',
+ },
+ },
+ small: {
+ true: {
+ alignItems: 'center',
+ minWidth: 112,
+ maxHeight: 28,
+ paddingBlock: 4,
+ paddingInline: 7, // 8 - 1px border
+ },
+ },
+ } as const,
+});
type SelectTriggerProps = {
small?: boolean;
@@ -19,27 +93,17 @@ export const SelectTrigger = forwardRef(
);
return (
-
{props.children}
-
+
);
},
);
diff --git a/packages/fuselage/src/components/Sidebar/Item.tsx b/packages/fuselage/src/components/Sidebar/Item.tsx
index 90179d20d0..d4ef05ade5 100644
--- a/packages/fuselage/src/components/Sidebar/Item.tsx
+++ b/packages/fuselage/src/components/Sidebar/Item.tsx
@@ -1,5 +1,7 @@
import type { AllHTMLAttributes, ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+import { RcxInteractive, RcxText, RcxView } from '../../primitives';
import type { BoxProps } from '../Box';
import type { IconProps } from '../Icon';
import { Icon as FuselageIcon } from '../Icon';
@@ -7,118 +9,218 @@ import { Icon as FuselageIcon } from '../Icon';
import type { SidebarActionProps } from './SidebarActions';
import { SidebarAction, SidebarActions } from './SidebarActions';
+// sidebar-base: flex, align-items:center, border-radius:small, font-scale p2
+const SidebarBase = styled(RcxView, {
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ borderRadius: '$x2',
+});
+
+// SidebarItemContainer
export type SidebarItemContainerProps = {
children?: ReactNode;
} & AllHTMLAttributes;
+const SidebarItemContainerFrame = styled(SidebarBase, {
+ name: 'SidebarItemContainer',
+ flexGrow: 0,
+ flexShrink: 0,
+ marginInline: 2,
+});
+
export const SidebarItemContainer = (props: SidebarItemContainerProps) => (
-
+
);
+// SidebarItemMenu
export type SidebarItemMenuProps = {
children?: ReactNode;
} & AllHTMLAttributes;
+const SidebarItemMenuFrame = styled(RcxView, {
+ name: 'SidebarItemMenu',
+ position: 'relative',
+ flexShrink: 0,
+ width: 0,
+ height: '100%',
+ opacity: 0,
+ // On parent hover, these are overridden via group
+ '$group-sidebarItem-hover': {
+ position: 'static' as any,
+ width: 20,
+ marginInline: 4,
+ opacity: 1,
+ },
+});
+
export const SidebarItemMenu = (props: SidebarItemMenuProps) => (
-
+
);
+// SidebarItemContent
export type SidebarItemContentProps = {
children?: ReactNode;
className?: string;
} & AllHTMLAttributes;
+const SidebarItemContentFrame = styled(RcxView, {
+ name: 'SidebarItemContent',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ flexWrap: 'wrap',
+ flexGrow: 1,
+ flexShrink: 1,
+ flexBasis: '100%',
+});
+
export const SidebarItemContent = ({
- className = '',
+ className: _className,
...props
}: SidebarItemContentProps) => (
-
+
);
+// SidebarItemTitle
export type SidebarItemTitleProps = {
children?: ReactNode;
className?: string;
} & AllHTMLAttributes;
+const SidebarItemTitleFrame = styled(RcxText, {
+ name: 'SidebarItemTitle',
+ fontFamily: '$body',
+ fontSize: '$p2',
+ fontWeight: '$p2',
+ lineHeight: '$p2',
+ letterSpacing: '$p2',
+ display: 'block',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+ flexGrow: 1,
+ flexShrink: 1,
+ flexBasis: '1%',
+ marginInline: 2,
+});
+
export const SidebarItemTitle = ({
- className = '',
+ className: _className,
...props
}: SidebarItemTitleProps) => (
-
+
);
+// SidebarItemTime
export type SidebarItemTimeProps = {
children?: ReactNode;
className?: string;
} & AllHTMLAttributes;
+const SidebarItemTimeFrame = styled(RcxText, {
+ name: 'SidebarItemTime',
+ fontFamily: '$body',
+ fontSize: '$micro',
+ fontWeight: '$micro',
+ lineHeight: '$micro',
+ letterSpacing: '$micro',
+ overflowWrap: 'normal',
+ marginInline: 4,
+});
+
export const SidebarItemTime = ({
- className,
+ className: _className,
...props
}: SidebarItemTimeProps) => (
-
+
);
+// SidebarItemBadge
export type SidebarItemBadgeProps = {
children?: ReactNode;
className?: string;
} & AllHTMLAttributes;
+const SidebarItemBadgeFrame = styled(RcxView, {
+ name: 'SidebarItemBadge',
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginInline: 4,
+});
+
export const SidebarItemBadge = ({
- className,
+ className: _className,
...props
}: SidebarItemBadgeProps) => (
-
+
);
+// SidebarItemSubtitle
export type SidebarItemSubtitleProps = {
children?: ReactNode;
className?: string;
} & AllHTMLAttributes;
+const SidebarItemSubtitleFrame = styled(RcxText, {
+ name: 'SidebarItemSubtitle',
+ fontFamily: '$body',
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ display: 'block',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+ flexGrow: 1,
+ flexShrink: 1,
+ flexBasis: '1%',
+ marginInline: 2,
+});
+
export const SidebarItemSubtitle = ({
- className,
+ className: _className,
...props
}: SidebarItemSubtitleProps) => (
-
+
);
+// SidebarItemWrapper
export type SidebarItemWrapperProps = {
children?: ReactNode;
className?: string;
} & AllHTMLAttributes;
+const SidebarItemWrapperFrame = styled(RcxView, {
+ name: 'SidebarItemWrapper',
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ borderRadius: '$x2',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ flexGrow: 1,
+ flexShrink: 0,
+ marginInline: -2,
+});
+
export const SidebarItemWrapper = ({
- className = '',
+ className: _className,
...props
}: SidebarItemWrapperProps) => (
-
+
);
+// SidebarItemIcon
export type SidebarItemIconProps = {
children?: ReactNode;
className?: string;
@@ -126,6 +228,26 @@ export type SidebarItemIconProps = {
icon: IconProps['name'];
} & Omit, 'name' | 'is'>;
+const SidebarItemIconFrame = styled(RcxView, {
+ name: 'SidebarItemIcon',
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'center',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ width: 16,
+ marginInline: 2,
+ variants: {
+ highlighted: {
+ true: {
+ color: '$fontTitlesLabels',
+ },
+ },
+ } as const,
+});
+
export const SidebarItemIcon = ({
highlighted,
children,
@@ -133,26 +255,31 @@ export const SidebarItemIcon = ({
className: _className,
...props
}: SidebarItemIconProps) => (
-
- {children || }
-
+
+ {children || }
+
);
+// SidebarItemAvatar
export type SidebarItemAvatarProps = {
children?: ReactNode;
} & AllHTMLAttributes;
+const SidebarItemAvatarInner = styled(RcxView, {
+ name: 'SidebarItemAvatarInner',
+ display: 'flex',
+ flexDirection: 'row',
+ flexGrow: 0,
+ flexShrink: 0,
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+});
+
export const SidebarItemAvatar = ({ ...props }: SidebarItemAvatarProps) => (
-
+
);
@@ -164,6 +291,7 @@ export const SidebarItemAction = (props: SidebarItemActionProps) => (
);
+// SidebarItem - the main item component
export type SidebarItemProps = {
selected?: boolean;
highlighted?: boolean;
@@ -173,6 +301,56 @@ export type SidebarItemProps = {
children?: ReactNode;
} & AllHTMLAttributes;
+// Base frame for sidebar item
+const SidebarItemFrame = styled(RcxView, {
+ name: 'SidebarItem',
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ borderRadius: '$x2',
+ paddingBlock: 4,
+ paddingInline: 16,
+ // @ts-ignore
+ textDecoration: 'none',
+ color: '$fontDefault',
+ variants: {
+ highlighted: {
+ true: {
+ color: '$fontTitlesLabels',
+ fontWeight: '600',
+ },
+ },
+ selected: {
+ true: {
+ backgroundColor: '$surfaceSelected',
+ },
+ },
+ featured: {
+ true: {
+ color: '$fontPureWhite',
+ backgroundColor: '$surfaceFeatured',
+ },
+ },
+ } as const,
+});
+
+const SidebarItemClickable = styled(SidebarItemFrame, {
+ name: 'SidebarItemClickable',
+ cursor: 'pointer',
+ focusable: true,
+ hoverStyle: {
+ backgroundColor: '$surfaceHover',
+ },
+ pressStyle: {
+ backgroundColor: '$surfaceSelected',
+ },
+ focusVisibleStyle: {
+ // @ts-ignore
+ outlineOffset: -1,
+ boxShadow: 'none',
+ },
+});
+
/**
* Item component to be used inside Sidebar.
*/
@@ -182,28 +360,53 @@ export const SidebarItem = Object.assign(
highlighted,
clickable,
featured,
- is: Tag = 'div',
+ is: Tag,
children,
...props
- }: SidebarItemProps) => (
-
-
-
- ),
+ }: SidebarItemProps) => {
+ const Frame = (clickable || Tag === 'a') ? SidebarItemClickable : SidebarItemFrame;
+
+ // If Tag is 'a', use native for links per agent rules
+ if (Tag === 'a') {
+ return (
+
+ {children}
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+ },
{
Menu: SidebarItemMenu,
Container: SidebarItemContainer,
diff --git a/packages/fuselage/src/components/Sidebar/Section.tsx b/packages/fuselage/src/components/Sidebar/Section.tsx
index f9a51b4a10..600f9daad0 100644
--- a/packages/fuselage/src/components/Sidebar/Section.tsx
+++ b/packages/fuselage/src/components/Sidebar/Section.tsx
@@ -1,21 +1,47 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText, RcxView } from '../../primitives';
+
+const SidebarSectionTitleFrame = styled(RcxText, {
+ name: 'SidebarSectionTitle',
+ fontFamily: '$body',
+ fontSize: '$c2',
+ fontWeight: '$c2',
+ lineHeight: '$c2',
+ letterSpacing: '$c2',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+ color: '$fontDefault',
+});
export type SidebarSectionTitleProps = {
children?: ReactNode;
};
export const SidebarSectionTitle = (props: SidebarSectionTitleProps) => (
-
+
);
+const SidebarSectionFrame = styled(RcxView, {
+ name: 'SidebarSection',
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginBlock: 8,
+ paddingInline: 16,
+});
+
export type SidebarSectionProps = {
children?: ReactNode;
};
export const SidebarSection = Object.assign(
- (props: SidebarSectionProps) => (
-
- ),
+ (props: SidebarSectionProps) => ,
{
Title: SidebarSectionTitle,
},
diff --git a/packages/fuselage/src/components/Sidebar/Sidebar.styles.scss b/packages/fuselage/src/components/Sidebar/Sidebar.styles.scss
deleted file mode 100644
index f8dd5bf3ad..0000000000
--- a/packages/fuselage/src/components/Sidebar/Sidebar.styles.scss
+++ /dev/null
@@ -1,432 +0,0 @@
-@use '../../styles/colors.scss';
-@use '../../styles/lengths.scss';
-@use '../../styles/typography.scss';
-@import '../../styles/mixins/all.scss';
-
-$sidebar-color-surface-default: theme(
- 'sidebar-color-surface-default',
- colors.surface(sidebar)
-);
-
-$sidebar-color-surface-hover: theme(
- 'sidebar-color-surface-hover',
- colors.surface(hover)
-);
-
-$sidebar-color-surface-selected: theme(
- 'sidebar-color-surface-selected',
- colors.surface(selected)
-);
-
-$sidebar-color-font-default: theme(
- 'sidebar-color-font-default',
- colors.font(default)
-);
-
-$sidebar-color-font-title: theme(
- 'sidebar-color-font-title',
- colors.font(titles-labels)
-);
-
-$sidebar-color-stroke-default: theme(
- 'sidebar-color-stroke-default',
- colors.stroke(light)
-);
-$sidebar-item-color: theme('sidebar-item-color', colors.font(secondary-info));
-$sidebar-item-color-highlighted: theme(
- 'sidebar-item-color-highlighted',
- colors.font(titles-labels)
-);
-$sidebar-item-background-color-hover: theme(
- 'sidebar-item-background-color-hover',
- transparent
-);
-$sidebar-item-color-hover: theme(
- 'sidebar-item-color-hover',
- colors.font(hover)
-);
-$sidebar-item-color-focus: theme(
- 'sidebar-item-color-focus',
- colors.surface(dark)
-);
-$sidebar-item-background-color-featured: theme(
- 'sidebar-item-background-color-featured',
- colors.surface(featured)
-);
-$sidebar-item-color-featured: theme(
- 'sidebar-item-color-featured',
- colors.font(pure-white)
-);
-$sidebar-item-background-color-featured-hover: theme(
- 'sidebar-item-background-color-featured-hover',
- colors.surface(featured-hover)
-);
-$sidebar-item-background-color-featured-selected: theme(
- 'sidebar-item-background-color-featured-selected',
- colors.surface(featured-hover)
-);
-
-$sidebar-header-height: theme(
- 'sidebar-header-height',
- theme('header-height', lengths.size(64))
-);
-
-$sidebar-header-v2-height: theme(
- 'sidebar-header-v2-height',
- theme('header-height-v2', lengths.size(44))
-);
-
-$sidebar-section-height: theme(
- 'sidebar-section-height',
- theme('section-height', lengths.size(56))
-);
-
-$sidebar-footer-box-shadow: theme(
- 'sidebar-footer-box-shadow',
- rgba(0, 0, 0, 0.1)
-);
-
-$sidebar-footer-highlight-color: theme(
- 'sidebar-footer-highlight-color',
- colors.font(annotation)
-);
-
-// sidebar-banner-colors
-$sidebar-banner-background-default: theme(
- 'sidebar-banner-background-default',
- colors.surface(hover)
-);
-$sidebar-banner-color-default: theme(
- 'sidebar-banner-color-default',
- colors.font(titles-labels)
-);
-
-$sidebar-banner-background-info: theme(
- 'sidebar-banner-background-info',
- colors.status-background(info)
-);
-$sidebar-banner-color-info: theme(
- 'sidebar-banner-background-info',
- colors.status-font(on-info)
-);
-
-$sidebar-banner-background-success: theme(
- 'sidebar-banner-background-success',
- colors.status-background(success)
-);
-$sidebar-banner-color-success: theme(
- 'sidebar-banner-background-success',
- colors.status-font(on-success)
-);
-
-$sidebar-banner-background-warning: theme(
- 'sidebar-banner-background-warning',
- colors.status-background(warning)
-);
-$sidebar-banner-color-warning: theme(
- 'sidebar-banner-background-warning',
- colors.status-font(on-warning)
-);
-
-$sidebar-banner-background-danger: theme(
- 'sidebar-banner-background-danger',
- colors.status-background(danger)
-);
-$sidebar-banner-color-danger: theme(
- 'sidebar-banner-background-danger',
- colors.status-font(on-danger)
-);
-
-%sidebar-base {
- display: flex;
-
- align-items: center;
-
- border-radius: lengths.border-radius(small);
- @include typography.use-font-scale(p2);
-}
-
-%highlighted {
- color: $sidebar-item-color-highlighted;
-
- font-weight: 600;
-}
-
-@mixin highlighted {
- &--highlighted {
- @extend %highlighted;
- }
-}
-
-.rcx-sidebar {
- color: $sidebar-color-font-default;
- background: $sidebar-color-surface-default;
-
- &--divider {
- border-color: theme(
- 'sidebar-color-stroke-extra-light',
- colors.stroke(light)
- );
- }
-
- &-topbar {
- display: flex;
- flex-direction: column;
-
- flex-shrink: 0;
-
- height: $sidebar-header-height;
-
- color: $sidebar-item-color;
-
- &--toolbox {
- height: $sidebar-section-height;
- }
-
- &__wrapper {
- display: flex;
- justify-content: space-between;
- align-items: center;
-
- flex-grow: 1;
-
- padding-inline: lengths.padding(16);
- }
-
- &__title {
- @include typography.use-font-scale(p2m);
- color: $sidebar-color-font-title;
- }
- }
-
- &-topbar-v2 {
- height: $sidebar-header-v2-height;
- }
-
- &-item {
- @extend %sidebar-base;
- @include highlighted;
-
- padding-block: lengths.padding(4);
- padding-inline: lengths.padding(16);
-
- text-decoration: none;
-
- color: $sidebar-color-font-default;
-
- &__wrapper {
- @extend %sidebar-base;
- @include typography.use-with-truncated-text();
- flex: 1 0;
-
- margin-inline: lengths.margin(-2);
- }
-
- &--clickable {
- @include clickable;
- @include use-link-colors($color: colors.font(default));
-
- @include on-hover {
- background-color: $sidebar-color-surface-hover;
- }
-
- @include on-focus {
- outline-offset: -1px;
- box-shadow: none;
- }
- }
-
- &:active,
- &--selected {
- background-color: $sidebar-color-surface-selected;
- }
-
- &--featured {
- color: $sidebar-item-color-featured;
- background-color: $sidebar-item-background-color-featured;
-
- &:hover,
- :active {
- background-color: $sidebar-item-background-color-featured-hover;
- }
- }
-
- &__avatar {
- display: flex;
- flex: 0 0 auto;
-
- @include typography.use-with-truncated-text();
- }
-
- &__container {
- @extend %sidebar-base;
- flex: 0 0 auto;
-
- margin-inline: lengths.margin(2);
- }
-
- &__icon {
- @include typography.use-with-truncated-text();
- @include highlighted;
-
- display: flex;
- justify-content: center;
-
- width: lengths.size(16);
- margin-inline: lengths.margin(2);
- }
-
- &__content {
- @include typography.use-with-truncated-text();
- flex-wrap: wrap;
- flex: 1 1 100%;
- }
-
- &__title,
- &__subtitle {
- @extend %sidebar-base;
- @include typography.use-with-truncated-text();
- display: block;
- flex: 1 1 1%;
-
- margin-inline: lengths.margin(2);
- }
-
- &__subtitle {
- @include typography.use-font-scale(c1);
- }
-
- &__time {
- @include typography.use-font-scale(micro);
- margin-inline: lengths.margin(4);
- }
-
- &__badge {
- display: flex;
- align-items: center;
-
- margin-inline: lengths.margin(4);
- }
-
- &:hover &__menu-wrapper,
- &:focus-within &__menu-wrapper {
- position: static;
-
- width: lengths.size(20);
-
- margin-inline: lengths.margin(4);
-
- opacity: 1;
- }
-
- &__menu {
- position: absolute;
-
- transform: translateY(-50%);
- }
-
- &__menu-wrapper {
- position: relative;
-
- flex-shrink: 0;
-
- width: 0;
-
- height: 100%;
-
- opacity: 0;
- }
- }
-
- &-title {
- @include typography.use-font-scale(c2);
- @include typography.use-with-truncated-text();
-
- color: $sidebar-color-font-default;
- }
-
- &-section {
- display: flex;
-
- justify-content: space-between;
- align-items: center;
-
- margin-block: lengths.margin(8);
- padding-inline: lengths.padding(16);
- }
-
- &-banner {
- display: flex;
- justify-content: space-between;
- align-items: center;
-
- height: 100px;
- padding: lengths.padding(16);
-
- color: $sidebar-banner-color-default;
- background-color: $sidebar-banner-background-default;
- column-gap: 0.25rem;
-
- &__actions {
- display: flex;
- align-items: center;
- }
-
- &--text {
- @include typography.use-font-scale(h5);
- }
-
- &--description {
- @include typography.use-font-scale(p2m);
- display: inline-block;
-
- color: currentColor;
-
- &--clickable {
- cursor: pointer;
- border-block-end: lengths.border-width(default) solid;
-
- @include use-link-colors();
- }
- }
-
- &--info {
- color: $sidebar-banner-color-info;
- background-color: $sidebar-banner-background-info;
- }
-
- &--success {
- color: $sidebar-banner-color-success;
- background-color: $sidebar-banner-background-success;
- }
-
- &--warning {
- color: $sidebar-banner-color-warning;
- background-color: $sidebar-banner-background-warning;
- }
-
- &--danger {
- color: $sidebar-banner-color-danger;
- background-color: $sidebar-banner-background-danger;
- }
- }
-
- &-footer {
- padding-block: lengths.padding(4);
-
- &--elevated {
- box-shadow: 0 -4px 12px $sidebar-footer-box-shadow;
- }
-
- &__highlights {
- @include typography.use-font-scale(c1);
-
- display: flex;
- justify-content: center;
-
- padding-block-start: lengths.padding(4);
-
- color: $sidebar-footer-highlight-color;
- }
- }
-}
diff --git a/packages/fuselage/src/components/Sidebar/Sidebar.tsx b/packages/fuselage/src/components/Sidebar/Sidebar.tsx
index e2c56179a7..24a5db2814 100644
--- a/packages/fuselage/src/components/Sidebar/Sidebar.tsx
+++ b/packages/fuselage/src/components/Sidebar/Sidebar.tsx
@@ -1,4 +1,7 @@
-import { Box, type BoxProps } from '../Box';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../primitives';
+import type { BoxProps } from '../Box';
import { SidebarItem } from './Item';
import { SidebarSection } from './Section';
@@ -8,8 +11,14 @@ import SidebarTopBar from './TopBar';
export type SidebarProps = BoxProps;
+const SidebarFrame = styled(RcxView, {
+ name: 'Sidebar',
+ color: '$fontDefault',
+ backgroundColor: '$surfaceSidebar',
+});
+
const Sidebar = Object.assign(
- (props: SidebarProps) => ,
+ (props: SidebarProps) => ,
{
TopBar: SidebarTopBar,
Item: SidebarItem,
diff --git a/packages/fuselage/src/components/Sidebar/SidebarBanner.tsx b/packages/fuselage/src/components/Sidebar/SidebarBanner.tsx
index 3c0b03110b..1efcafb036 100644
--- a/packages/fuselage/src/components/Sidebar/SidebarBanner.tsx
+++ b/packages/fuselage/src/components/Sidebar/SidebarBanner.tsx
@@ -1,5 +1,7 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+import { RcxText, RcxView } from '../../primitives';
import { IconButton } from '../Button';
type VariantType = 'default' | 'info' | 'success' | 'warning' | 'danger';
@@ -14,6 +16,73 @@ export type SidebarBannerProps = {
addon?: ReactNode;
};
+const SidebarBannerFrame = styled(RcxView, {
+ name: 'SidebarBanner',
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ height: 100,
+ padding: 16,
+ color: '$fontTitlesLabels',
+ backgroundColor: '$surfaceHover',
+ // @ts-ignore
+ columnGap: 4,
+ variants: {
+ variant: {
+ default: {},
+ info: {
+ color: '$statusFontOnInfo',
+ backgroundColor: '$statusBgInfo',
+ },
+ success: {
+ color: '$statusFontOnSuccess',
+ backgroundColor: '$statusBgSuccess',
+ },
+ warning: {
+ color: '$statusFontOnWarning',
+ backgroundColor: '$statusBgWarning',
+ },
+ danger: {
+ color: '$statusFontOnDanger',
+ backgroundColor: '$statusBgDanger',
+ },
+ },
+ } as const,
+ defaultVariants: {
+ variant: 'default',
+ },
+});
+
+const BannerTextFrame = styled(RcxText, {
+ name: 'SidebarBannerText',
+ fontFamily: '$body',
+ fontSize: '$h5',
+ fontWeight: '$h5',
+ lineHeight: '$h5',
+ letterSpacing: '$h5',
+ overflowWrap: 'normal',
+});
+
+const BannerDescriptionFrame = styled(RcxText, {
+ name: 'SidebarBannerDescription',
+ fontFamily: '$body',
+ fontSize: '$p2m',
+ fontWeight: '$p2m',
+ lineHeight: '$p2m',
+ letterSpacing: '$p2m',
+ display: 'inline-block',
+ color: 'inherit',
+ overflowWrap: 'normal',
+});
+
+const BannerActionsFrame = styled(RcxView, {
+ name: 'SidebarBannerActions',
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+});
+
const SidebarBanner = ({
text,
description,
@@ -23,34 +92,28 @@ const SidebarBanner = ({
onClose,
children,
}: SidebarBannerProps) => (
-
-
- {text &&
{text}
}
+
+
+ {text && {text}}
{description && (
- e.key === 'Enter' && onClick?.()}
+ focusable={!!onClick}
+ onPress={onClick as any}
+ cursor={onClick ? 'pointer' : undefined}
+ // @ts-ignore
+ borderBlockEnd={onClick ? '1px solid' : undefined}
>
{description}
-
+
)}
{children}
-
-
+
+
{addon}
{onClose && }
-
-
+
+
);
export default SidebarBanner;
diff --git a/packages/fuselage/src/components/Sidebar/SidebarFooter.tsx b/packages/fuselage/src/components/Sidebar/SidebarFooter.tsx
index 8c5c255bbb..fa80990b4e 100644
--- a/packages/fuselage/src/components/Sidebar/SidebarFooter.tsx
+++ b/packages/fuselage/src/components/Sidebar/SidebarFooter.tsx
@@ -1,31 +1,49 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxText, RcxView } from '../../primitives';
export type SidebarFooterProps = {
children?: ReactNode;
elevated?: boolean;
};
+const SidebarFooterFrame = styled(RcxView, {
+ name: 'SidebarFooter',
+ paddingBlock: 4,
+ variants: {
+ elevated: {
+ true: {
+ boxShadow: '0 -4px 12px rgba(0, 0, 0, 0.1)',
+ },
+ },
+ } as const,
+});
+
export const SidebarFooter = ({ elevated, ...props }: SidebarFooterProps) => (
-
+
);
export type SidebarFooterHighlightProps = {
children?: ReactNode;
};
+const SidebarFooterHighlightFrame = styled(RcxText, {
+ name: 'SidebarFooterHighlight',
+ fontFamily: '$body',
+ fontSize: '$c1',
+ fontWeight: '$c1',
+ lineHeight: '$c1',
+ letterSpacing: '$c1',
+ display: 'flex',
+ justifyContent: 'center',
+ paddingBlockStart: 4,
+ color: '$fontAnnotation',
+ overflowWrap: 'normal',
+});
+
export const SidebarFooterHighlight = ({
...props
}: SidebarFooterHighlightProps) => (
-
+
);
diff --git a/packages/fuselage/src/components/Sidebar/TopBar/TopBar.tsx b/packages/fuselage/src/components/Sidebar/TopBar/TopBar.tsx
index 891d3d5f52..3e1b863f5a 100644
--- a/packages/fuselage/src/components/Sidebar/TopBar/TopBar.tsx
+++ b/packages/fuselage/src/components/Sidebar/TopBar/TopBar.tsx
@@ -1,18 +1,25 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../../primitives';
type TopBarProps = {
children?: ReactNode;
className?: string;
};
+const TopBarFrame = styled(RcxView, {
+ name: 'SidebarTopBar',
+ display: 'flex',
+ flexDirection: 'column',
+ flexShrink: 0,
+ height: 64,
+ color: '$fontSecondaryInfo',
+});
+
/**
* Sidebar TopBar and ToolBox.
*/
-export const TopBar = ({ className, ...props }: TopBarProps) => (
-
+export const TopBar = ({ className: _className, ...props }: TopBarProps) => (
+
);
diff --git a/packages/fuselage/src/components/Sidebar/TopBar/TopBarSection.tsx b/packages/fuselage/src/components/Sidebar/TopBar/TopBarSection.tsx
index 633a5c33de..f52922dd1f 100644
--- a/packages/fuselage/src/components/Sidebar/TopBar/TopBarSection.tsx
+++ b/packages/fuselage/src/components/Sidebar/TopBar/TopBarSection.tsx
@@ -1,8 +1,9 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+import { RcxView } from '../../../primitives';
import SidebarDivider from '../SidebarDivider';
-import { TopBar } from './TopBar';
import { TopBarWrapper } from './TopBarWrapper';
type TopBarSectionProps = {
@@ -10,18 +11,22 @@ type TopBarSectionProps = {
className?: string;
};
+const TopBarSectionFrame = styled(RcxView, {
+ name: 'SidebarTopBarSection',
+ display: 'flex',
+ flexDirection: 'column',
+ flexShrink: 0,
+ height: 64,
+ color: '$fontSecondaryInfo',
+});
+
export const TopBarSection = ({
- className,
+ className: _className,
children,
...props
}: TopBarSectionProps) => (
-
+
-
+
);
diff --git a/packages/fuselage/src/components/Sidebar/TopBar/TopBarTitle.tsx b/packages/fuselage/src/components/Sidebar/TopBar/TopBarTitle.tsx
index 87d76f2f33..64406b031e 100644
--- a/packages/fuselage/src/components/Sidebar/TopBar/TopBarTitle.tsx
+++ b/packages/fuselage/src/components/Sidebar/TopBar/TopBarTitle.tsx
@@ -1,11 +1,27 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
-import { Box } from '../../Box';
+import { RcxText } from '../../../primitives';
type TopBarTitleProps = {
children?: ReactNode;
};
+const TopBarTitleFrame = styled(RcxText, {
+ name: 'SidebarTopBarTitle',
+ fontFamily: '$body',
+ fontSize: '$p2m',
+ fontWeight: '$p2m',
+ lineHeight: '$p2m',
+ letterSpacing: '$p2m',
+ color: '$fontTitlesLabels',
+ overflow: 'hidden',
+ // @ts-ignore
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflowWrap: 'normal',
+});
+
export const TopBarTitle = (props: TopBarTitleProps) => (
-
+
);
diff --git a/packages/fuselage/src/components/Sidebar/TopBar/TopBarToolBox.tsx b/packages/fuselage/src/components/Sidebar/TopBar/TopBarToolBox.tsx
index b7d26426be..865aba99f0 100644
--- a/packages/fuselage/src/components/Sidebar/TopBar/TopBarToolBox.tsx
+++ b/packages/fuselage/src/components/Sidebar/TopBar/TopBarToolBox.tsx
@@ -1,8 +1,9 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+import { RcxView } from '../../../primitives';
import SidebarDivider from '../SidebarDivider';
-import { TopBar } from './TopBar';
import { TopBarWrapper } from './TopBarWrapper';
type TopBarToolBoxProps = {
@@ -10,18 +11,22 @@ type TopBarToolBoxProps = {
className?: string;
};
+const TopBarToolBoxFrame = styled(RcxView, {
+ name: 'SidebarTopBarToolBox',
+ display: 'flex',
+ flexDirection: 'column',
+ flexShrink: 0,
+ height: 56,
+ color: '$fontSecondaryInfo',
+});
+
export const TopBarToolBox = ({
children,
- className,
+ className: _className,
...props
}: TopBarToolBoxProps) => (
-
+
-
+
);
diff --git a/packages/fuselage/src/components/Sidebar/TopBar/TopBarV2.tsx b/packages/fuselage/src/components/Sidebar/TopBar/TopBarV2.tsx
index 853d6e1e3b..b59f1f7f72 100644
--- a/packages/fuselage/src/components/Sidebar/TopBar/TopBarV2.tsx
+++ b/packages/fuselage/src/components/Sidebar/TopBar/TopBarV2.tsx
@@ -1,18 +1,18 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../../primitives';
type TopBarProps = {
children?: ReactNode;
className?: string;
};
-export const TopBarV2 = ({ className, ...props }: TopBarProps) => (
-
+const TopBarV2Frame = styled(RcxView, {
+ name: 'SidebarTopBarV2',
+ height: 44,
+});
+
+export const TopBarV2 = ({ className: _className, ...props }: TopBarProps) => (
+
);
diff --git a/packages/fuselage/src/components/Sidebar/TopBar/TopBarWrapper.tsx b/packages/fuselage/src/components/Sidebar/TopBar/TopBarWrapper.tsx
index 82b47ba5e7..cd8203acf4 100644
--- a/packages/fuselage/src/components/Sidebar/TopBar/TopBarWrapper.tsx
+++ b/packages/fuselage/src/components/Sidebar/TopBar/TopBarWrapper.tsx
@@ -1,12 +1,22 @@
import type { ReactNode } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../../primitives';
type TopBarWrapperProps = {
children?: ReactNode;
};
+const TopBarWrapperFrame = styled(RcxView, {
+ name: 'SidebarTopBarWrapper',
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ flexGrow: 1,
+ paddingInline: 16,
+});
+
export const TopBarWrapper = ({ children }: TopBarWrapperProps) => (
-
+ {children}
);
diff --git a/packages/fuselage/src/components/SidebarV2/Sidebar.styles.scss b/packages/fuselage/src/components/SidebarV2/Sidebar.styles.scss
deleted file mode 100644
index c51ea74304..0000000000
--- a/packages/fuselage/src/components/SidebarV2/Sidebar.styles.scss
+++ /dev/null
@@ -1,317 +0,0 @@
-@use '../../styles/colors.scss';
-@use '../../styles/lengths.scss';
-@use '../../styles/typography.scss';
-@import '../../styles/mixins/all.scss';
-
-@import './SidebarFooter/SidebarFooter.styles.scss';
-@import './SidebarItem/SidebarItem.styles.scss';
-@import './SidebarMedia/SidebarMedia.styles.scss';
-
-$sidebar-color-surface-default: theme(
- 'sidebar-color-surface-default',
- colors.surface(sidebar)
-);
-$sidebar-color-surface-hover: theme(
- 'sidebar-color-surface-hover',
- colors.surface(hover)
-);
-$sidebar-color-surface-selected: theme(
- 'sidebar-color-surface-selected',
- colors.surface(selected)
-);
-
-$sidebar-color-font-default: theme(
- 'sidebar-color-font-default',
- colors.font(default)
-);
-
-$sidebar-accordion-border-color: theme(
- 'sidebar-accordion-border-color',
- colors.stroke(light)
-);
-
-$sidebar-link-color: theme('sidebar-link-color', colors.font(titles-labels));
-
-$sidebar-banner-background-default: theme(
- 'sidebar-banner-background-default',
- colors.surface(sidebar)
-);
-$sidebar-banner-color-default: theme(
- 'sidebar-banner-color-default',
- colors.font(titles-labels)
-);
-
-$sidebar-banner-background-info: theme(
- 'sidebar-banner-background-info',
- colors.status-background(info)
-);
-$sidebar-banner-color-info: theme(
- 'sidebar-banner-background-info',
- colors.status-font(on-info)
-);
-
-$sidebar-banner-background-success: theme(
- 'sidebar-banner-background-success',
- colors.status-background(success)
-);
-$sidebar-banner-color-success: theme(
- 'sidebar-banner-background-success',
- colors.status-font(on-success)
-);
-
-$sidebar-banner-background-warning: theme(
- 'sidebar-banner-background-warning',
- colors.status-background(warning)
-);
-$sidebar-banner-color-warning: theme(
- 'sidebar-banner-background-warning',
- colors.status-font(on-warning)
-);
-
-$sidebar-banner-background-danger: theme(
- 'sidebar-banner-background-danger',
- colors.status-background(danger)
-);
-$sidebar-banner-color-danger: theme(
- 'sidebar-banner-background-danger',
- colors.status-font(on-danger)
-);
-
-.rcx-sidebar-v2 {
- position: relative;
-
- display: flex;
- flex-direction: column;
-
- height: lengths.size(full);
-
- color: $sidebar-color-font-default;
- background-color: $sidebar-color-surface-default;
-
- &--divider {
- border-color: theme(
- 'sidebar-color-stroke-extra-light',
- colors.stroke(light)
- );
- }
-
- &-section {
- display: flex;
- align-items: center;
-
- height: lengths.size(44);
-
- padding-inline: lengths.padding(16);
-
- gap: lengths.padding(8);
- }
-
- &-accordion {
- display: flex;
- overflow: hidden;
- flex-flow: column nowrap;
- justify-content: stretch;
- flex: 0 1 auto;
-
- height: lengths.size(full);
-
- &__wrapper {
- display: flex;
- overflow: scroll;
- flex-direction: column;
- }
- }
-
- &-collapse-group,
- &-accordion-item {
- display: flex;
- flex-flow: column nowrap;
-
- &__bar {
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
-
- min-height: lengths.size(24);
-
- padding: calc(lengths.padding(8) - lengths.border-width('default'))
- calc(lengths.padding(16) - lengths.border-width('default'));
-
- text-align: start;
-
- color: colors.font(default);
-
- background-color: $sidebar-color-surface-default;
- column-gap: lengths.padding(4);
-
- &[tabindex] {
- @include clickable;
- @include focus-state($shadow: false);
-
- &.hover,
- &:hover {
- background-color: colors.surface(tint);
- }
- }
-
- &--disabled {
- cursor: not-allowed;
-
- color: colors.font(disabled);
- background-color: colors.surface(disabled);
- }
- }
-
- &__title {
- flex: 1 1 lengths.size(none);
-
- margin: lengths.margin(none);
-
- white-space: nowrap;
-
- @include typography.use-text-ellipsis;
- @include typography.use-font-scale(c2);
- }
-
- &__panel {
- visibility: hidden;
- overflow: hidden;
-
- height: lengths.size(none);
- margin: lengths.margin(none);
- padding: lengths.padding(none);
-
- list-style: none;
-
- &--expanded {
- visibility: visible;
-
- flex-grow: 1;
-
- height: 100%;
- padding-block: lengths.padding(4) lengths.padding(8);
- }
- }
- }
-
- &-collapse-group__panel--expanded {
- padding-block: lengths.padding(none);
- }
-
- &-accordion-item {
- flex: 0 1 0;
-
- border: 2px solid transparent;
- border-bottom: lengths.border-width(default) solid
- $sidebar-accordion-border-color;
-
- &__bar {
- position: sticky;
-
- z-index: 1;
- top: 0;
-
- padding: lengths.padding(12) lengths.padding(16) lengths.padding(12)
- lengths.padding(none);
-
- border-radius: lengths.border-radius(small);
-
- background-color: colors.surface(sidebar);
-
- .rcx-sidebar-v2-accordion-item__chevron {
- visibility: hidden;
- }
-
- &:focus-visible,
- &.hover,
- &:hover {
- .rcx-sidebar-v2-accordion-item__chevron {
- visibility: visible;
- }
- }
- }
-
- &__title {
- @include typography.use-font-scale(h5);
- }
- }
-
- &-link {
- @include use-link-colors($color: $sidebar-link-color);
- }
-
- &-banner {
- display: flex;
- justify-content: space-between;
- align-items: center;
-
- padding: lengths.padding(16);
-
- color: $sidebar-banner-color-default;
- border-bottom: lengths.border-width(default) solid
- $sidebar-accordion-border-color;
- background-color: $sidebar-banner-background-default;
- gap: lengths.padding(12);
-
- &__addon {
- display: flex;
- align-items: center;
- }
-
- &__title {
- margin: 0;
- padding: 0;
-
- @include typography.use-font-scale(h5);
- }
-
- &__link {
- @include typography.use-font-scale(p2m);
- @include clickable();
-
- display: inline-block;
-
- text-decoration: underline;
- }
-
- &--info {
- color: $sidebar-banner-color-info;
- background-color: $sidebar-banner-background-info;
- }
-
- &--success {
- color: $sidebar-banner-color-success;
- background-color: $sidebar-banner-background-success;
- }
-
- &--warning {
- color: $sidebar-banner-color-warning;
- background-color: $sidebar-banner-background-warning;
- }
-
- &--danger {
- color: $sidebar-banner-color-danger;
- background-color: $sidebar-banner-background-danger;
- }
- }
-
- &--collapsed {
- overflow: hidden;
-
- width: lengths.size(48);
-
- &:not(:hover) {
- .rcx-sidebar-v2-item.rcx-sidebar-v2-link > .rcx-sidebar-v2-item__title,
- .rcx-sidebar-v2-banner__content {
- display: none;
- }
-
- .rcx-sidebar-v2-media__title,
- .rcx-sidebar-v2-footer {
- visibility: hidden;
-
- white-space: nowrap;
- }
- }
- }
-}
diff --git a/packages/fuselage/src/components/SidebarV2/Sidebar.tsx b/packages/fuselage/src/components/SidebarV2/Sidebar.tsx
index f01795f6b8..828408dd0f 100644
--- a/packages/fuselage/src/components/SidebarV2/Sidebar.tsx
+++ b/packages/fuselage/src/components/SidebarV2/Sidebar.tsx
@@ -1,22 +1,37 @@
import { forwardRef, type HTMLAttributes } from 'react';
+import { styled } from '@tamagui/core';
+
+import { RcxView } from '../../primitives';
export type SidebarProps = {
collapsed?: boolean;
} & HTMLAttributes;
+const SidebarV2Frame = styled(RcxView, {
+ name: 'SidebarV2',
+ tag: 'nav',
+ position: 'relative',
+ display: 'flex',
+ flexDirection: 'column',
+ height: '100%',
+ color: '$fontDefault',
+ backgroundColor: '$surfaceSidebar',
+ variants: {
+ collapsed: {
+ true: {
+ overflow: 'hidden',
+ width: 48,
+ },
+ },
+ } as const,
+});
+
export const Sidebar = forwardRef(
- ({ collapsed, className, ...props }, ref) => (
-