Skip to content

Commit

Permalink
refactor(typescript): convert Grid, FlexGrid and children to TypeScri…
Browse files Browse the repository at this point in the history
…pt (#13074)

* refactor(typescript): convert Grid, FlexGrid and children to TypeScript

* fix(Grid): restore span attribute support

* fix(typescript): use PolymorphicProps
  • Loading branch information
lewandom committed Feb 16, 2023
1 parent aefd152 commit e4e1c0f
Show file tree
Hide file tree
Showing 10 changed files with 406 additions and 127 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright IBM Corp. 2016, 2018
* Copyright IBM Corp. 2016, 2023
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
Expand All @@ -9,20 +9,22 @@ import cx from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import { usePrefix } from '../../internal/usePrefix';
import { PolymorphicProps } from '../../types/common';
import { GridSettings, useGridSettings } from './GridContext';
import { GridComponent, GridProps } from './GridTypes';

function CSSGrid({
as: BaseComponent = 'div',
function CSSGrid<T extends React.ElementType>({
as: BaseComponent = 'div' as T,
children,
className: customClassName,
condensed = false,
fullWidth = false,
narrow = false,
...rest
}) {
}: GridProps<T>) {
const prefix = usePrefix();
const { subgrid } = useGridSettings();
let mode = 'wide';
let mode: SubgridMode = 'wide';
if (narrow) {
mode = 'narrow';
} else if (condensed) {
Expand All @@ -36,7 +38,8 @@ function CSSGrid({
as={BaseComponent}
className={customClassName}
mode={mode}
{...rest}>
{...rest}
>
{children}
</Subgrid>
</GridSettings>
Expand All @@ -50,11 +53,13 @@ function CSSGrid({
[`${prefix}--css-grid--full-width`]: fullWidth,
});

// cast as any to let TypeScript allow passing in attributes to base component
const BaseComponentAsAny: any = BaseComponent
return (
<GridSettings mode="css-grid" subgrid>
<BaseComponent className={className} {...rest}>
<BaseComponentAsAny className={className} {...rest}>
{children}
</BaseComponent>
</BaseComponentAsAny>
</GridSettings>
);
}
Expand Down Expand Up @@ -93,13 +98,36 @@ CSSGrid.propTypes = {
narrow: PropTypes.bool,
};

function Subgrid({
type SubgridMode = 'wide' | 'narrow' | 'condensed'

interface SubgridBaseProps {

/**
* Pass in content that will be rendered within the `Subgrid`
*/
children?: React.ReactNode;

/**
* Specify a custom className to be applied to the `Subgrid`
*/
className?: string;

/**
* Specify the grid mode for the subgrid
*/
mode?: SubgridMode;

}

type SubgridProps = PolymorphicProps<any, SubgridBaseProps>

const Subgrid = ({
as: BaseComponent = 'div',
className: customClassName,
children,
mode,
...rest
}) {
}: SubgridProps) => {
const prefix = usePrefix();
const className = cx(customClassName, {
[`${prefix}--subgrid`]: true,
Expand Down Expand Up @@ -133,7 +161,9 @@ Subgrid.propTypes = {
/**
* Specify the grid mode for the subgrid
*/
mode: PropTypes.oneOf(['wide', 'narrow', 'condensed']),
mode: PropTypes.oneOf(['wide', 'narrow', 'condensed'] as SubgridMode[]),
};

export { CSSGrid };
const CSSGridComponent: GridComponent = CSSGrid;

export { CSSGridComponent as CSSGrid };
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright IBM Corp. 2016, 2018
* Copyright IBM Corp. 2016, 2023
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
Expand All @@ -10,10 +10,95 @@ import cx from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import { usePrefix } from '../../internal/usePrefix';
import { PolymorphicProps } from '../../types/common';
import { useGridSettings } from './GridContext';

function Column({
as: BaseComponent = 'div',
type ColumnSpanPercent = '25%' | '50%' | '75%' | '100%';

type ColumnSpanSimple = boolean | number | ColumnSpanPercent

interface ColumnSpanObject {

span?: ColumnSpanSimple;

offset?: number;

start?: number;

end?: number;

}

export type ColumnSpan = ColumnSpanSimple | ColumnSpanObject

interface ColumnBaseProps {

/**
* Pass in content that will be rendered within the `Column`
*/
children?: React.ReactNode;

/**
* Specify a custom className to be applied to the `Column`
*/
className?: string,

/**
* Specify column span for the `lg` breakpoint (Default breakpoint up to 1312px)
* This breakpoint supports 16 columns by default.
*
* @see https://www.carbondesignsystem.com/guidelines/layout#breakpoints
*/
lg?: ColumnSpan;

/**
* Specify column span for the `max` breakpoint. This breakpoint supports 16
* columns by default.
*
* @see https://www.carbondesignsystem.com/guidelines/layout#breakpoints
*/
max?: ColumnSpan;

/**
* Specify column span for the `md` breakpoint (Default breakpoint up to 1056px)
* This breakpoint supports 8 columns by default.
*
* @see https://www.carbondesignsystem.com/guidelines/layout#breakpoints
*/
md?: ColumnSpan;

/**
* Specify column span for the `sm` breakpoint (Default breakpoint up to 672px)
* This breakpoint supports 4 columns by default.
*
* @see https://www.carbondesignsystem.com/guidelines/layout#breakpoints
*/
sm?: ColumnSpan;

/**
* Specify column span for the `xlg` breakpoint (Default breakpoint up to
* 1584px) This breakpoint supports 16 columns by default.
*
* @see https://www.carbondesignsystem.com/guidelines/layout#breakpoints
*/
xlg?: ColumnSpan;

/**
* Specify constant column span, start, or end values that will not change
* based on breakpoint
*/
span?: ColumnSpan;

}

export type ColumnProps<T extends React.ElementType> = PolymorphicProps<T, ColumnBaseProps>;

export interface ColumnComponent {
<T extends React.ElementType>(props: ColumnProps<T>, context?: any): React.ReactElement<any, any> | null;
}

function Column<T extends React.ElementType>({
as: BaseComponent = 'div' as T,
children,
className: customClassName,
sm,
Expand All @@ -22,7 +107,7 @@ function Column({
xlg,
max,
...rest
}) {
}: ColumnProps<T>) {
const { mode } = useGridSettings();
const prefix = usePrefix();

Expand Down Expand Up @@ -50,10 +135,12 @@ function Column({
[`${prefix}--col`]: columnClassName.length === 0,
});

// cast as any to let TypeScript allow passing in attributes to base component
const BaseComponentAsAny: any = BaseComponent
return (
<BaseComponent className={className} {...rest}>
<BaseComponentAsAny className={className} {...rest}>
{children}
</BaseComponent>
</BaseComponentAsAny>
);
}

Expand Down Expand Up @@ -148,7 +235,7 @@ function CSSGridColumn({
max,
span,
...rest
}) {
}: ColumnProps<any>) {
const prefix = usePrefix();
const breakpointClassName = getClassNameForBreakpoints(
[sm, md, lg, xlg, max],
Expand Down Expand Up @@ -250,8 +337,8 @@ const breakpointNames = ['sm', 'md', 'lg', 'xlg', 'max'];
* @param {Array<boolean|number|Breakpoint>} breakpoints
* @returns {string}
*/
function getClassNameForBreakpoints(breakpoints, prefix) {
const classNames = [];
function getClassNameForBreakpoints(breakpoints: (ColumnSpan | undefined)[], prefix: string): string {
const classNames: string[] = [];

for (let i = 0; i < breakpoints.length; i++) {
const breakpoint = breakpoints[i];
Expand Down Expand Up @@ -282,25 +369,27 @@ function getClassNameForBreakpoints(breakpoints, prefix) {
continue;
}

const { span, offset, start, end } = breakpoint;

if (typeof offset === 'number' && offset > 0) {
classNames.push(`${prefix}--${name}:col-start-${offset + 1}`);
}

if (typeof start === 'number') {
classNames.push(`${prefix}--${name}:col-start-${start}`);
}

if (typeof end === 'number') {
classNames.push(`${prefix}--${name}:col-end-${end}`);
}

if (typeof span === 'number') {
classNames.push(`${prefix}--${name}:col-span-${span}`);
} else if (typeof span === 'string') {
classNames.push(`${prefix}--${name}:col-span-${span.slice(0, -1)}`);
continue;
if (typeof breakpoint === 'object') {
const { span, offset, start, end } = breakpoint;

if (typeof offset === 'number' && offset > 0) {
classNames.push(`${prefix}--${name}:col-start-${offset + 1}`);
}

if (typeof start === 'number') {
classNames.push(`${prefix}--${name}:col-start-${start}`);
}

if (typeof end === 'number') {
classNames.push(`${prefix}--${name}:col-end-${end}`);
}

if (typeof span === 'number') {
classNames.push(`${prefix}--${name}:col-span-${span}`);
} else if (typeof span === 'string') {
classNames.push(`${prefix}--${name}:col-span-${span.slice(0, -1)}`);
continue;
}
}
}

Expand All @@ -312,8 +401,8 @@ function getClassNameForBreakpoints(breakpoints, prefix) {
* @param {Array<boolean|number|Breakpoint>} breakpoints
* @returns {string}
*/
function getClassNameForFlexGridBreakpoints(breakpoints, prefix) {
const classNames = [];
function getClassNameForFlexGridBreakpoints(breakpoints: (ColumnSpan | undefined)[], prefix: string): string {
const classNames: string[] = [];

for (let i = 0; i < breakpoints.length; i++) {
const breakpoint = breakpoints[i];
Expand All @@ -337,17 +426,19 @@ function getClassNameForFlexGridBreakpoints(breakpoints, prefix) {
continue;
}

const { span, offset } = breakpoint;
if (typeof span === 'number') {
classNames.push(`${prefix}--col-${name}-${span}`);
}
if (typeof breakpoint === 'object') {
const { span, offset } = breakpoint;
if (typeof span === 'number') {
classNames.push(`${prefix}--col-${name}-${span}`);
}

if (span === true) {
classNames.push(`${prefix}--col-${name}`);
}
if (span === true) {
classNames.push(`${prefix}--col-${name}`);
}

if (typeof offset === 'number') {
classNames.push(`${prefix}--offset-${name}-${offset}`);
if (typeof offset === 'number') {
classNames.push(`${prefix}--offset-${name}-${offset}`);
}
}
}

Expand All @@ -357,8 +448,8 @@ function getClassNameForFlexGridBreakpoints(breakpoints, prefix) {
/**
* Build the appropriate className for a span value
*/
function getClassNameForSpan(value, prefix) {
const classNames = [];
function getClassNameForSpan(value: ColumnSpan | undefined, prefix: string): string {
const classNames: string[] = [];

if (typeof value === 'number' || typeof value === 'string') {
classNames.push(`${prefix}--col-span-${value}`);
Expand All @@ -381,4 +472,4 @@ function getClassNameForSpan(value, prefix) {
return classNames.join('');
}

export default Column;
export default Column as ColumnComponent;
Loading

0 comments on commit e4e1c0f

Please sign in to comment.