@appbuckets/react-ui
Version:
Just Another React UI Framework
417 lines (410 loc) • 11.2 kB
JavaScript
;
var tslib = require('tslib');
var React = require('react');
var clsx = require('clsx');
var reactUiCore = require('@appbuckets/react-ui-core');
var customHook = require('../utils/customHook.js');
require('../BucketTheme/BucketTheme.js');
var BucketContext = require('../BucketTheme/BucketContext.js');
function _interopDefaultLegacy(e) {
return e && typeof e === 'object' && 'default' in e ? e : { default: e };
}
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(
n,
k,
d.get
? d
: {
enumerable: true,
get: function () {
return e[k];
},
}
);
}
});
}
n['default'] = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/ _interopNamespace(React);
var clsx__default = /*#__PURE__*/ _interopDefaultLegacy(clsx);
/* --------
* Internal Function
* -------- */
function nextFrame(callback) {
requestAnimationFrame(function () {
requestAnimationFrame(callback);
});
}
/* --------
* Component Render
* -------- */
var Collapsable = function (receivedProps) {
var props = BucketContext.useWithDefaultProps('collapsable', receivedProps);
var _a = customHook.useSharedClassName(props),
className = _a.className,
_b = _a.rest,
children = _b.children,
collapsedHeight = _b.collapsedHeight,
content = _b.content,
userDefinedDefaultOpen = _b.defaultOpen,
disabled = _b.disabled,
onChange = _b.onChange,
onClose = _b.onClose,
onOpen = _b.onOpen,
userDefinedOpen = _b.open,
skipAnimation = _b.skipAnimation,
trigger = _b.trigger,
userDefinedTriggerRef = _b.triggerRef,
rest = tslib.__rest(_b, [
'children',
'collapsedHeight',
'content',
'defaultOpen',
'disabled',
'onChange',
'onClose',
'onOpen',
'open',
'skipAnimation',
'trigger',
'triggerRef',
]);
// ----
// Memoized Props and Internal Hooks
// ----
var contentRef = React__namespace.useRef(null);
var triggerRef = React__namespace.useRef();
var _c = tslib.__read(
React__namespace.useReducer(function (val) {
return val + 1;
}, 0),
2
),
forceUpdate = _c[1];
var _d = tslib.__read(React__namespace.useState(0), 2),
callbackTick = _d[0],
setCallbackTick = _d[1];
var _e = tslib.__read(
reactUiCore.useAutoControlledValue(false, {
defaultProp: userDefinedDefaultOpen,
prop: userDefinedOpen,
}),
2
),
isOpen = _e[0],
trySetOpen = _e[1];
var collapsedVisibility = React__namespace.useMemo(
function () {
return collapsedHeight === 0 ? 'hidden' : 'unset';
},
[collapsedHeight]
);
// ----
// Internal State
// ----
var state = React__namespace.useRef({
collapse: isOpen ? 'expanded' : 'collapsed',
style: {
height: isOpen ? '' : collapsedHeight,
visibility: isOpen ? 'unset' : collapsedVisibility,
},
}).current;
// ----
// Handlers and Callbacks
// ----
var onCallback = React__namespace.useCallback(function (callback, params) {
if (typeof callback === 'function') {
if (Array.isArray(params)) {
callback.apply(
void 0,
tslib.__spreadArray([], tslib.__read(params), false)
);
} else {
callback();
}
}
}, []);
var getElementHeight = React__namespace.useCallback(function () {
var _a;
return ''.concat(
((_a = contentRef.current) === null || _a === void 0
? void 0
: _a.scrollHeight) || 0,
'px'
);
}, []);
var setCollapsed = React__namespace.useCallback(
function () {
/** If no ref, return */
if (!contentRef.current) {
return;
}
/** Update the State */
state.collapse = 'collapsed';
state.style = {
height: collapsedHeight,
visibility: collapsedVisibility,
};
forceUpdate();
setTimeout(function () {
setCallbackTick(Date.now());
}, 0);
},
[collapsedHeight, collapsedVisibility, state]
);
var setCollapsing = React__namespace.useCallback(
function () {
/** If no ref, return */
if (!contentRef.current) {
return;
}
/** If must avoid animation, skip */
if (skipAnimation) {
return setCollapsed();
}
/** Update the State */
state.collapse = 'collapsing';
state.style = {
height: getElementHeight(),
visibility: 'unset',
};
forceUpdate();
nextFrame(function () {
if (!contentRef.current) {
return;
}
if (state.collapse !== 'collapsing') {
return;
}
state.style = {
height: collapsedHeight,
visibility: 'unset',
};
setCallbackTick(Date.now());
});
},
[collapsedHeight, getElementHeight, setCollapsed, skipAnimation, state]
);
var setExpanded = React__namespace.useCallback(
function () {
/** If no ref, return */
if (!contentRef.current) {
return;
}
/** Update the State */
state.collapse = 'expanded';
state.style = {
height: '',
visibility: 'unset',
};
forceUpdate();
setTimeout(function () {
setCallbackTick(Date.now());
}, 0);
},
[state]
);
var setExpanding = React__namespace.useCallback(
function () {
/** If no ref, return */
if (!contentRef.current) {
return;
}
/** If must avoid animation, skip */
if (skipAnimation) {
return setExpanded();
}
/** Update state */
state.collapse = 'expanding';
nextFrame(function () {
/** If no ref, return */
if (!contentRef.current) {
return;
}
if (state.collapse !== 'expanding') {
return;
}
state.style = {
height: getElementHeight(),
visibility: 'unset',
};
setCallbackTick(Date.now());
});
},
[getElementHeight, setExpanded, skipAnimation, state]
);
var handleTransitionEnd = React__namespace.useCallback(
function (_a) {
var target = _a.target,
propertyName = _a.propertyName;
/** Skip other transition */
if (target !== contentRef.current || propertyName !== 'height') {
return;
}
var height = target.style.height;
/** Properly continue transition */
switch (state.collapse) {
case 'expanding':
if (!(height === '' || height === ''.concat(collapsedHeight, 'px'))) {
setExpanded();
}
break;
case 'collapsing':
if (!(height === '' || height !== ''.concat(collapsedHeight, 'px'))) {
setCollapsed();
}
break;
}
},
[collapsedHeight, setExpanded, setCollapsed, state.collapse]
);
var handleTriggerRef = React__namespace.useCallback(
function (component) {
triggerRef.current = component;
if (userDefinedTriggerRef !== undefined) {
reactUiCore.handleRef(userDefinedTriggerRef, component);
}
},
[userDefinedTriggerRef]
);
var handleCollapsableToggle = React__namespace.useCallback(
function () {
/** Abort if Disabled */
if (disabled) {
return;
}
if (state.collapse === 'collapsed' || state.collapse === 'collapsing') {
/** Invoke user defined callback */
if (typeof onOpen === 'function') {
onOpen(state);
}
/** Try setting State */
trySetOpen(true);
} else if (
state.collapse === 'expanded' ||
state.collapse === 'expanding'
) {
/** Invoke user defined callback */
if (typeof onClose === 'function') {
onClose(state);
}
/** Try setting State */
trySetOpen(false);
}
},
[disabled, onClose, onOpen, state, trySetOpen]
);
// ----
// LifeCycle Events
// ----
/** Simulate getDerivedStateFromProps */
var didOpen = state.collapse === 'expanding' || state.collapse === 'expanded';
React__namespace.useEffect(
function () {
if (callbackTick) {
onCallback(onChange, [state]);
}
},
// This effect is used to auto invoke on state change
// only once the callbackTick will be update.
// Callback Tick must be the only dependencies
// eslint-disable-next-line react-hooks/exhaustive-deps
[callbackTick]
);
React__namespace.useEffect(
function () {
if (isOpen && !didOpen && !disabled) {
setExpanding();
} else if (!isOpen && didOpen && !disabled) {
setCollapsing();
}
},
// This effect is used to set the animation start
// while opening or closing the collapsable element
// In this case, the only dependencies of the Effect
// must be the open state and the internal didOpen state
// eslint-disable-next-line react-hooks/exhaustive-deps
[didOpen, isOpen]
);
// ----
// Class List Building
// ----
var classes = clsx__default['default'](
{
disabled: disabled,
opening: state.collapse === 'expanding',
opened: state.collapse === 'expanded' || state.collapse === 'expanding',
closing: state.collapse === 'collapsing',
closed: state.collapse === 'collapsed' || state.collapse === 'collapsing',
},
'collapsable',
className
);
// ----
// Trigger Element
// ----
var triggerElement = React__namespace.useMemo(
function () {
if (!trigger) {
return null;
}
return React__namespace.createElement(
reactUiCore.Ref,
{ innerRef: handleTriggerRef },
React__namespace.cloneElement(trigger, {
onClick: handleCollapsableToggle,
})
);
},
[handleCollapsableToggle, handleTriggerRef, trigger]
);
// ----
// Component Render
// ----
var collapsableContent = reactUiCore.childrenUtils.isNil(children)
? content
: children;
var contentStyle = tslib.__assign(
{ overflow: state.collapse === 'expanded' ? '' : 'hidden' },
state.style
);
return React__namespace.createElement(
'div',
tslib.__assign({}, rest, { className: classes }),
triggerElement &&
React__namespace.createElement(
'div',
{ className: 'trigger' },
triggerElement
),
React__namespace.createElement(
'div',
{
ref: contentRef,
className: 'content',
style: contentStyle,
onTransitionEnd: handleTransitionEnd,
},
collapsableContent
)
);
};
Collapsable.displayName = 'Collapsable';
Collapsable.create = reactUiCore.createShorthandFactory(
Collapsable,
function (content) {
return {
content: content,
};
}
);
module.exports = Collapsable;