Skip to content

Commit

Permalink
feat: useSubscribe hook introduced
Browse files Browse the repository at this point in the history
  • Loading branch information
mauroerta committed May 21, 2021
1 parent dd7170d commit 8de68f2
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 15 deletions.
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# @morfeo

Morfeo it's a set of tools that helps you to build **consistent UIs** based on a single source of truth: the **theme**.
morfeo is a framework-agnostic set of tools that will help you to build your next **design system**
based on a single source of truth: the **theme**.

You can use it with any framework like [React](https://reactjs.org/), [React Native](https://reactnative.dev/), [Vue](https://v3.vuejs.org/), [Angular](https://angular.io/), [Svelte](https://svelte.dev/) or just Vanilla JS/TS.

Expand Down Expand Up @@ -48,11 +49,13 @@ morfeo is cross-framework, but to have a faster implementation and a better deve

**@morfeo/native** perfect for React native

**@morfeo/svelte** made for svelte

**@morfeo/styled-components-web** deep integration with styled-components

**@morfeo/angular** **_coming soon_**

**@morfeo/jss** perfect for svelte
**@morfeo/jss** to generate plain css from _cssinjs_

**@morfeo/hooks** hook for React/React Native

Expand All @@ -62,7 +65,7 @@ morfeo is cross-framework, but to have a faster implementation and a better deve

## Motivations

When your application start to grow, maintain UI consistency it's not easy.
When your application starts to grow, maintain UI consistency it's not easy.
Even in popular applications we often face **wrong typographies**, different **color pallettes** used across different pages or inconsistent **spacings** in each component.

These problems are even more frequent in large applications where different teams works on different features (maybe with different technologies and frameworks).
Expand All @@ -73,7 +76,9 @@ These problems are even more frequent in large applications where different team

The main concepts around morfeo are 2 entities, **theme** and **parsers**:

`theme` it's an handler that contains the **design language** of your application, for example a set of colors, spacings, shadows, sizes and gradients, and example of theme could be the following:
`theme` it's an handler that contains the **design language** of your application, for example a set of colors, spacings, shadows, sizes and gradients, and the base style of your components.

An example of theme could be the following:

```typescript
import { theme } from '@morfeo/core';
Expand Down Expand Up @@ -126,7 +131,7 @@ function Button() {
}
```

Or if you want, you can use the hooks inside the package `@morfeo/hook`
Or if you want, you can use the hooks inside the package `@morfeo/hooks`

```tsx
import { theme } from '@morfeo/core';
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/theme/createTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ export function createTheme() {
listeners.map(([listener]) => listener(context));
}

function set(theme: Partial<Theme>) {
function set(
theme: {
[TK in ThemeKey]?: Partial<Theme[TK]>;
},
) {
context = deepMerge(context, theme) as Theme;
callListeners();
}
Expand Down
1 change: 1 addition & 0 deletions packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './useTheme';
export * from './useStyles';
export * from './useSubscribe';
17 changes: 14 additions & 3 deletions packages/hooks/src/useStyles.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Style, ResolvedStyle, parsers } from '@morfeo/core';
import { useMemo, useState } from 'react';
import { useSubscribe } from './useSubscribe';

export function useStyles<K extends string>(styles: Record<K, Style>) {
function parseStyles<K extends string>(styles: Record<K, Style>) {
const styleKeys = Object.keys(styles);

return styleKeys.reduce(
(acc, curr) => ({
...acc,
Expand All @@ -12,6 +13,16 @@ export function useStyles<K extends string>(styles: Record<K, Style>) {
) as Record<K, ResolvedStyle>;
}

export function useStyles<K extends string>(styles: Record<K, Style>) {
const [_, setForceRender] = useState(0);
useSubscribe(() => {
setForceRender(prev => prev + 1);
});
return useMemo(() => parseStyles(styles), [styles]);
}

export function useStyle(style: Style) {
return parsers.resolve(style);
const { style: parsedStyle } = useStyles({ style });

return parsedStyle;
}
18 changes: 18 additions & 0 deletions packages/hooks/src/useSubscribe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { theme, Theme } from '@morfeo/core';
import { DependencyList, useEffect, useRef } from 'react';

type ThemeSubscriber = (nextTheme: Theme) => void;

export function useSubscribe(
callback: ThemeSubscriber,
dependencies: DependencyList = [],
) {
const uid = useRef<string>(theme.subscribe(callback));
useEffect(() => {
return () => {
theme.cleanUp(uid.current);
};
}, dependencies);

return uid.current;
}
8 changes: 2 additions & 6 deletions packages/hooks/src/useTheme.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { theme, ThemeKey, Theme } from '@morfeo/core';
import { useEffect, useState } from 'react';
import { useSubscribe } from './useSubscribe';

/**
* Same as `theme.get()` but connected it will cause a re-render
Expand All @@ -8,12 +9,7 @@ import { useEffect, useState } from 'react';
export function useTheme() {
const [t, setTheme] = useState(theme.get());

useEffect(() => {
const uid = theme.subscribe(setTheme);
return () => {
theme.cleanUp(uid);
};
}, []);
useSubscribe(setTheme);

return t;
}
Expand Down
16 changes: 16 additions & 0 deletions packages/hooks/tests/useStyles.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { renderHook } from '@testing-library/react-hooks';
import { theme } from '@morfeo/core';
import { useStyles, useStyle } from '../src';
import { act } from 'react-test-renderer';

const THEME = {
colors: {
Expand Down Expand Up @@ -36,4 +37,19 @@ describe('useStyle', () => {

expect(result.current).toEqual({ padding: '10px' });
});

test('should change the result after theme update', async () => {
const { result } = renderHook(() => useStyle({ color: 'primary' }));
expect(result.current).toEqual({ color: 'black' });

act(() =>
theme.set({
colors: {
primary: 'white',
secondary: 'black',
},
}),
);
expect(result.current).toEqual({ color: 'white' });
});
});
27 changes: 27 additions & 0 deletions packages/hooks/tests/useSubscribe.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { theme } from '@morfeo/core';
import { useSubscribe } from '../src';

const THEME = {
colors: {
primary: 'black',
secondary: 'white',
},
space: {
s: '10px',
},
} as any;

beforeAll(() => {
theme.set(THEME);
});

describe('useSubscribe', () => {
test('should call the callback after a change of the theme', async () => {
const mockFn = jest.fn();
renderHook(() => useSubscribe(mockFn));
act(() => theme.set({}));
act(() => theme.set({}));
expect(mockFn).toHaveBeenCalledTimes(2);
});
});

0 comments on commit 8de68f2

Please sign in to comment.