@whitemordred/react-native-bootstrap5
Version:
A complete React Native library that replicates Bootstrap 5.3 with 100% feature parity, full theming support, CSS variables, and dark/light mode
323 lines (321 loc) • 13 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Popover = exports.Tooltip = void 0;
const react_1 = __importStar(require("react"));
const react_native_1 = require("react-native");
const ThemeProvider_1 = require("../theme/ThemeProvider");
const Tooltip = ({ children, content, placement = 'top', trigger = 'hover', visible: controlledVisible, onVisibilityChange, style, tooltipStyle, textStyle, backgroundColor, textColor, delay = 500, }) => {
const [visible, setVisible] = (0, react_1.useState)(false);
const [childLayout, setChildLayout] = (0, react_1.useState)({ x: 0, y: 0, width: 0, height: 0 });
const [tooltipLayout, setTooltipLayout] = (0, react_1.useState)({ width: 0, height: 0 });
const childRef = (0, react_1.useRef)(null);
const timeoutRef = (0, react_1.useRef)();
const { theme } = (0, ThemeProvider_1.useTheme)();
const isVisible = controlledVisible !== undefined ? controlledVisible : visible;
const showTooltip = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
setVisible(true);
onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(true);
}, delay);
};
const hideTooltip = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
setVisible(false);
onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(false);
};
const toggleTooltip = () => {
if (isVisible) {
hideTooltip();
}
else {
showTooltip();
}
};
const measureChild = () => {
if (childRef.current) {
childRef.current.measure((x, y, width, height, pageX, pageY) => {
setChildLayout({ x: pageX, y: pageY, width, height });
});
}
};
const getTooltipPosition = () => {
const { width: screenWidth, height: screenHeight } = react_native_1.Dimensions.get('window');
const margin = 10;
let top = 0;
let left = 0;
switch (placement) {
case 'top':
top = childLayout.y - tooltipLayout.height - margin;
left = childLayout.x + (childLayout.width - tooltipLayout.width) / 2;
break;
case 'bottom':
top = childLayout.y + childLayout.height + margin;
left = childLayout.x + (childLayout.width - tooltipLayout.width) / 2;
break;
case 'left':
top = childLayout.y + (childLayout.height - tooltipLayout.height) / 2;
left = childLayout.x - tooltipLayout.width - margin;
break;
case 'right':
top = childLayout.y + (childLayout.height - tooltipLayout.height) / 2;
left = childLayout.x + childLayout.width + margin;
break;
}
// Ensure tooltip stays within screen bounds
if (left < margin)
left = margin;
if (left + tooltipLayout.width > screenWidth - margin) {
left = screenWidth - tooltipLayout.width - margin;
}
if (top < margin)
top = margin;
if (top + tooltipLayout.height > screenHeight - margin) {
top = screenHeight - tooltipLayout.height - margin;
}
return { top, left };
};
const getArrowPosition = () => {
const arrowSize = 8;
switch (placement) {
case 'top':
return {
position: 'absolute',
bottom: -arrowSize,
left: '45%',
borderLeftWidth: arrowSize / 2,
borderRightWidth: arrowSize / 2,
borderTopWidth: arrowSize,
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderTopColor: backgroundColor || theme.colors.dark,
};
case 'bottom':
return {
position: 'absolute',
top: -arrowSize,
left: '45%',
borderLeftWidth: arrowSize / 2,
borderRightWidth: arrowSize / 2,
borderBottomWidth: arrowSize,
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderBottomColor: backgroundColor || theme.colors.dark,
};
case 'left':
return {
position: 'absolute',
right: -arrowSize,
top: '45%',
borderTopWidth: arrowSize / 2,
borderBottomWidth: arrowSize / 2,
borderLeftWidth: arrowSize,
borderTopColor: 'transparent',
borderBottomColor: 'transparent',
borderLeftColor: backgroundColor || theme.colors.dark,
};
case 'right':
return {
position: 'absolute',
left: -arrowSize,
top: '45%',
borderTopWidth: arrowSize / 2,
borderBottomWidth: arrowSize / 2,
borderRightWidth: arrowSize,
borderTopColor: 'transparent',
borderBottomColor: 'transparent',
borderRightColor: backgroundColor || theme.colors.dark,
};
default:
return {};
}
};
const getTriggerProps = () => {
switch (trigger) {
case 'click':
return {
onPress: () => {
measureChild();
toggleTooltip();
},
};
case 'focus':
return {
onFocus: () => {
measureChild();
showTooltip();
},
onBlur: hideTooltip,
};
default: // hover
return {
onPressIn: () => {
measureChild();
showTooltip();
},
onPressOut: hideTooltip,
};
}
};
const tooltipPosition = getTooltipPosition();
return (<>
<react_native_1.TouchableOpacity style={style} activeOpacity={trigger === 'hover' ? 0.8 : 1} {...getTriggerProps()} onLayout={() => measureChild()}>
{children}
</react_native_1.TouchableOpacity>
<react_native_1.Modal transparent visible={isVisible} animationType="fade" onRequestClose={hideTooltip}>
<react_native_1.TouchableOpacity style={styles.overlay} activeOpacity={1} onPress={trigger === 'click' ? hideTooltip : undefined}>
<react_native_1.View style={[
styles.tooltip,
{
backgroundColor: backgroundColor || theme.colors.dark,
top: tooltipPosition.top,
left: tooltipPosition.left,
},
tooltipStyle,
]} onLayout={(event) => {
const { width, height } = event.nativeEvent.layout;
setTooltipLayout({ width, height });
}}>
{typeof content === 'string' ? (<react_native_1.Text style={[styles.tooltipText, { color: textColor || theme.colors.white }, textStyle]}>
{content}
</react_native_1.Text>) : (content)}
<react_native_1.View style={getArrowPosition()}/>
</react_native_1.View>
</react_native_1.TouchableOpacity>
</react_native_1.Modal>
</>);
};
exports.Tooltip = Tooltip;
const Popover = (_a) => {
var { title, dismissible = true, variant = 'light' } = _a, tooltipProps = __rest(_a, ["title", "dismissible", "variant"]);
const { theme } = (0, ThemeProvider_1.useTheme)();
const variantColors = theme.colors[variant];
const backgroundColor = typeof variantColors === 'string'
? variantColors
: (variantColors === null || variantColors === void 0 ? void 0 : variantColors[500]) || theme.colors.light;
const textColor = variant === 'light' ? theme.colors.dark : theme.colors.white;
const content = (<react_native_1.View style={styles.popoverContent}>
{title && (<react_native_1.View style={[styles.popoverHeader, { borderBottomColor: theme.colors.gray[200] }]}>
<react_native_1.Text style={[styles.popoverTitle, { color: textColor }]}>
{title}
</react_native_1.Text>
{dismissible && (<react_native_1.TouchableOpacity onPress={() => { var _a; return (_a = tooltipProps.onVisibilityChange) === null || _a === void 0 ? void 0 : _a.call(tooltipProps, false); }} style={styles.popoverClose}>
<react_native_1.Text style={[styles.popoverCloseText, { color: textColor }]}>×</react_native_1.Text>
</react_native_1.TouchableOpacity>)}
</react_native_1.View>)}
<react_native_1.View style={styles.popoverBody}>
{typeof tooltipProps.content === 'string' ? (<react_native_1.Text style={[styles.popoverText, { color: textColor }]}>
{tooltipProps.content}
</react_native_1.Text>) : (tooltipProps.content)}
</react_native_1.View>
</react_native_1.View>);
return (<exports.Tooltip {...tooltipProps} content={content} backgroundColor={backgroundColor} textColor={textColor} trigger="click" tooltipStyle={react_native_1.StyleSheet.flatten([styles.popover, tooltipProps.tooltipStyle])}/>);
};
exports.Popover = Popover;
const styles = react_native_1.StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'transparent',
},
tooltip: {
position: 'absolute',
paddingHorizontal: 8,
paddingVertical: 6,
borderRadius: 4,
maxWidth: 250,
elevation: 5,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 4,
},
tooltipText: {
fontSize: 14,
textAlign: 'center',
},
popover: {
padding: 0,
minWidth: 200,
maxWidth: 300,
},
popoverContent: {
overflow: 'hidden',
},
popoverHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: 1,
},
popoverTitle: {
fontSize: 16,
fontWeight: '600',
flex: 1,
},
popoverClose: {
padding: 4,
},
popoverCloseText: {
fontSize: 20,
fontWeight: 'bold',
},
popoverBody: {
paddingHorizontal: 16,
paddingVertical: 12,
},
popoverText: {
fontSize: 14,
lineHeight: 20,
},
});
exports.default = exports.Tooltip;