Skip to content

Commit

Permalink
Merge pull request #30 from heyjul3s/feat/text-input
Browse files Browse the repository at this point in the history
Feat/text input
  • Loading branch information
heyjul3s committed Dec 30, 2020
2 parents 6573aaf + 8e083fa commit 111f533
Show file tree
Hide file tree
Showing 19 changed files with 339 additions and 65 deletions.
2 changes: 1 addition & 1 deletion packages/block/__tests__/block.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ describe('@artifak/block', () => {
describe('createBlocks - generates Block React components based on styles object argument provided', () => {
it('should return the base Typography system component when provided with an invalid argument', () => {
const expected = {};
expect(createBlocks({} as any, {} as any)).toEqual(expected);
expect(createBlocks({} as any, {} as any)).toHaveProperty('Base');
expect(createBlocks(null as any, null as any)).toEqual(expected);
expect(createBlocks(false as any, false as any)).toEqual(expected);
expect(createBlocks(0 as any, 0 as any)).toEqual(expected);
Expand Down
9 changes: 4 additions & 5 deletions packages/block/src/createBlocks.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import {
createComponents,
GenericRecord,
ComponentsRecord,
Settings,
StyledComponentConfig,
Variant
StyledComponentConfig
} from '@artifak/component-generator';

/* eslint-disable @typescript-eslint/no-explicit-any */
Expand All @@ -14,7 +13,7 @@ export function createBlocks<
Element = HTMLDivElement
>(
base: StyledComponentConfig<Props, ThemeType, Element>,
settings: Settings
): GenericRecord<Config, React.FC<Props & Variant<ThemeType>>> {
settings: Settings<Element>
): ComponentsRecord<Config, Props, ThemeType> {
return createComponents<Config, ThemeType, Props, Element>(base, settings);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { system } from 'styled-system';
import { createStyledComponent } from '../src';
import { createStyledComponent, extractStylePseudos } from '../src';

describe('createStyledComponent - creates a styled component with params of styles, styleProps, attrs and element', () => {
it('should return a styled component with default config when no arguments are provided', () => {
Expand All @@ -26,3 +26,33 @@ describe('createStyledComponent - creates a styled component with params of styl
expect(resultingStyledComponent).toBeDefined();
});
});

describe('extractStylePseudos', () => {
it('should return an empty object if styles arg is invalid', () => {
expect(extractStylePseudos()).toEqual({});
});

it('should return pseudo styles from styles object', () => {
const styles = {
display: 'block',

'&:hover': {
color: 'red'
},

'&::-webkit-input-placeholder': {
color: 'hotpink'
}
};

expect(extractStylePseudos(styles)).toEqual({
'&:hover': {
color: 'red'
},

'&::-webkit-input-placeholder': {
color: 'hotpink'
}
});
});
});
19 changes: 19 additions & 0 deletions packages/component-generator/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,25 @@ export function BasicUsage() {
);
}

const {
Container: BasicContainer,
FlexContainer: BasicFlexContainer,
UnpaddedContainer: BasicUnpaddedContainer
} = createComponents<typeof containers.components>(
Basic,
containers.components
);

export function UsageWithSCbase() {
return (
<>
<BasicContainer>Default Container</BasicContainer>
<BasicFlexContainer>Flex Container</BasicFlexContainer>
<BasicUnpaddedContainer>Unpadded Container</BasicUnpaddedContainer>
</>
);
}

export default {
title: 'Generator'
};
3 changes: 2 additions & 1 deletion packages/component-generator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@
"styled-components": ">=5.2.1"
},
"dependencies": {
"lodash.isempty": "^4.4.0"
"lodash.isempty": "^4.4.0",
"lodash.isplainobject": "^4.0.6"
},
"resolutions": {
"react": ">=17.0.1"
Expand Down
85 changes: 52 additions & 33 deletions packages/component-generator/src/createComponents.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,72 @@
import React, { HTMLAttributes } from 'react';
import { createStyledComponent } from './createStyledComponent';
import { AllHTMLAttributes } from 'react';
import { isValidElementType } from 'react-is';
import isPlainObject from 'lodash.isplainobject';
import isEmpty from 'lodash.isempty';
import { createStyledComponent, createFC } from './createStyledComponent';

import {
Settings,
BaseProps,
GenericRecord,
StyledComponentConfig,
Variant
Variant,
ComponentsRecord
} from './typings';

/* eslint-disable @typescript-eslint/no-explicit-any */
export function createComponents<
Config = any,
ThemeType = any,
Props = Record<string, unknown>,
Element = HTMLDivElement
>(
base: StyledComponentConfig<Props, ThemeType, Element>,
settings: Settings
): GenericRecord<
Config,
React.FC<Props & BaseProps<ThemeType> & Variant<ThemeType>>
> & { Base: React.FC<Props & BaseProps<ThemeType> & Variant<ThemeType>> } {
const acc = {} as GenericRecord<
Config,
React.FC<Props & BaseProps<ThemeType>>
> & { Base: React.FC<Props & BaseProps<ThemeType> & Variant<ThemeType>> };

if (!!base && Object.keys(base).length >= 1) {
acc.Base = createStyledComponent(base);
base:
| StyledComponentConfig<Props, AllHTMLAttributes<Element>, ThemeType>
| React.FC<Props>,
settings: Settings<Element>
): ComponentsRecord<Config, Props, ThemeType> {
const dict = createBaseComponent<Config, ThemeType, Props>(base);
const isConfigBase = dict.hasOwnProperty('Base');

if ((isValidElementType(base) || !isEmpty(base)) && !isEmpty(settings)) {
return Object.entries(settings).reduce((dict, entry) => {
const [prop, setting] = entry;

if (hasKey(settings, prop)) {
dict[prop] = isConfigBase
? generateComponent<ThemeType, Props, Element>(base, setting)
: createFC(base, setting);
dict[prop].displayName = prop;
}
return dict;
}, dict);
}

return !!base && !!settings && Object.keys(settings).length >= 1
? Object.entries(settings).reduce((acc, entry) => {
const [prop, setting] = entry;
return dict;
}

export function createBaseComponent<
Config = any,
ThemeType = any,
Props = Record<string, unknown>
>(base): ComponentsRecord<Config, Props, ThemeType> {
const dict = {} as ComponentsRecord<Config, Props, ThemeType>;

if (hasKey(settings, prop)) {
acc[prop] = createStyledComponent({
...base,
styles: { ...base.styles, ...setting },
element: !!setting.as ? setting.as : base.element
} as StyledComponentConfig<Props & Variant<ThemeType>, ThemeType, HTMLAttributes<Element>>);
if (isPlainObject(base)) {
dict.Base = createStyledComponent(base);
}

acc[prop].displayName = prop;
}
return dict;
}

return acc;
}, acc)
: acc;
export function generateComponent<
ThemeType = any,
Props = Record<string, unknown>,
Element = HTMLDivElement
>(base, setting) {
return createStyledComponent({
...base,
styles: { ...base.styles, ...setting },
attrs: { ...base.attrs, ...setting.attrs } || {},
element: !!setting.as ? setting.as : base.element
} as StyledComponentConfig<Props & Variant<ThemeType>, AllHTMLAttributes<Element>, ThemeType>);
}

/* eslint-disable @typescript-eslint/no-explicit-any */
Expand Down
37 changes: 31 additions & 6 deletions packages/component-generator/src/createStyledComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import isEmpty from 'lodash.isempty';
import styled from 'styled-components';
import styled, { CSSObject } from 'styled-components';

import {
compose,
Expand All @@ -15,7 +15,7 @@ import {
typography
} from 'styled-system';

import { StyledComponentConfig } from './typings';
import { StyledComponentConfig, StyledSystemCSSObject } from './typings';

const pipe = (...fns) => value => fns.reduce((acc, fn) => fn(acc), value);

Expand All @@ -25,16 +25,25 @@ export function createStyledComponent<
Theme = void,
Attributes = void
>(config: StyledComponentConfig<Props, Theme, Attributes>): React.FC<Props> {
const Styled = pipe(
const Styled = createStyled<Props, Theme, Attributes>(config);
return createFC<Props>(Styled, config.styles);
}

export function createStyled<Props = void, Theme = void, Attributes = void>(
config: StyledComponentConfig<Props, Theme, Attributes>
) {
return pipe(
createStyledElement,
createStyledAttributes,
applyStyledProps
)(config);
}

export function createFC<Props = void>(Component, styles): React.FC<Props> {
return props => (
<Styled {...props} {...config.styles}>
<Component {...props} {...styles}>
{props.children}
</Styled>
</Component>
);
}

Expand Down Expand Up @@ -72,9 +81,13 @@ export function applyStyledProps<
Attributes = void
>({
component = styled.div,
styleProps = []
styleProps = [],
styles
}: StyledComponentConfig<Props, Theme, Attributes>) {
const pseudoStyles = extractStylePseudos(styles);

return component(
pseudoStyles,
compose(
background,
border,
Expand All @@ -89,3 +102,15 @@ export function applyStyledProps<
)
);
}

export function extractStylePseudos(
styles: StyledSystemCSSObject = {}
): CSSObject {
return Object.keys(styles).reduce((acc, key) => {
if (key.includes('&:')) {
acc[key] = styles[key];
}

return acc;
}, {});
}
7 changes: 5 additions & 2 deletions packages/component-generator/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
export { createComponents } from './createComponents';
export { createStyledComponent } from './createStyledComponent';
export {
createStyledComponent,
extractStylePseudos
} from './createStyledComponent';
export {
Attrs,
BaseProps,
BaseComponentProps,
GenericRecord,
ComponentsRecord,
Settings,
StyledComponentConfig,
Variant
Expand Down
16 changes: 11 additions & 5 deletions packages/component-generator/src/typings.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AllHTMLAttributes } from 'react';
import * as CSS from 'csstype';
import { ThemedStyledFunction, ThemedStyledProps } from 'styled-components';
import { styleFn, Scale } from 'styled-system';
Expand Down Expand Up @@ -43,7 +44,7 @@ export type StyledComponentConfig<
ThemeType = void
> = {
styles?: StyledSystemCSSObject;
attrs?: Attrs<Props, Attributes, ThemeType>;
attrs?: Attributes;
styleProps?: styleFn[];
element?: keyof JSX.IntrinsicElements;
component?: ThemedStyledFunction<
Expand All @@ -58,10 +59,15 @@ export type CSSObjectWithScale = CSS.Properties<string | number | Scale>;
export type CSSPseudos = { [K in CSS.Pseudos]?: CSSObjectWithScale };
export type StyledSystemCSSObject = CSSObjectWithScale & CSSPseudos;

export type GenericRecord<Dict, Type> = {
[key in keyof Dict]: Type;
export type ComponentsRecord<Dict, Props, ThemeType> = {
[key in keyof Dict | 'Base']: React.FC<
Props & BaseProps<ThemeType> & Variant<ThemeType>
>;
};

export type Settings = {
[key: string]: StyledSystemCSSObject & { as?: string };
export type Settings<Element = HTMLDivElement> = {
[key: string]: StyledSystemCSSObject & {
as?: string;
attrs?: AllHTMLAttributes<Element>;
};
};
11 changes: 11 additions & 0 deletions packages/text-input/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# `@artifak/text-input`

> TODO: description
## Usage

```
const textInput = require('@artifak/text-input');
// TODO: DEMONSTRATE API
```
39 changes: 39 additions & 0 deletions packages/text-input/__tests__/createTextInputs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { createTextInputs } from '../src';

describe('createTypography - generates typography React components based on styles object argument provided', () => {
it('should return the base Typography system component when provided with an invalid argument', () => {
expect(createTextInputs({} as any, {} as any)).toHaveProperty('Base');
expect(createTextInputs(null as any, null as any)).toHaveProperty('Base');
expect(createTextInputs(false as any, false as any)).toHaveProperty('Base');
expect(createTextInputs(0 as any, 0 as any)).toHaveProperty('Base');
expect(createTextInputs(void 0 as any, void 0 as any)).toHaveProperty(
'Base'
);
});

it('should create React components', () => {
const inputs = {
base: {
styles: {
width: '100%',
border: '1px solid black',
padding: ['1em']
}
},

components: {
InputSearch: {
maxWidth: '300px',
attrs: { type: 'search' }
},

InputUrl: {
display: 'block',
attrs: { type: 'url' }
}
}
};

expect(createTextInputs(inputs.base, inputs.components)).toBeDefined();
});
});
Loading

0 comments on commit 111f533

Please sign in to comment.