Skip to content

Commit

Permalink
Merge pull request #195 from vtex/feature/profile-form-css-handles
Browse files Browse the repository at this point in the history
Feature/profile form css handles
  • Loading branch information
beatrizmaselli authored Jun 14, 2024
2 parents 94afdc4 + f2ebd5e commit 4b3a994
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 15 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- CSS Handles based at field names for edit profile customizations at my account.

## [3.16.6] - 2024-06-05

### Added
Expand Down
3 changes: 3 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@
"registries": [
"smartcheckout"
],
"dependencies": {
"vtex.css-handles": "0.x"
},
"$schema": "https://raw.githubusercontent.com/vtex/node-vtex-api/master/gen/manifest.schema"
}
5 changes: 4 additions & 1 deletion react/ProfileContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import emptyProfile from './modules/emptyProfile'
import defaultRules from './rules/default'
import StyleguideInput from './inputs/StyleguideInput'
import styles from './styles.css'
import ProfileFieldWrapper from './ProfileFieldWrapper'

const PROFILE_FIELD_CSS_HANDLE_PREFIX = 'profileForm-'
class ProfileContainer extends Component {
constructor(props) {
super(props)
Expand Down Expand Up @@ -82,7 +84,7 @@ class ProfileContainer extends Component {
<form className={styles.profileContainer} onSubmit={this.handleSubmit}>
<div className={styles.personalFields}>
{rules.personalFields.map(field => (
<ProfileField
<ProfileFieldWrapper
key={field.name}
field={field}
data={profile[field.name]}
Expand All @@ -91,6 +93,7 @@ class ProfileContainer extends Component {
Input={Input}
userProfile={profile}
blockDocument={this.props.blockDocument}
cssHandle={`${PROFILE_FIELD_CSS_HANDLE_PREFIX}${field.name}`}
/>
))}
</div>
Expand Down
15 changes: 15 additions & 0 deletions react/ProfileFieldWrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react'
import { useCssHandles } from 'vtex.css-handles'
import ProfileField from './ProfileField'

function ProfileFieldWrapper(props) {
const handles = useCssHandles([props.cssHandle])

return (
<div className={handles[props.cssHandle]}>
<ProfileField {...props} />
</div>
)
}

export default ProfileFieldWrapper
117 changes: 117 additions & 0 deletions react/__mocks__/vtex.css-handles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/* eslint-disable default-param-last */
import React, { createContext, useContext, useMemo } from 'react'

export const useCssHandles = cssHandles => {
const handles = {}

cssHandles.forEach(handle => {
handles[handle] = handle
})

return { handles, withModifiers: withModifiersHelper(handles) }
}

const withModifiersHelper = handles => (handle, modifier) => {
return applyModifiers(handles[handle], modifier)
}

export const createCssHandlesContext = cssHandles => {
const Context = createContext({
handles: cssHandles,
withModifiers: withModifiersHelper(cssHandles),
})

const useContextCssHandles = () => {
return useContext(Context)
}

const CssHandlesProvider = ({ withModifiers, handles, children }) => {
const value = useMemo(
() => ({
handles,
withModifiers,
}),
[withModifiers, handles]
)

return <Context.Provider value={value}>{children}</Context.Provider>
}

return { useContextCssHandles, CssHandlesProvider }
}

const validateModifier = modifier => {
if (typeof modifier !== 'string') {
console.error(
`Invalid modifier type on \`cssHandles.applyModifier\`. All modifiers should be strings, found "${modifier}" `
)

return false
}

/* This is not an error, so doesn't log any message, but should
* invalidate the current modifier and not include it */
if (modifier === '') {
return false
}

if (/[^A-z0-9-]/.test(modifier)) {
console.error(
`Invalid modifier on \`cssHandles.applyModifier\`. Modifiers should contain only letters, numbers or -`
)

return false
}

return true
}

export const applyModifiers = (handles, modifier) => {
const normalizedModifiers =
typeof modifier === 'string' ? [modifier] : modifier

if (!Array.isArray(normalizedModifiers)) {
console.error(
'Invalid modifier type on `cssHandles.applyModifier`. Please use either a string or an array of strings'
)

return handles
}

const splitHandles = handles.split(' ')

const modifiedHandles = normalizedModifiers
.map(currentModifier => {
const isValid = validateModifier(currentModifier)

if (!isValid) {
return ''
}

return splitHandles
.map(handle => `${handle}--${currentModifier}`)
.join(' ')
.trim()
})
.filter(l => l.length > 0)
.join(' ')
.trim()

return splitHandles.concat(modifiedHandles).join(' ').trim()
}

export const withCssHandles =
(handles = [], options) =>
Component => {
const EnhancedComponent = props => {
const { handles: cssHandles } = useCssHandles(handles, options)

return <Component handles={cssHandles} {...props} />
}

const displayName = Component.displayName || Component.name || 'Component'

EnhancedComponent.displayName = `withCssHandles(${displayName})`

return EnhancedComponent
}
16 changes: 6 additions & 10 deletions react/__tests__/ProfileContainer.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import { shallowWithIntl, loadTranslation } from 'enzyme-react-intl'
import ProfileContainer from '../ProfileContainer'
import ProfileField from '../ProfileField'
import ProfileFieldWrapper from '../ProfileFieldWrapper'
import mockRules from '../__mocks__/rules'
import mockProfile from '../__mocks__/profile'

Expand All @@ -10,6 +10,7 @@ loadTranslation('../messages/pt.json')
describe('ProfileContainer', () => {
let wrapper
let mockSubmit

beforeEach(() => {
// Arrange
mockSubmit = jest.fn()
Expand All @@ -24,22 +25,17 @@ describe('ProfileContainer', () => {

it('should render fields based on rules', () => {
// Act
const result = wrapper.find(ProfileField)
const result = wrapper.find(ProfileFieldWrapper)

// Assert
expect(result).toHaveLength(4)
})

it('should pass down profile data to fields', () => {
// Act
const firstName = wrapper
.find(ProfileField)
.first()
.props().data.value
const lastName = wrapper
.find(ProfileField)
.last()
.props().data.value
const firstName = wrapper.find(ProfileFieldWrapper).first().props()
.data.value
const lastName = wrapper.find(ProfileFieldWrapper).last().props().data.value

// Assert
expect(firstName).toBe('John')
Expand Down
5 changes: 2 additions & 3 deletions react/__tests__/ProfileRules.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { IntlProvider } from 'react-intl'

import { prepareDateRules, filterDateType } from '../utils/dateRules'
import defaultRules from '../rules/default'

Expand All @@ -9,7 +8,7 @@ const { intl } = intlProvider.getChildContext()
function getBirthDate(rules) {
const preparedRules = prepareDateRules(rules, intl)
const birthDateInitialized = preparedRules.personalFields.find(
rule => rule.name === 'birthDate',
(rule) => rule.name === 'birthDate',
)
return birthDateInitialized
}
Expand All @@ -27,7 +26,7 @@ describe('ProfileRules aux functions', () => {
const rules = defaultRules
const birthDate = filterDateType(rules.personalFields)[0]

expect(birthDate.mask).toBeUndefined()
expect(birthDate.mask).toBeDefined()
expect(birthDate.validate).toBeDefined()
expect(birthDate.display).toBeUndefined()
expect(birthDate.submit).toBeUndefined()
Expand Down
3 changes: 2 additions & 1 deletion react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@
"nwb": "^0.21.5",
"react-dom": "^16.4.1",
"react-hot-loader": "^4.3.4",
"react-test-renderer": "^16.4.1"
"react-test-renderer": "^16.4.1",
"vtex.css-handles": "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public/@types/vtex.css-handles"
},
"peerDependencies": {
"vtex-tachyons": "^2.5.0"
Expand Down
4 changes: 4 additions & 0 deletions react/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11276,6 +11276,10 @@ vtex-tachyons@^2.6.0:
resolved "https://registry.yarnpkg.com/vtex-tachyons/-/vtex-tachyons-2.10.0.tgz#d72363dcd77d0960cb23efc82e0e8d168714f153"
integrity sha512-WWEOR4iyrMQ2fzp+EActa5ZLaYSMdPk2yxP8XQRhg+q+QnbFH2EKGayjkea4okXRDE7KTKQZdLF3GlB/HaXOow==

"vtex.css-handles@http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public/@types/vtex.css-handles":
version "0.4.4"
resolved "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public/@types/vtex.css-handles#8c45c6decf9acd2b944e07261686decff93d6422"

w3c-hr-time@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
Expand Down

0 comments on commit 4b3a994

Please sign in to comment.