Skip to content

Instantly share code, notes, and snippets.

@jesstelford
Last active July 9, 2020 12:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jesstelford/2850962727b94c4e093e18dd68204321 to your computer and use it in GitHub Desktop.
Save jesstelford/2850962727b94c4e093e18dd68204321 to your computer and use it in GitHub Desktop.
A fork of Capsize used to generate a typographic scale (https://github.com/seek-oss/capsize)

A fork of Capsize used to generate a typographic scale (https://github.com/seek-oss/capsize)

Generate the typographic scale by running generate-typographic-scale.js:

node generate-typographic-scale.js

Add it to your Chakra UI theme under the key typography:

{
  "typography": {
    "sm": { ... },
  }
}

Then render the text.js component.

// Modified from https://github.com/seek-oss/capsize/blob/47ad276443609559096f4d95d45de387f97a037b/packages/capsize/src/index.ts
const preventCollapse = 1;
module.exports = function capsize({
leading,
gap,
capHeight,
fontSize,
fontMetrics,
}) {
if (typeof leading !== 'undefined' && typeof gap !== 'undefined') {
throw new Error(
'Only a single line height style can be provided. Please pass either `gap` OR `leading`.'
);
}
if (typeof capHeight !== 'undefined' && typeof fontSize !== 'undefined') {
throw new Error('Please pass either `capHeight` OR `fontSize`, not both.');
}
const capHeightScale = fontMetrics.capHeight / fontMetrics.unitsPerEm;
let specifiedFontSize;
let specifiedCapHeight;
if (typeof fontSize !== 'undefined') {
specifiedFontSize = fontSize;
specifiedCapHeight = fontSize * capHeightScale;
} else {
specifiedFontSize = capHeight / capHeightScale;
specifiedCapHeight = capHeight;
}
const specifiedLineHeight =
typeof gap !== 'undefined' ? specifiedCapHeight + gap : leading;
return createCss({
specifiedLineHeight,
fontSize: specifiedFontSize,
fontMetrics,
});
};
function createCss({ specifiedLineHeight, fontSize, fontMetrics }) {
const capHeightScale = fontMetrics.capHeight / fontMetrics.unitsPerEm;
const toScale = (value) => value / fontSize;
const absoluteDescent = Math.abs(fontMetrics.descent);
const descentScale = absoluteDescent / fontMetrics.unitsPerEm;
const ascentScale = fontMetrics.ascent / fontMetrics.unitsPerEm;
const contentArea = fontMetrics.ascent + absoluteDescent;
const lineHeight = contentArea + fontMetrics.lineGap;
const lineHeightScale = lineHeight / fontMetrics.unitsPerEm;
const lineHeightNormal = lineHeightScale * fontSize;
const hasSpecifiedLineHeight = typeof specifiedLineHeight !== 'undefined';
const specifiedLineHeightOffset =
hasSpecifiedLineHeight && typeof specifiedLineHeight === 'number'
? (lineHeightNormal - specifiedLineHeight) / 2
: 0;
const leadingTrim = (value) =>
value - toScale(specifiedLineHeightOffset) + toScale(preventCollapse);
return {
fontSize: `${fontSize}px`,
...(hasSpecifiedLineHeight && { lineHeight: `${specifiedLineHeight}px` }),
paddingTop: `${preventCollapse}px`,
paddingBottom: `${preventCollapse}px`,
':before': {
content: "''",
marginTop: `-${leadingTrim(ascentScale - capHeightScale)}em`,
display: 'block',
height: 0,
},
':after': {
content: "''",
marginBottom: `-${leadingTrim(descentScale)}em`,
display: 'block',
height: 0,
},
};
}
const { diff } = require('deep-object-diff');
const capsize = require('./capsize');
const rubikFont = {
ascent: 935,
capHeight: 700,
descent: -250,
familyName: 'Rubik',
lineGap: 0,
subfamilyName: 'Regular',
unitsPerEm: 1000,
xHeight: 520,
};
const sizes = {
xs: { fontSize: 10, lineHeight: 18 },
sm: { fontSize: 12, lineHeight: 20 },
md: { fontSize: 14, lineHeight: 22 },
lg: { fontSize: 16, lineHeight: 24 },
xl: { fontSize: 20, lineHeight: 28 },
'2xl': { fontSize: 24, lineHeight: 32 },
'3xl': { fontSize: 30, lineHeight: 38 },
'4xl': { fontSize: 38, lineHeight: 46 },
'5xl': { fontSize: 46, lineHeight: 54 },
'6xl': { fontSize: 56, lineHeight: 64 },
};
const result = {};
for (const size in sizes) {
const none = capsize({
fontSize: sizes[size].fontSize,
gap: 0,
fontMetrics: rubikFont,
});
const noneLineHeight = parseFloat(none.lineHeight);
const lineHeightStep = (sizes[size].lineHeight - noneLineHeight) / 3;
const shortLineHeight = noneLineHeight + lineHeightStep;
const tallLineHeight = sizes[size].lineHeight + lineHeightStep * 3;
const short = capsize({
fontSize: sizes[size].fontSize,
leading: shortLineHeight,
fontMetrics: rubikFont,
});
const base = capsize({
fontSize: sizes[size].fontSize,
leading: sizes[size].lineHeight,
fontMetrics: rubikFont,
});
const tall = capsize({
fontSize: sizes[size].fontSize,
leading: tallLineHeight,
fontMetrics: rubikFont,
});
result[size] = {
base: none,
leading: {
short: diff(none, short),
base: diff(none, base),
tall: diff(none, tall),
},
};
}
console.log(JSON.stringify(result, null, 2));
/** @jsx jsx */
import { jsx } from '@emotion/core';
import { useTheme, Box } from '@chakra-ui/core';
import propTypes from 'prop-types';
import merge from 'lodash/merge';
const getTextStyles = ({ theme, size, leading }) => {
if (leading === 'none') {
return theme.typography[size].base;
}
return merge(
{},
theme.typography[size].base,
theme.typography[size].leading[leading]
);
};
const Text = ({ size = 'md', leading = 'base', ...props }) => {
const theme = useTheme();
const styles = getTextStyles({ theme, size, leading });
return <Box as="p" {...props} css={styles} />;
};
Text.displayName = 'Text';
Text.propTypes = {
size: propTypes.oneOf([
'xs',
'sm',
'md',
'lg',
'xl',
'2xl',
'3xl',
'4xl',
'5xl',
]),
leading: propTypes.oneOf(['none', 'short', 'base', 'tall']),
};
export { Text };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment