Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"rimraf": "^2.6.2",
"rollup": "^0.54.1",
"rollup-plugin-babel": "^3.0.3",
"rollup-plugin-node-resolve": "^4.2.3",
"styled-components": "4.2.0"
},
"peerDependencies": {
Expand Down
66 changes: 40 additions & 26 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
import babel from 'rollup-plugin-babel';
// https://github.com/rollup/rollup/issues/346
import resolve from 'rollup-plugin-node-resolve';

export default {
const globals = {
react: 'React',
payment: 'payment',
'credit-card-type': 'creditCardType',
'styled-components': 'styled'
};

const external = [
'react',
'credit-card-type',
'payment',
'styled-components'
];

const plugins = [
resolve({}),
babel({
presets: [['env', { modules: false }], 'react', 'flow'],
plugins: [
'external-helpers',
'transform-class-properties',
'transform-object-rest-spread'
]
})
]

export default [{
input: `${__dirname}/src/index.js`,
output: {
name: 'CreditCardInput',
file: `${__dirname}/lib/index.js`,
format: 'umd',
globals: {
react: 'React',
payment: 'payment',
'credit-card-type': 'creditCardType',
'styled-components': 'styled'
}
},
sourcemap: true,
external: [
'react',
'credit-card-type',
'payment',
'styled-components'
],
plugins: [
babel({
presets: [['env', { modules: false }], 'react', 'flow'],
plugins: [
'external-helpers',
'transform-class-properties',
'transform-object-rest-spread'
]
})
]
};
globals
}
}, {
input: `${__dirname}/src/hooks/use-credit-card-input.js`,
output: {
name: 'useCreditCardInput',
file: `${__dirname}/lib/useCreditCardInput.js`,
format: 'umd',
globals
}
}].map(output => Object.assign(output, {external, plugins, sourcemap: true}));
214 changes: 214 additions & 0 deletions src/credit-card-input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
// @flow

import React, { useEffect } from 'react';
import styled from 'styled-components';
import useCreditCardInput from './hooks/use-credit-card-input';

const Container = styled.div`
display: inline-block;
${({ styled }) => ({ ...styled })};
`;

const FieldWrapper = styled.div`
display: flex;
align-items: center;
position: relative;
background-color: white;
padding: 10px;
border-radius: 3px;
overflow: hidden;
${({ styled }) => ({ ...styled })};

&.is-invalid {
border: 1px solid #ff3860;
${({ invalidStyled }) => ({ ...invalidStyled })};
}
`;

const CardImage = styled.img`
height: 1em;
${({ styled }) => ({ ...styled })};
`;

const InputWrapper = styled.label`
align-items: center;
display: ${props => (props.isActive ? 'flex' : 'none')};
margin-left: 0.5em;
position: relative;
transition: transform 0.5s;
transform: translateX(${props => (props.translateX ? '4rem' : '0')});

&::after {
content: attr(data-max);
visibility: hidden;
height: 0;
}

& .credit-card-input {
border: 0px;
position: absolute;
width: 100%;
font-size: 1em;
${({ inputStyled }) => ({ ...inputStyled })};

&:focus {
outline: 0px;
}
}

& .zip-input {
display: ${props => (props.isZipActive ? 'flex' : 'none')};
}
`;

const DangerText = styled.p`
font-size: 0.8rem;
margin: 5px 0 0 0;
color: #ff3860;
${({ styled }) => ({ ...styled })};
`;

type CreditCardInputProps = {
cardTypes: Object,
onError?: Function,
cardExpiryInputProps: Object,
cardNumberInputProps: Object,
cardCVCInputProps: Object,
cardZipInputProps: Object,
cardImageClassName: string,
cardImageStyle: Object,
containerClassName: string,
containerStyle: Object,
dangerTextClassName: string,
dangerTextStyle: Object,
fieldClassName: string,
fieldStyle: Object,
images: Object,
inputClassName: string,
inputStyle: Object,
invalidClassName: string,
invalidStyle: Object,
customTextLabels: Object
};

const defaultProps: CreditCardInputProps = {
cardExpiryInputProps: {},
cardNumberInputProps: {},
cardCVCInputProps: {},
cardZipInputProps: {},
cardImageClassName: '',
cardImageStyle: {},
containerClassName: '',
containerStyle: {},
dangerTextClassName: '',
dangerTextStyle: {},
fieldClassName: '',
fieldStyle: {},
inputClassName: '',
inputStyle: {},
invalidClassName: 'is-invalid',
invalidStyle: {},
customTextLabels: {}
};

const CreditCardInput = (props: CreditCardInputProps) => {
const {
state,
cardNumberProps,
cardExpiryProps,
cardCvcProps,
cardZipProps
} = useCreditCardInput(props);

const {
cardImageClassName,
cardImageStyle,
containerClassName,
containerStyle,
dangerTextClassName,
dangerTextStyle,
fieldClassName,
fieldStyle,
inputStyle,
invalidClassName,
invalidStyle
} = props;

const { cardImage, enableZipInput, errorText, showZip } = state;

const setFieldInvalid = (errorText: string) => {
// $FlowFixMe
document.getElementById('field-wrapper').classList.add(invalidClassName);
};

const setFieldValid = () => {
// $FlowFixMe
document.getElementById('field-wrapper').classList.remove(invalidClassName);
};

useEffect(() => {
if (errorText) {
setFieldInvalid(errorText);
} else {
setFieldValid();
}
}, [errorText]);

return (
<Container className={containerClassName} styled={containerStyle}>
<FieldWrapper
id="field-wrapper"
className={fieldClassName}
styled={fieldStyle}
invalidStyled={invalidStyle}
>
<CardImage
className={cardImageClassName}
styled={cardImageStyle}
src={cardImage}
/>
<InputWrapper
inputStyled={inputStyle}
isActive
translateX={false}
data-max="9999 9999 9999 9999 9999"
>
<input name="card-number" {...cardNumberProps} />
</InputWrapper>
<InputWrapper
inputStyled={inputStyle}
isActive
data-max="MM / YY 9"
translateX={enableZipInput && !showZip}
>
<input name="expiry" {...cardExpiryProps} />
</InputWrapper>
<InputWrapper
inputStyled={inputStyle}
isActive
data-max="99999"
translateX={enableZipInput && !showZip}
>
<input name="cvc" {...cardCvcProps} />
</InputWrapper>
<InputWrapper
data-max="999999"
isActive={enableZipInput}
isZipActive={showZip}
translateX={enableZipInput && !showZip}
>
<input name="zip" {...cardZipProps} />
</InputWrapper>
</FieldWrapper>
{errorText && (
<DangerText className={dangerTextClassName} styled={dangerTextStyle}>
{errorText}
</DangerText>
)}
</Container>
);
};

CreditCardInput.defaultProps = defaultProps;

export default CreditCardInput;
30 changes: 30 additions & 0 deletions src/events/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export default function() {
var topics = {};
var hOP = topics.hasOwnProperty;

return {
subscribe: function(topic, listener) {
// Create the topic's object if not yet created
if (!hOP.call(topics, topic)) topics[topic] = [];

// Add the listener to queue
var index = topics[topic].push(listener) - 1;

// Provide handle back for removal of topic
return {
remove: function() {
delete topics[topic][index];
}
};
},
publish: function(topic, info) {
// If the topic doesn't exist, or there's no listeners in queue, just leave
if (!hOP.call(topics, topic)) return;

// Cycle through topics queue, fire!
topics[topic].forEach(function(item) {
item(info !== undefined ? info : {});
});
}
};
}
9 changes: 9 additions & 0 deletions src/events/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import events from './events';
import * as types from './types';

export default function() {
return {
...events(),
types
};
}
4 changes: 4 additions & 0 deletions src/events/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const FOCUS_ON_CARD_NUMBER = Symbol('FOCUS_ON_CARD_NUMBER');
export const FOCUS_ON_CARD_EXPIRY = Symbol('FOCUS_ON_CARD_EXPIRY');
export const FOCUS_ON_CARD_CVC = Symbol('FOCUS_ON_CARD_CVC');
export const FOCUS_ON_CARD_ZIP = Symbol('FOCUS_ON_CARD_ZIP');
Loading