Skip to content

Commit

Permalink
Merge pull request #228 from j-f1/add-types
Browse files Browse the repository at this point in the history
Add TypeScript types to the React integration
  • Loading branch information
shawnbot authored Aug 27, 2018
2 parents 470a8b8 + 70983ec commit cbf33c9
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 4 deletions.
10 changes: 7 additions & 3 deletions lib/octicons_react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
"license": "MIT",
"main": "dist/index.umd.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"repository": "primer/octicons",
"scripts": {
"pretest": "npm run lint",
"pretest": "npm run lint && npm run ts-test",
"ts-test": "tsc -P ts-tests",
"test": "jest",
"start": "NODE_ENV=production next",
"lint": "eslint src pages script",
"prepare": "script/build.js && npm run rollup",
"prepare": "script/build.js && script/types.js && npm run rollup",
"prepublishOnly": "../../script/notify pending",
"preversion": "npm run prepare",
"publish": "../../script/notify success",
Expand All @@ -31,6 +33,7 @@
"prop-types": "^15.6.1"
},
"devDependencies": {
"@types/react": "^16.4.6",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
Expand All @@ -47,7 +50,8 @@
"react-test-renderer": "^16.4.1",
"rollup": "^0.62.0",
"rollup-plugin-babel": "^3.0.5",
"rollup-plugin-commonjs": "^9.1.3"
"rollup-plugin-commonjs": "^9.1.3",
"typescript": "^2.9.2"
},
"peerDependencies": {
"react": ">=15"
Expand Down
42 changes: 41 additions & 1 deletion lib/octicons_react/script/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@ const {join, resolve} = require('path')

const srcDir = resolve(__dirname, '../src/__generated__')
const iconsFile = join(srcDir, 'icons.js')
const typesFile = join(srcDir, 'icons.d.ts')

function CamelCase(str) {
return str.replace(/(^|-)([a-z])/g, (_, __, c) => c.toUpperCase())
}

const icons = [...Object.entries(octicons)]
const octiconNames = [...Object.entries(octicons)]

const icons = octiconNames
.map(([key, octicon]) => {
const name = CamelCase(key)
const {width, height, path} = octicon
// convert attributes like fill-rule into JSX equivalents, e.g. fillRule
const svg = path.replace(/([a-z]+)-([a-z]+)=/g, (_, a, b) => `${a}${CamelCase(b)}=`)
const type = `Icon<${width}, ${height}>`
const code = `function ${name}() {
return ${svg}
}
Expand All @@ -26,6 +30,7 @@ ${name}.size = [${width}, ${height}]
key,
name,
octicon,
type,
code
}
})
Expand Down Expand Up @@ -57,9 +62,44 @@ export {
})
}

function writeTypes(file) {
const count = icons.length
const code = `/* THIS FILE IS GENERATED. DO NOT EDIT IT. */
import * as React from 'react'
type Icon<
W extends number = number,
H extends number = number
> = React.SFC<{}> & { size: [W, H] };
${icons.map(({name, type}) => `declare const ${name}: ${type}`).join('\n')}
type iconsByName = {
${icons.map(({key, type}) => `'${key}': ${type}`).join(',\n ')}
}
declare const iconsByName: iconsByName
declare function getIconByName<T extends keyof iconsByName>(
name: T
): IconsByName[T];
declare function getIconByName(name: string): Icon | undefined
export {
Icon,
getIconByName,
iconsByName,
${icons.map(({name}) => name).join(',\n ')}
}`
return fse.writeFile(file, code, 'utf8').then(() => {
console.warn('wrote %s with %d exports', file, count)
return icons
})
}

fse
.mkdirs(srcDir)
.then(() => writeIcons(iconsFile))
.then(() => writeTypes(typesFile))
.catch(error => {
console.error(error)
process.exit(1)
Expand Down
23 changes: 23 additions & 0 deletions lib/octicons_react/script/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env node
const fse = require('fs-extra')
const {join, resolve} = require('path')

const srcDir = resolve(__dirname, '../src/__generated__')
const iconsSrc = join(srcDir, 'icons.d.ts')
const indexSrc = join(srcDir, '../index.d.ts')

const destDir = resolve(__dirname, '../dist')
const iconsDest = join(destDir, 'icons.d.ts')
const indexDest = join(destDir, 'index.d.ts')

fse.copy(iconsSrc, iconsDest).catch(die)
fse
.readFile(indexSrc, 'utf8')
.then(content => content.replace(/.\/__generated__\//g, './'))
.then(fse.writeFile.bind(fse, indexDest))
.catch(die)

function die(err) {
console.error(err.stack)
process.exit(1)
}
24 changes: 24 additions & 0 deletions lib/octicons_react/src/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from 'react'

import {Icon} from './__generated__/icons'

type Size = 'small' | 'medium' | 'large'
export interface OcticonProps {
ariaLabel?: string
children?: React.ReactElement<any>
height?: number
icon: Icon
size?: number | Size
verticalAlign?: 'middle' | 'text-bottom' | 'text-top' | 'top'
width?: number
}

declare const Octicon: React.SFC<OcticonProps>
export default Octicon

export function createIcon<C extends React.SFC<{}>, W extends number, H extends number>(
component: C,
size: [W, H]
): Icon<W, H>

export * from './__generated__/icons'
6 changes: 6 additions & 0 deletions lib/octicons_react/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,10 @@ Octicon.propTypes = {
width: PropTypes.number
}

// Helper since TS makes this painful
export function createIcon(component, size) {
component.size = size
return component
}

export * from './__generated__/icons'
67 changes: 67 additions & 0 deletions lib/octicons_react/ts-tests/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as React from 'react'
import Octicon, {
OcticonProps,
Beaker,
Zap,
Repo,
Plus,
LogoGithub,
getIconByName,
iconsByName,
createIcon
} from '../src'

function Icon({boom}: {boom: boolean}): React.ReactNode {
return <Octicon icon={boom ? Zap : Beaker} />
}

function OcticonByName({name, ...props}: {name: keyof iconsByName} & OcticonProps): React.ReactNode {
return <Octicon {...props} icon={getIconByName(name)} />
}

// Unfortunately, `Object.keys` returns `string[]` unconditionally;
// see https://github.com/Microsoft/TypeScript/pull/13971 &
// https://github.com/Microsoft/TypeScript/issues/12870 for details.
function keys<T>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[]
}

function OcticonsList() {
return (
<ul>
{keys(iconsByName).map(key => (
<li key={key}>
<code>{key}</code>
<Octicon icon={iconsByName[key]} />
</li>
))}
</ul>
)
}

function VerticalAlign() {
return (
<h1>
<Octicon icon={Repo} size="large" verticalAlign="middle" /> github/github
<Octicon icon={Plus} ariaLabel="Add new item" /> New
<Octicon icon={LogoGithub} size="large" ariaLabel="GitHub" />
</h1>
)
}

const CirclesIcon = createIcon(
() => {
return (
<React.Fragment>
<circle r={5} cx={5} cy={5} />
<circle r={5} cx={15} cy={5} />
<circle r={5} cx={25} cy={5} />
</React.Fragment>
)
},
[30, 10]
)

export function CirclesOcticon(props: OcticonProps) {
return <Octicon {...props} icon={CirclesIcon} />
}
12 changes: 12 additions & 0 deletions lib/octicons_react/ts-tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"compileOnSave": false,
"compilerOptions": {
"module": "commonjs",
"noEmit": true,
"noImplicitAny": true,
"jsx": "react",
"lib": ["es2015"]
},
"files": ["../src/index.d.ts", "./index.tsx"]
}

0 comments on commit cbf33c9

Please sign in to comment.