diff --git a/apps/storybook/CHANGELOG.md b/apps/storybook/CHANGELOG.md
index e159572c..454f4073 100644
--- a/apps/storybook/CHANGELOG.md
+++ b/apps/storybook/CHANGELOG.md
@@ -1,5 +1,12 @@
# @syntax/storybook
+## 0.13.5
+
+### Patch Changes
+
+- Updated dependencies [cbcfc85]
+ - @cambly/syntax-core@10.12.0
+
## 0.13.4
### Patch Changes
diff --git a/apps/storybook/package.json b/apps/storybook/package.json
index d330cc68..444da9f7 100644
--- a/apps/storybook/package.json
+++ b/apps/storybook/package.json
@@ -1,6 +1,6 @@
{
"name": "@syntax/storybook",
- "version": "0.13.4",
+ "version": "0.13.5",
"private": true,
"scripts": {
"dev": "NODE_OPTIONS=--openssl-legacy-provider storybook dev -p 6006",
@@ -9,7 +9,7 @@
"clean": "rm -rf .turbo && rm -rf node_modules"
},
"dependencies": {
- "@cambly/syntax-core": "workspace:10.11.0",
+ "@cambly/syntax-core": "workspace:10.12.0",
"@cambly/syntax-design-tokens": "workspace:0.11.1",
"@cambly/syntax-floating-components": "workspace:^0.5.0",
"react": "18.2.0",
diff --git a/packages/syntax-core/CHANGELOG.md b/packages/syntax-core/CHANGELOG.md
index dda83d6c..a0222b45 100644
--- a/packages/syntax-core/CHANGELOG.md
+++ b/packages/syntax-core/CHANGELOG.md
@@ -1,5 +1,11 @@
# @cambly/syntax-core
+## 10.12.0
+
+### Minor Changes
+
+- cbcfc85: Cambio: add Avatar & AvatarGroup updates
+
## 10.11.0
### Minor Changes
diff --git a/packages/syntax-core/package.json b/packages/syntax-core/package.json
index 0995db52..2ac1aa73 100644
--- a/packages/syntax-core/package.json
+++ b/packages/syntax-core/package.json
@@ -1,7 +1,7 @@
{
"name": "@cambly/syntax-core",
"description": "Cambly design system core components",
- "version": "10.11.0",
+ "version": "10.12.0",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
@@ -14,7 +14,7 @@
"scripts": {
"build": "tsup",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
- "dev": "tsup --watch",
+ "dev": "NODE_OPTIONS=--max_old_space_size=4096 tsup --watch",
"lint": "TIMING=1 eslint \"src/**/*.ts*\" --max-warnings 0",
"stylelint": "stylelint \"**/*.css\"",
"stylelint:fix": "npm run stylelint --fix",
diff --git a/packages/syntax-core/src/Avatar/Avatar.module.css b/packages/syntax-core/src/Avatar/Avatar.module.css
index b4410398..0f46ad2b 100644
--- a/packages/syntax-core/src/Avatar/Avatar.module.css
+++ b/packages/syntax-core/src/Avatar/Avatar.module.css
@@ -8,9 +8,16 @@
background-size: cover;
background-repeat: no-repeat;
border-radius: 50%;
+}
+
+.avatarImageClassic {
border: 2px solid #fff;
}
+.avatarImageOutlineCambio {
+ border: 2px solid var(--color-cambio-gray-100);
+}
+
.sm {
width: 24px;
height: 24px;
@@ -30,3 +37,23 @@
width: 128px;
height: 129px;
}
+
+.smCambio {
+ width: 32px;
+ height: 32px;
+}
+
+.mdCambio {
+ width: 48px;
+ height: 48px;
+}
+
+.lgCambio {
+ width: 64px;
+ height: 64px;
+}
+
+.xlCambio {
+ width: 64px;
+ height: 64px;
+}
diff --git a/packages/syntax-core/src/Avatar/Avatar.tsx b/packages/syntax-core/src/Avatar/Avatar.tsx
index 92aff5b3..63fc36af 100644
--- a/packages/syntax-core/src/Avatar/Avatar.tsx
+++ b/packages/syntax-core/src/Avatar/Avatar.tsx
@@ -3,6 +3,7 @@ import classNames from "classnames";
import styles from "./Avatar.module.css";
import Box from "../Box/Box";
import { useAvatarGroup } from "../AvatarGroup/AvatarGroup";
+import { useTheme } from "../ThemeProvider/ThemeProvider";
const sizeToIconStyles = {
sm: { bottom: 6, marginInlineEnd: 2, height: 4, width: 4 },
@@ -11,30 +12,51 @@ const sizeToIconStyles = {
xl: { bottom: 12, marginInlineEnd: 12, height: 16, width: 16 },
} as const;
-const sizeToMargin = {
+const sizeToMarginClassic = {
sm: -16,
md: -28,
lg: -48,
xl: -88,
} as const;
+const sizeToMarginCambio = {
+ sm: -12,
+ md: -20,
+ lg: -28,
+ xl: -28,
+} as const;
+
function AvatarInternal({
accessibilityLabel,
icon,
+ outline,
size = "md",
src,
}: {
accessibilityLabel: string;
icon?: React.ReactElement;
+ outline?: boolean;
size?: "sm" | "md" | "lg" | "xl";
src: string;
}): ReactElement {
+ const { themeName } = useTheme();
+
return (
-
+
{icon && (
@@ -81,11 +103,18 @@ const Avatar = ({
/**
* Size of the avatar.
*
+ * Classic:
* * `sm`: 24px
* * `md`: 40px
* * `lg`: 72px
* * `xl`: 128px
*
+ * Cambio:
+ * * `sm`: 32px
+ * * `md`: 48px
+ * * `lg`: 64px
+ * * `xl`: 64px (deprecated, maps to `lg` in Cambio)
+ *
* @defaultValue `md`
*/
size?: "sm" | "md" | "lg" | "xl";
@@ -95,6 +124,7 @@ const Avatar = ({
src: string;
}): JSX.Element => {
const avatarGroupContext = useAvatarGroup();
+ const { themeName } = useTheme();
if (avatarGroupContext !== null) {
return (
@@ -102,7 +132,10 @@ const Avatar = ({
position="relative"
dangerouslySetInlineStyle={{
__style: {
- marginInlineEnd: sizeToMargin[avatarGroupContext.size],
+ marginInlineEnd:
+ themeName === "cambio"
+ ? sizeToMarginCambio[avatarGroupContext.size]
+ : sizeToMarginClassic[avatarGroupContext.size],
},
}}
>
@@ -116,6 +149,7 @@ const Avatar = ({
diff --git a/packages/syntax-core/src/AvatarGroup/AvatarGroup.tsx b/packages/syntax-core/src/AvatarGroup/AvatarGroup.tsx
index 59718b88..1f56955e 100644
--- a/packages/syntax-core/src/AvatarGroup/AvatarGroup.tsx
+++ b/packages/syntax-core/src/AvatarGroup/AvatarGroup.tsx
@@ -5,8 +5,14 @@ import {
type ReactElement,
} from "react";
import Box from "../Box/Box";
+import { useTheme } from "../ThemeProvider/ThemeProvider";
-type Size = "sm" | "md" | "lg" | "xl";
+type Size =
+ | "sm"
+ | "md"
+ | "lg"
+ /* `xl` is deprecated and mapped to `lg` in Cambio */
+ | "xl";
type Orientation = "standard" | "reverse";
type AvatarGroupContextType = {
@@ -43,11 +49,18 @@ export default function AvatarGroup({
/**
* Size of the avatars in the AvatarGroup.
*
+ * Classic:
* * `sm`: 24px
* * `md`: 40px
* * `lg`: 72px
* * `xl`: 128px
*
+ * Cambio:
+ * * `sm`: 32px
+ * * `md`: 48px
+ * * `lg`: 64px
+ * * `xl`: 64px (deprecated, maps to `lg` in Cambio)
+ *
* @defaultValue `md`
*/
size?: Size;
@@ -65,8 +78,11 @@ export default function AvatarGroup({
*/
children: ReactNode;
}): ReactElement {
+ const { themeName } = useTheme();
+ const parsedSize = themeName === "cambio" && size === "xl" ? "lg" : size;
+
return (
-
+
(
-
-
-
- {Icon && }
-
- {text}
-
-
-
-
-);
+}): JSX.Element => {
+ const { themeName } = useTheme();
+
+ return (
+
+
+
+ {Icon && }
+
+ {text}
+
+
+
+
+ );
+};
export default Badge;
diff --git a/packages/syntax-core/src/Box/Box.module.css b/packages/syntax-core/src/Box/Box.module.css
index 861abf89..f6da4bfe 100644
--- a/packages/syntax-core/src/Box/Box.module.css
+++ b/packages/syntax-core/src/Box/Box.module.css
@@ -119,6 +119,10 @@
display: inline-block;
}
+.inlineFlex {
+ display: inline-flex;
+}
+
.none {
display: none;
}
@@ -149,6 +153,10 @@
display: inline-block;
}
+ .inlineFlexSmall {
+ display: inline-flex;
+ }
+
.noneSmall {
display: none;
}
@@ -180,6 +188,10 @@
display: inline-block;
}
+ .inlineFlexLarge {
+ display: inline-flex;
+ }
+
.noneLarge {
display: none;
}
diff --git a/packages/syntax-core/src/Box/Box.tsx b/packages/syntax-core/src/Box/Box.tsx
index 61aa26e0..2a8570c5 100644
--- a/packages/syntax-core/src/Box/Box.tsx
+++ b/packages/syntax-core/src/Box/Box.tsx
@@ -7,6 +7,7 @@ import type allColors from "../colors/allColors";
import colorStyles from "../colors/colors.module.css";
import roundingStyles from "../rounding.module.css";
import { forwardRef } from "react";
+import { useTheme } from "../ThemeProvider/ThemeProvider";
type AlignItems = "baseline" | "center" | "end" | "start" | "stretch";
type As =
@@ -24,7 +25,13 @@ type As =
| "summary";
type Dimension = number | string;
type Direction = "row" | "column";
-type Display = "none" | "flex" | "block" | "inlineBlock" | "visuallyHidden";
+type Display =
+ | "none"
+ | "flex"
+ | "block"
+ | "inlineBlock"
+ | "inlineFlex"
+ | "visuallyHidden";
type Gap = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
type JustifyContent =
| "start"
@@ -353,6 +360,7 @@ type BoxProps = {
/**
* Border radius of the box.
*
+ * Classic:
* * `none`: 0px
* * `sm`: 8px
* * `md`: 12px
@@ -360,6 +368,14 @@ type BoxProps = {
* * `xl`: 24px
* * `full`: 999px
*
+ * Cambio:
+ * * `none`: 0px
+ * * `sm`: 4px
+ * * `md`: 8px
+ * * `lg`: 8px (maps to `md`)
+ * * `xl`: 8px (maps to `md`)
+ * * `full`: 999px
+ *
* @defaultValue "none"
*/
rounding?: "xl" | "lg" | "md" | "sm" | "full" | "none";
@@ -417,6 +433,15 @@ type BoxProps = {
width?: Dimension;
};
+function roundingCambio(
+ rounding: "sm" | "md" | "lg" | "xl" | "full",
+): "sm" | "md" | "full" {
+ if (rounding === "lg" || rounding === "xl") {
+ return "md";
+ }
+ return rounding;
+}
+
/**
* [Box](https://cambly-syntax.vercel.app/?path=/docs/components-box--docs) is primitive design component and is used by lots of other components. It keeps details like spacing, borders and colors consistent across all of Syntax.
*
@@ -429,6 +454,7 @@ const Box = forwardRef(function Box(
ref,
): ReactElement {
const { as: BoxElement = "div", children, ...boxProps } = props;
+ const { themeName } = useTheme();
const {
// Classname
@@ -492,6 +518,15 @@ const Box = forwardRef(function Box(
...maybePassThroughProps
} = boxProps;
+ const classicRoundingStyle =
+ themeName === "classic" && rounding && rounding !== "none"
+ ? roundingStyles[`rounding${rounding}`]
+ : undefined;
+ const cambioRoundingStyles =
+ themeName === "cambio" && rounding && rounding !== "none"
+ ? roundingStyles[`rounding${roundingCambio(rounding)}Cambio`]
+ : undefined;
+
const parsedProps = {
className: classNames(
styles.box,
@@ -575,7 +610,8 @@ const Box = forwardRef(function Box(
smJustifyContent && styles[`justifyContent${smJustifyContent}Small`],
lgJustifyContent && styles[`justifyContent${lgJustifyContent}Large`],
position && position !== "static" && styles[position],
- rounding && rounding !== "none" && roundingStyles[`rounding${rounding}`],
+ classicRoundingStyle,
+ cambioRoundingStyles,
overflow && styles[`overflow${overflow}`],
overflowX && styles[`overflowX${overflowX}`],
overflowY && styles[`overflowY${overflowY}`],
diff --git a/packages/syntax-core/src/ButtonGroup/ButtonGroup.module.css b/packages/syntax-core/src/ButtonGroup/ButtonGroup.module.css
index 69040199..641e6536 100644
--- a/packages/syntax-core/src/ButtonGroup/ButtonGroup.module.css
+++ b/packages/syntax-core/src/ButtonGroup/ButtonGroup.module.css
@@ -21,3 +21,15 @@
.largeGap {
gap: 16px;
}
+
+.smallGapCambio {
+ gap: 4px;
+}
+
+.mediumGapCambio {
+ gap: 8px;
+}
+
+.largeGapCambio {
+ gap: 12px;
+}
diff --git a/packages/syntax-core/src/ButtonGroup/ButtonGroup.tsx b/packages/syntax-core/src/ButtonGroup/ButtonGroup.tsx
index f0792bbb..e8d9ac04 100644
--- a/packages/syntax-core/src/ButtonGroup/ButtonGroup.tsx
+++ b/packages/syntax-core/src/ButtonGroup/ButtonGroup.tsx
@@ -2,13 +2,20 @@ import { type ReactElement, type ReactNode } from "react";
import styles from "./ButtonGroup.module.css";
import { type Size } from "../constants";
import classNames from "classnames";
+import { useTheme } from "../ThemeProvider/ThemeProvider";
-const gap = {
+const gapClassic = {
sm: styles.smallGap,
md: styles.mediumGap,
lg: styles.largeGap,
} as const;
+const gapCambio = {
+ sm: styles.smallGapCambio,
+ md: styles.mediumGapCambio,
+ lg: styles.largeGapCambio,
+} as const;
+
/**
* [ButtonGroup](https://cambly-syntax.vercel.app/?path=/docs/components-buttongroup--docs) groups buttons in a row or column with consistent spacing between each button.
*/
@@ -26,10 +33,16 @@ const ButtonGroup = ({
/**
* The size of the button group defines the spacing between each button
*
+ * Classic:
* * `sm`: 8px
* * `md`: 12px
* * `lg`: 16px
*
+ * Cambio:
+ * * `sm`: 4px
+ * * `md`: 8px
+ * * `lg`: 12px
+ *
* @defaultValue "md"
*/
size?: (typeof Size)[number];
@@ -38,10 +51,15 @@ const ButtonGroup = ({
*/
children?: ReactNode;
}): ReactElement => {
- const classnames = classNames(styles.buttonGroup, gap[size], {
- [styles.horizontal]: orientation === "horizontal",
- [styles.vertical]: orientation === "vertical",
- });
+ const { themeName } = useTheme();
+ const classnames = classNames(
+ styles.buttonGroup,
+ themeName === "classic" ? gapClassic[size] : gapCambio[size],
+ {
+ [styles.horizontal]: orientation === "horizontal",
+ [styles.vertical]: orientation === "vertical",
+ },
+ );
return {children}
;
};
diff --git a/packages/syntax-core/src/Card/Card.tsx b/packages/syntax-core/src/Card/Card.tsx
index e0ed1dee..1c153b2c 100644
--- a/packages/syntax-core/src/Card/Card.tsx
+++ b/packages/syntax-core/src/Card/Card.tsx
@@ -1,4 +1,5 @@
import Box from "../Box/Box";
+import { useTheme } from "../ThemeProvider/ThemeProvider";
import type allColors from "../colors/allColors";
type CardType = {
@@ -15,6 +16,15 @@ type CardType = {
* The child components to render within Card.
*/
children: JSX.Element;
+ /**
+ * The size of the card (Cambio only) which specifies the padding and spacing of the card.
+ *
+ * `compact`: 8px padding
+ * `roomy`: 16px padding
+ *
+ * @defaultValue `roomy`
+ */
+ size?: "compact" | "roomy";
};
/**
@@ -23,14 +33,18 @@ type CardType = {
export default function Card({
backgroundColor = "white",
children,
+ size,
"data-testid": dataTestId,
}: CardType): JSX.Element {
+ const { themeName } = useTheme();
+
+ const cambioPadding = size === "compact" ? 2 : 4;
+
return (
{
+ const { themeName } = useTheme();
const isHydrated = useIsHydrated();
const disabled = !isHydrated || disabledProp;
const [isFocused, setIsFocused] = useState(false);
const { isFocusVisible } = useFocusVisible();
- const checkboxStyling = classNames(styles.checkbox, styles[size]);
- const uncheckedStyling = classNames(checkboxStyling, styles.uncheckedBox, {
- [styles.uncheckedBorder]: !error,
- [styles.uncheckedErrorBorder]: error,
- [focusStyles.accessibilityOutlineFocus]: isFocused && isFocusVisible,
- });
- const checkedStyling = classNames(checkboxStyling, styles.checkedBox, {
- [styles.checkedNonError]: !error,
- [styles.checkedError]: error,
+ const checkboxStyling = classNames(styles.checkbox, styles[size], {
[focusStyles.accessibilityOutlineFocus]: isFocused && isFocusVisible,
});
+ const classicCheckboxStyling = classNames(
+ checkboxStyling,
+ styles[`${size}BorderRadius`],
+ );
+
+ const cambioCheckboxStyling = classNames(
+ checkboxStyling,
+ styles.cambioCheckbox,
+ error
+ ? colorStyles.cambioDestructive370BackgroundColor
+ : colorStyles.cambioGray370BackgroundColor,
+ );
+
+ const uncheckedStyling =
+ themeName === "classic"
+ ? classNames(classicCheckboxStyling, styles.uncheckedBox, {
+ [styles.uncheckedBorder]: !error,
+ [styles.uncheckedErrorBorder]: error,
+ })
+ : cambioCheckboxStyling;
+ const checkedStyling =
+ themeName === "classic"
+ ? classNames(classicCheckboxStyling, styles.checkedBox, {
+ [styles.checkedNonError]: !error,
+ [styles.checkedError]: error,
+ })
+ : cambioCheckboxStyling;
+
return (