ChainGraph landing page bootstrapped with Gatsby.js
Chaingraph follows Blockmatic's Hook Architecture and Contribution Guidelines as is and as it needs on this repository; it also includes Blockmatic's dev-configs implementation.
-
State Management: Redux style but state is managed with native React
useContext
+useReducer
with a helper package unstated-next at./src/store
depending the area where the state will be controlled and easy to follow. This state management is divided by 2 custom hooks,useGlobalStore
anduseSubscriptionStore
, where:useGlobalStore.js
: It represents the most global states on all site, where it controls user preferred theme, user scroll reading, user mobile/tablet state (if we need to do JS changes on render cicles), withuserThemeHandler(active_debug)
,scrollStateHandler(active_debug)
andmobileStateHandler(active_debug)
functions respectively, where:userThemeHandler(active_debug)
: Controls and reads user theme, by default, it sets user preferred theme from browser and save it on asession-cookie
withuniversal-cookie
that helps us to maintain user's theme on SSR.scrollStateHandler(active_debug)
: It manage 3 different states that, in some way would help us to create animations, interactions on the UI and boost UX, those are:height
:NUMBER
, current user position on actual screen.yOffset
:NUMBER
, helps to read previous user real position and identify if is scrolling up/down.scroll
:BOOL
, it reads if user has scroll down or not.
mobileStateHandler(active_debug)
: Sets whenever user screen, a.k.a.innerWidth
is from mobile or not.
useSubscriptionStore.js
: Manage all subscription logic and UI feedback that is involved, quite simple but useful, since we use Mailchimp for it and a Mailchimp gatsby plugin adaptation for the use of the subscription method, with functionssendSubscriptionRequest(email, options, active_debug)
andmodalConfirm(active_debug)
, where:sendSubscriptionRequest(email, options, active_debug)
: Holds the send of subscription request, which within it has a note about how it works under the hood. This updates some values for the end user so they can know if they're subscribed or not, if there is an error or not, kept on the subscription initial state value calledmsg
anderror
respectably.modalConfirm(active_debug)
: Function controller for the modal notification feedback for the end user, returning only false for the closing of this Modal notification.
-
UI Components: Created with emotion and styled-system, contained at
./src/components/UI
, referencing each one on the index.js file within this folder, so we can call it faster and globally, if someday we add a feature like Storybook to future projects or with this one too; either way, the components management are split.- Others that are "unique" are within the folder that represent page components, such as
blockchain.js
for example; some of them can be used on different repositories that may have same logic, such assubscribe.js
as an example too. - Every UI Components follow a theme structure, where it is divided most of it by colors, due it is basically the only area where it would change between repositories similar to this one, bootstrapped with Gatsby.js. Here we can define basics theme colors and it has a helper too
getColors(palette, component, key)
where navigates through the theme color structure. This is located atsrc/utils/utility.js
.
- Others that are "unique" are within the folder that represent page components, such as
-
Animation Control: Actions and styles according the user interaction on the site are integrated with Framer Motion on the majority of UI components, with 2 different ways to manage the mobile styles:
JS = addEventListener
andCSS-in-JS = emotion
and using them depending our development needs. Also with the control of user's preferred theme, we can read the user preferences at first and user may be able to change the theme and be stored by session cookie for faster read before DOM rendering.- Animations has a base transition object, that it can be pass by only adding the attribute
transition
, i.e.:
export const component = () => { const DURATION = 0.5 // any duration on seconds const DELAY = 0 // any delay (if necessary) on seconds const transition = { repeat: Infinity, // if we have an animation loop duration: DURATION, repeatType: 'reverse', // if we have an animation loop ease: 'linear' || 'easeInOut', // 'easeInOut' is default, 'linear' is defined for animation loops type: 'spring', stiffness: 144, delay: DELAY, } const animate = { x: 0, // transform: translateX(0px) opacity: 1 } const initial = { x: -100, opacity: 0 } return ( <motion.div transition={transition} animate={animate} initial={initial} /> ) }
This base transition can be used on one time animation, loops or UI feedback, anything. Depending what we want; by default the cubic bezier has like a bounce effect at the end of animate state and that gives a good UI Feedback feeling.
But, what if we want to create an animation loop? How do we achieve it without much complexity?
We can do it by thinking about keyframe or steps, long story short, this could be represented as an
array
on themotion
attribute calledanimate
, passing an object with the value that we want animate, where each value represent a percentage of the keyframe, i.e.:@keyframes slide-loop { 0% { transform: translateX(0px); } 25% { transform: translateX(-100px); } 50% { transform: translateX(100px); } 75% { transform: translateX(-50px); } 100% { transform: translateX(50px); } } div.slide-loop--animation { animate: slide-loop 10s linear infinity reverse; }
Can be easily translate as:
/* INSIDE REACT COMPONENT */ const transition = { repeat: Infinity, duration: 10, repeatType: 'reverse', ease: 'linear', // Depending of the loop, is ideal to use linear cubic-bezier, that depends on our perspective at the end type: 'spring', stiffness: 144, } const animate = { x: [0, -100, 100, -50, 50], // @keyframe slide-loop } const initial = { x: 0, } return ( <motion.div transition={transition} animate={animate} initial={initial} /> )
It is important though, that the initial value on the array is the value that we define on the
motion
component with the attributeinitial
, so the loop continue smoothly and, we might be able too, to define the first value asnull
on the array andframer-motion
takes it as the initial value previously defined.Now, we can orchestrate this by passing child animation calls, which is called motion orchestration, adding motion variants or motion propagation but thats depend by 2 questions:
- Do we want an animation that needs to transite before/after any children/siblings that are on the parent component and then create a kind of orchestral animation loop? Such as our hands, when we move individually our fingers in order to create a wave and looping this movement over and over (waiting with anxiety hand gesture).
- Does the animation needs to be created as an individual movement, where we need to create a kind of perspective where show a natural movement? Such as leafs when the wind blows, it doesn't move orchestrally but it follows a pattern, moving individually but pointing on the same direction.
Both can be achieve by taking in consideration 2 things:
transform-style: preserve-3d
andtransform-origin
. Both depends on this very often and we need to think in 3D and how things really moves in our environment so we can translate it on 2D, just likeblockchain.js
component, I thought about space and how things moves on space, and as reference, it doesn't move exactly in a direction, but it follows a pattern, some solid state of matter moves sideways, other up/down, and others both, but those depends on solid state of matter inertia and his hypothetical mass and volume. - Animations has a base transition object, that it can be pass by only adding the attribute
We may find different function utils at ./src/utils/utility.js
, such as getColors(palette, component, key)
, and:
updateObject(oldObject, updatedProperties)
: A very useful function where help us to maintain a lean object mutation, passing as first parameter the object that we want to mutate and the second one (an object) the values that we want to update, i.e.:
const object = {
first: 'value',
second: 2,
third: {
first: 'on third value',
second: {
first: false,
},
},
fourth: [1, 2, 3],
}
const mutatedObject = updateObject(object, {
second: 4,
third: {
...object.third,
second: {
...object.third.second,
first: true,
},
},
fourth: [...object.fourth, 4]
})
Observe that we always need to do spread operators for safe mutations due JS cannot do deep copies of objects, which is OK.
checkValidity(value, rules, password, confirm_password)
: userful utility for form input validations, as first parameter, the value that we want to check, second the rules that we want to apply—which are already defined within the function so we may reference them on usage—and third and fourth are pure for password input validation.
But, how do we process these validations?
We create an object, where we can define the input type, attributes and else so those could be read for <Input />
component at ./src/components/UI/input.js
and object structure as follows:
const input = {
elementType: 'email',
elementConfig: {
type: 'text',
},
label: 'Email',
value: '',
validation: {
required: true,
valid: false,
touched: false,
emailFormat: /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/,
},
}
You may find a specific structure of this at ./src/components/home/subscribe.js
as an example.
debug(type, value)
: debug helper that was designed for store actions functions for every steps of actions that this site does, taking the first parameter thetype
of action called and thevalue
, which is an object that contains the affected values, but by debug propose, we must pass an object with the current state value and the new value that we will assign in order to see which part of the action cicle is failing, and thisdebug
function can be activated IF, we pass the parameter calledactive_debug
asBOOL true
so console can shout the action and the state.