diff --git a/.changeset/rotten-beers-hunt.md b/.changeset/rotten-beers-hunt.md
new file mode 100644
index 0000000000..81d8a516aa
--- /dev/null
+++ b/.changeset/rotten-beers-hunt.md
@@ -0,0 +1,6 @@
+---
+"@marigold/components": minor
+"@marigold/system": minor
+---
+
+feat: `getColor` util
diff --git a/packages/components/src/Headline/Headline.tsx b/packages/components/src/Headline/Headline.tsx
index c21ff57aa0..615637693d 100644
--- a/packages/components/src/Headline/Headline.tsx
+++ b/packages/components/src/Headline/Headline.tsx
@@ -5,7 +5,7 @@ import {
TextAlignProp,
cn,
createVar,
- get,
+ getColor,
textAlign,
useClassNames,
useTheme,
@@ -43,10 +43,7 @@ const _Headline = ({
{...props}
className={cn(classNames, 'text-[--color]', textAlign[align])}
style={createVar({
- color:
- color &&
- theme.colors &&
- get(theme.colors, color.replace('-', '.'), color /* fallback */),
+ color: color && getColor(theme, color, color /* fallback */),
})}
>
{children}
diff --git a/packages/components/src/Text/Text.tsx b/packages/components/src/Text/Text.tsx
index 536f9ea565..37a89f28de 100644
--- a/packages/components/src/Text/Text.tsx
+++ b/packages/components/src/Text/Text.tsx
@@ -8,7 +8,7 @@ import {
createVar,
cursorStyle,
fontWeight,
- get,
+ getColor,
textAlign,
textSize,
textStyle,
@@ -66,10 +66,7 @@ export const Text = ({
fontSize && textSize[fontSize]
)}
style={createVar({
- color:
- color &&
- theme.colors &&
- get(theme.colors, color.replace('-', '.'), color /* fallback */),
+ color: color && getColor(theme, color, color /* fallback */),
})}
>
{children}
diff --git a/packages/system/src/components/SVG/SVG.stories.tsx b/packages/system/src/components/SVG/SVG.stories.tsx
index c1100a1ee5..507b6cf96b 100644
--- a/packages/system/src/components/SVG/SVG.stories.tsx
+++ b/packages/system/src/components/SVG/SVG.stories.tsx
@@ -29,6 +29,16 @@ const meta = {
},
},
},
+ color: {
+ control: {
+ type: 'text',
+ },
+ table: {
+ defaultValue: {
+ summary: undefined,
+ },
+ },
+ },
},
} satisfies Meta;
diff --git a/packages/system/src/components/SVG/SVG.test.tsx b/packages/system/src/components/SVG/SVG.test.tsx
index eba034b283..b99df46ba3 100644
--- a/packages/system/src/components/SVG/SVG.test.tsx
+++ b/packages/system/src/components/SVG/SVG.test.tsx
@@ -32,17 +32,17 @@ test('supports classNames', () => {
const svg = screen.getByTestId(/svg/);
expect(svg).toMatchInlineSnapshot(`
-
- `);
+
+`);
});
test('supports default size', () => {
@@ -90,17 +90,17 @@ test('supports responsive sizing', () => {
const svg = screen.getByTestId(/svg/);
expect(svg).toMatchInlineSnapshot(`
-
- `);
+
+`);
});
test('supports custom width instead of default size', () => {
@@ -148,3 +148,16 @@ test('forwards ref', () => {
expect(ref.current).toBeInstanceOf(SVGElement);
});
+
+test('supports color prop', () => {
+ render(
+
+
+
+ );
+ const svg = screen.getByTestId(/svg/);
+
+ expect(svg.style.cssText).toMatchInlineSnapshot(`"--color: #ffa8a8;"`);
+});
diff --git a/packages/system/src/components/SVG/SVG.tsx b/packages/system/src/components/SVG/SVG.tsx
index 8d12924524..236432cb5e 100644
--- a/packages/system/src/components/SVG/SVG.tsx
+++ b/packages/system/src/components/SVG/SVG.tsx
@@ -2,23 +2,31 @@ import React, { forwardRef } from 'react';
import { HtmlProps } from '@marigold/types';
-import { cn } from '../../utils';
+import { useTheme } from '../../hooks';
+import { cn, createVar, getColor } from '../../utils';
-export interface SVGProps extends Omit, 'fill'> {
+export interface SVGProps extends Omit, 'fill' | 'style'> {
size?: number | string | number[] | string[];
className?: string;
}
export const SVG = forwardRef(
- ({ size = 24, children, className, ...props }, ref) => (
-
- )
+ ({ size = 24, children, className, color, ...props }, ref) => {
+ const theme = useTheme();
+
+ return (
+
+ );
+ }
);
diff --git a/packages/system/src/utils.test.ts b/packages/system/src/utils.test.ts
index c620ff2108..2ffb62760b 100644
--- a/packages/system/src/utils.test.ts
+++ b/packages/system/src/utils.test.ts
@@ -1,4 +1,4 @@
-import { cva } from './utils';
+import { cva, get, getColor } from './utils';
test('cva (simple)', () => {
expect(cva(['text-sm'])()).toMatchInlineSnapshot(`"text-sm"`);
@@ -22,3 +22,65 @@ test('cva (variants)', () => {
);
expect(styles({ size: 'large' })).toMatchInlineSnapshot(`"text-lg"`);
});
+
+test('get', () => {
+ const obj = {
+ root: 'root-value',
+ nested: {
+ value: {
+ very: {
+ deep: 'deeeeply-nested-value',
+ },
+ DEFAULT: 'this-is-just-for-reference',
+ },
+ },
+ };
+
+ expect(get(obj, 'does.not.exist')).toMatchInlineSnapshot(`undefined`);
+ expect(get(obj, 'does.not.exist', 'fallback')).toMatchInlineSnapshot(
+ `"fallback"`
+ );
+
+ expect(get(obj, 'root')).toMatchInlineSnapshot(`"root-value"`);
+ expect(get(obj, 'nested.value.very.deep')).toMatchInlineSnapshot(
+ `"deeeeply-nested-value"`
+ );
+
+ expect(get(obj, 'nested.value')).toMatchInlineSnapshot(`
+{
+ "DEFAULT": "this-is-just-for-reference",
+ "very": {
+ "deep": "deeeeply-nested-value",
+ },
+}
+`);
+});
+
+test('getColor', () => {
+ const theme = {
+ colors: {
+ brand: {
+ 100: 'brand-color',
+ },
+ accent: {
+ DEFAULT: 'default-accent-color',
+ hover: 'accent-hover-color',
+ },
+ },
+ };
+
+ expect(getColor(theme, 'does-not-exist')).toMatchInlineSnapshot(`undefined`);
+ expect(getColor(theme, 'does-not-exist', 'fallback')).toMatchInlineSnapshot(
+ `"fallback"`
+ );
+
+ expect(getColor(theme, 'brand-100')).toMatchInlineSnapshot(`"brand-color"`);
+ expect(getColor(theme, 'accent-hover')).toMatchInlineSnapshot(
+ `"accent-hover-color"`
+ );
+
+ // Support Tailwinds DEFAULT
+ expect(getColor(theme, 'accent')).toMatchInlineSnapshot(
+ `"default-accent-color"`
+ );
+});
diff --git a/packages/system/src/utils.ts b/packages/system/src/utils.ts
index 076a09b9fe..a711ea5787 100644
--- a/packages/system/src/utils.ts
+++ b/packages/system/src/utils.ts
@@ -59,6 +59,9 @@ export const createVar = (o: { [key: string]: string | number | undefined }) =>
Object.entries(o).map(([name, val]) => [`--${name}`, val])
) as React.CSSProperties;
+export const isObject = (val: any): val is { [key: string]: any } =>
+ val && val.constructor === Object;
+
/**
* Safely get a dot-notated path within a nested object, with ability
* to return a default if the full key path does not exist or
@@ -77,3 +80,18 @@ export const get = (obj: object, path: string, fallback?: any): any => {
return result === undefined ? fallback : result;
};
+
+/**
+ * Safely get a color value from a Tailwind theme object. This also supports
+ * Tailwind's "DEFAULT" fallback.
+ *
+ * Note: Use the CSS "var name" (e.g. primary-500) not the dot notation.
+ */
+export const getColor = (
+ theme: { colors?: object },
+ path: string,
+ fallback?: any
+): any => {
+ const result = get(theme.colors || {}, path.replace('-', '.'), fallback);
+ return isObject(result) ? result['DEFAULT'] : result;
+};