Skip to content
This repository has been archived by the owner on Aug 21, 2023. It is now read-only.

Commit

Permalink
Merge branch 'main' into feature/avatarrow-minwidth
Browse files Browse the repository at this point in the history
  • Loading branch information
tinkertrain committed Jan 17, 2022
2 parents dc36c1a + 194c6d5 commit 9e1a068
Show file tree
Hide file tree
Showing 89 changed files with 2,722 additions and 2,718 deletions.
834 changes: 212 additions & 622 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@helpscout/hsds-react",
"version": "3.32.0",
"version": "3.33.0",
"private": false,
"main": "dist/index.js",
"module": "dist/index.es.js",
Expand Down Expand Up @@ -95,15 +95,22 @@
"@helpscout/wedux": "0.0.11",
"@tippyjs/react": "^4.0.2",
"array-move": "2.1.0",
"chalk": "^1.1.3",
"classnames": "^2.3.1",
"compute-scroll-into-view": "1.0.11",
"dash-get": "1.0.2",
"downshift": "^6.1.1",
"execa": "^4.0.0",
"fast-deep-equal": "^2.0.1",
"jscodeshift": "^0.7.0",
"globby": "^11.0.0",
"inquirer": "^7.0.4",
"is-git-clean": "^1.1.0",
"invariant": "2.2.4",
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"lodash.throttle": "^4.1.1",
"meow": "^6.0.1",
"path-to-regexp": "2.4.0",
"prismjs": "^1.25.0",
"react-frame-component": "4.1.1",
Expand All @@ -121,7 +128,7 @@
"@babel/plugin-transform-runtime": "^7.4.3",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@helpscout/colorway": "0.9.7",
"@helpscout/colorway": "0.10.0",
"@helpscout/helix": "0.2.0",
"@helpscout/prestart": "^0.0.9",
"@storybook/addon-a11y": "6.3.12",
Expand All @@ -139,6 +146,7 @@
"@testing-library/user-event": "^12.1.5",
"@types/enzyme": "^3.1.14",
"@types/jest": "^26.0.4",
"@types/jscodeshift": "^0.11.3",
"@types/react": "^16.4.14",
"@types/react-dom": "^16.0.8",
"@types/react-transition-group": "^2.0.14",
Expand All @@ -150,7 +158,6 @@
"babel-plugin-prismjs": "^2.0.1",
"babel-plugin-styled-components": "^1.10.6",
"babel-plugin-tester": "^9.2.0",
"chalk": "^1.1.3",
"confusing-browser-globals": "^1.0.7",
"coveralls": "3.0.9",
"cross-env": "5.1.0",
Expand All @@ -166,17 +173,11 @@
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.18.3",
"eslint-plugin-react-hooks": "^4.1.0",
"execa": "^4.0.0",
"fs-extra": "^3.0.1",
"glob": "^7.1.3",
"globby": "^11.0.0",
"husky": "4.2.5",
"inquirer": "^7.0.4",
"is-git-clean": "^1.1.0",
"jest": "^26.1.0",
"jest-watch-typeahead": "^0.3.0",
"jscodeshift": "^0.7.0",
"meow": "^6.0.1",
"np": "^7.5.0",
"nyc": "^15.0.0",
"prettier": "2.0.5",
Expand Down
8 changes: 6 additions & 2 deletions scripts/codemod/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ const TRANSFORMER_INQUIRER_CHOICES = [
'3.0 ReplaceImports: Replace all hsds-react import with the next release',
value: 'ReplaceImportsTransform',
},
{
name: '3.31 Button: Button standardization',
value: 'ButtonStandardizationTransform',
},
]

function expandFilePathsIfNeeded(filesBeforeExpansion) {
Expand Down Expand Up @@ -225,7 +229,7 @@ function run() {
type: 'input',
name: 'moduleName',
message: 'Enter the original package name',
when: function(answers) {
when: function (answers) {
return (
answers.transformer === 'all' ||
answers.transformer === 'ReplaceImportsTransform'
Expand All @@ -237,7 +241,7 @@ function run() {
type: 'input',
name: 'moduleNameTarget',
message: 'Enter the new package name',
when: function(answers) {
when: function (answers) {
const isReplaceImports =
answers.transformer === 'all' ||
answers.transformer === 'ReplaceImportsTransform'
Expand Down
236 changes: 236 additions & 0 deletions scripts/codemod/transforms/ButtonStandardizationTransform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import { API, FileInfo } from 'jscodeshift'

/**
*
* @param {FileInfo} fileInfo
* @param {API} api
* @returns
*/
export default function buttonStandardizationTransform(fileInfo, api) {
const j = api.jscodeshift
const root = j(fileInfo.source)
const isCss = fileInfo.path.endsWith('.css.js')

if (
fileInfo.path.endsWith('.test.js') ||
fileInfo.path.includes('.stories') ||
fileInfo.path.includes('components/Button/Button.jsx') ||
fileInfo.path.includes('components/Button/Button.css.js')
)
return root.toSource()

const processClassnames = p => {
const { node } = p

;[
{ before: 'is-default', after: 'is-default-to-refactor' },
{ before: 'is-xxl', after: 'is-size-xxl' },
{ before: 'is-xl', after: 'is-size-xl' },
{ before: 'is-lg', after: 'is-size-lg' },
{ before: 'is-md', after: 'is-size-md' },
{ before: 'is-sm', after: 'is-size-sm' },
{ before: 'is-xs', after: 'is-size-xs' },
{ before: 'is-xxs', after: 'is-size-xxs' },
{ before: 'is-primary', after: 'is-theme-blue' },
{ before: 'is-secondary', after: 'is-theme-grey' },
{ before: 'is-success', after: 'is-theme-green' },
{ before: 'is-danger', after: 'is-theme-red' },
].forEach(({ before, after }) => {
const quasis = node.quasis.map(q =>
j.templateElement(
{
cooked: q.value.cooked.replace(before, after),
raw: q.value.raw.replace(before, after),
},
false
)
)
node.quasis = quasis
})

return node
}

const getAttribute = (attributes, name) => {
const attr = attributes.find(a => {
if (!a || !a.name || !a.name.name) return null
return a.name.name === name
})
if (!attr) return { value: { value: null, expression: null } }

return { name: attr.name.name, value: attr.value }
}

const filterAttributes = (attributes, extraAttrsToRemoves = []) => {
const toRemove = [
'allowContentEventPropagation',
'disableOnLoading',
'fetch',
'spinButtonOnLoading',
'canRenderFocus',
'isBlock',
...extraAttrsToRemoves,
]

return attributes.filter(a => {
if (!a || !a.name || !a.name.name) return true
return !toRemove.includes(a.name.name)
})
}

const createAttribute = (name, value) => {
if (value === true) {
return j.jsxAttribute(j.jsxIdentifier(name), null)
} else if (value === false) {
j.jsxAttribute(
j.jsxIdentifier(name),
j.jsxExpressionContainer({
type: 'Literal',
value: false,
})
)
}

if (typeof value === 'string') {
return j.jsxAttribute(j.jsxIdentifier(name), j.literal(value))
}

return j.jsxAttribute(j.jsxIdentifier(name), value)
}

const renameAttribute = (attributes, before, after) => {
return attributes.map(a => {
if (a && a.name && a.name.name && a.name.name === before) {
a.name.name = after
}
return a
})
}

const processAttributes = jSXElement => {
const { attributes } = jSXElement
const { value: kind } = getAttribute(attributes, 'kind')
const { value: state } = getAttribute(attributes, 'state')
const { value: size } = getAttribute(attributes, 'size')
const { value: shape } = getAttribute(attributes, 'shape')
const { value: theme } = getAttribute(attributes, 'theme')

const toValidateAttr = createAttribute('data-button-tovalidate', true)
const themeDefaultAttr = createAttribute('theme', 'blue')
const sizeDefaultAttr = createAttribute('size', 'lg')

const outlinedAttr = createAttribute('outlined', true)
const roundedAttr = createAttribute('rounded', true)
const linkedAttr = createAttribute('linked', true)

const extraAttrsToRemoves = [
'kind',
'size',
'state',
'shape',
'withCaret',
'isBorderless',
'iconSize',
]

// if theme exists, do nothing
if (theme.value) return attributes
// if there is no attribute at all, we'll add a data-tovalidate to list the button as a change and let developer decided what they need to do
if (!kind.value && !state.value && !size.value && !size.expression) {
return [
toValidateAttr,
themeDefaultAttr,
sizeDefaultAttr,
linkedAttr,
...filterAttributes(attributes, extraAttrsToRemoves),
]
}

const nextAttributes = []
if (size.value === 'xl' || size.value === 'lgxl') {
nextAttributes.push(createAttribute('size', 'xxl'))
} else if (size.value || size.expression) {
nextAttributes.push(createAttribute('size', size))
}

if (state.value === 'danger') {
nextAttributes.push(createAttribute('theme', 'red'))
}
if (state.value === 'success' || kind.value === 'tertiary') {
nextAttributes.push(createAttribute('theme', 'green'))
}
if (state.value === 'grey') {
nextAttributes.push(createAttribute('theme', 'grey'))
}

if (kind.value === 'primary' && !state.value) {
nextAttributes.push(createAttribute('theme', 'blue'))
}
if (kind.value === 'secondary' && !state.value) {
nextAttributes.push(createAttribute('theme', 'grey'))
}

if (kind.value === 'link') {
nextAttributes.push(createAttribute('theme', 'grey'))
nextAttributes.push(linkedAttr)
}

if (kind.value === 'secondary' || kind.value === 'tertiary') {
nextAttributes.push(outlinedAttr)
}
if (shape.value === 'rounded') {
nextAttributes.push(roundedAttr)
}

if (!kind.value || kind.value === 'default') {
nextAttributes.push(linkedAttr)
}

let updatedAttributes = renameAttribute(attributes, 'innerRef', 'ref')
updatedAttributes = renameAttribute(attributes, 'buttonRef', 'ref')
updatedAttributes = renameAttribute(attributes, 'isLoading', 'loading')

return [
...nextAttributes,
...filterAttributes(updatedAttributes, extraAttrsToRemoves),
]
}

const processButtonComponent = p => {
const jSXElement = p.value

const isIcon = jSXElement.name.name.includes('Icon')

// jSXElement.attributes = jSXElement.attributes.filter(a => {
// if (a.type === 'JSXSpreadAttribute') return true
// return a && a.name && a.name.name !== 'version'
// })

jSXElement.attributes = processAttributes(jSXElement)
}

//replace classNames
const cssElements = root.find(j.TemplateLiteral).filter(p => {
const hasVariable =
p.value.quasis.length > 0 &&
p.value.quasis.some(n => {
const rawString = n.value.raw.toString()
return rawString.includes('Button')
})
return hasVariable
})

cssElements.replaceWith(processClassnames)

// replace jsx component
const elements = root.find(j.JSXOpeningElement).filter(el => {
if (el.node.name.name) {
return el.node.name.name.includes('Button')
}
return false
})

elements.forEach(processButtonComponent)

return root.toSource()
}
10 changes: 7 additions & 3 deletions src/components/Accordion/__tests__/Accordion.Link.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import { mount } from 'enzyme'
import Link from '../Accordion.Link'
import Title from '../Accordion.Title'
import Section from '../Accordion.Section'
import { MemoryRouter as Router } from 'react-router-dom'

const wrap = fn => Component => fn(<Router>{Component}</Router>)
const mountWithRouter = wrap(mount)

describe('Section', () => {
test('Renders the link variant of Section if to/href are defined', () => {
const wrapper = mount(<Link to="/" />)
const wrapper = mountWithRouter(<Link to="/" />)
const el = wrapper.find(Section)

expect(el.prop('isLink')).toBe(true)
Expand All @@ -22,14 +26,14 @@ describe('Section', () => {

describe('Title', () => {
test('Passes to prop to Title', () => {
const wrapper = mount(<Link to="/page" />)
const wrapper = mountWithRouter(<Link to="/page" />)
const el = wrapper.find(Title)

expect(el.prop('to')).toBe('/page')
})

test('Passes href prop to Title', () => {
const wrapper = mount(<Link href="/page" />)
const wrapper = mountWithRouter(<Link href="/page" />)
const el = wrapper.find(Title)

expect(el.prop('href')).toBe('/page')
Expand Down
Loading

0 comments on commit 9e1a068

Please sign in to comment.