mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-13 13:58:35 +03:00
Remove design-system dependency
This commit is contained in:
86
src/components/controls/atoms/Button.js
Normal file
86
src/components/controls/atoms/Button.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
*/
|
||||
export const Button = ({
|
||||
primary,
|
||||
backgroundColor,
|
||||
borderRadius,
|
||||
size,
|
||||
label,
|
||||
normalCursor,
|
||||
...props
|
||||
}) => {
|
||||
const mode = primary ? "button--primary" : "button--secondary";
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={[
|
||||
"button",
|
||||
`button--${size}`,
|
||||
mode,
|
||||
normalCursor ? "no-hover" : "",
|
||||
].join(" ")}
|
||||
style={{ backgroundColor: backgroundColor, borderRadius: borderRadius }}
|
||||
{...props}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
Button.propTypes = {
|
||||
/**
|
||||
* Is this the principal call to action on the page?
|
||||
*/
|
||||
primary: PropTypes.bool,
|
||||
/**
|
||||
* What background color to use
|
||||
*/
|
||||
backgroundColor: PropTypes.string,
|
||||
/**
|
||||
* How much rounded are they?
|
||||
*/
|
||||
borderRadius: PropTypes.string,
|
||||
/**
|
||||
* How large should the button be?
|
||||
*/
|
||||
size: PropTypes.oneOf(["small", "medium", "large"]),
|
||||
/**
|
||||
* Button contents
|
||||
*/
|
||||
label: PropTypes.string.isRequired,
|
||||
/**
|
||||
* Optional click handler
|
||||
*/
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
Button.defaultProps = {
|
||||
backgroundColor: "red",
|
||||
borderRadius: "0%",
|
||||
primary: false,
|
||||
size: "medium",
|
||||
onClick: undefined,
|
||||
};
|
||||
|
||||
const CardButton = ({
|
||||
text,
|
||||
color = "#000",
|
||||
onClick = () => {},
|
||||
normalCursor,
|
||||
}) => (
|
||||
<Button
|
||||
size={"small"}
|
||||
backgroundColor={color}
|
||||
borderRadius={"12px"}
|
||||
primary={false}
|
||||
label={text}
|
||||
onClick={onClick}
|
||||
normalCursor={normalCursor}
|
||||
/>
|
||||
);
|
||||
|
||||
export default CardButton;
|
||||
15
src/components/controls/atoms/Caret.js
Normal file
15
src/components/controls/atoms/Caret.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
|
||||
const CardCaret = ({ isOpen, toggle }) => {
|
||||
let classes = isOpen ? "arrow-down" : "arrow-down folded";
|
||||
|
||||
return (
|
||||
<div className="card-toggle" onClick={toggle}>
|
||||
<p>
|
||||
<i className={classes} />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardCaret;
|
||||
12
src/components/controls/atoms/CustomField.js
Normal file
12
src/components/controls/atoms/CustomField.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from "react";
|
||||
import marked from "marked";
|
||||
|
||||
// TODO could this be a security vulnerability?
|
||||
const CardCustomField = ({ title, value }) => (
|
||||
<div className="card-cell">
|
||||
{title ? <h4>{title}</h4> : null}
|
||||
<div dangerouslySetInnerHTML={{ __html: marked(`${value}`) }} />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default CardCustomField;
|
||||
61
src/components/controls/atoms/Media.js
Normal file
61
src/components/controls/atoms/Media.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import React, { useRef } from "react";
|
||||
import { useCallback } from "react";
|
||||
import { typeForPath } from "../../../common/utilities";
|
||||
|
||||
const TITLE_LENGTH = 50;
|
||||
// TODO should videos
|
||||
// - play inline
|
||||
// - appear zoomed out/in
|
||||
// - only show cover image and then lightbox when clicked
|
||||
// - show video control plane?
|
||||
// TODO landscape image doesn't fit in box properly
|
||||
const Media = ({ src, title }) => {
|
||||
const videoRef = useRef();
|
||||
const onVideoStart = useCallback(() => {
|
||||
return videoRef.current?.play();
|
||||
}, []);
|
||||
const onVideoStop = useCallback(() => {
|
||||
return videoRef.current?.pause();
|
||||
}, []);
|
||||
|
||||
const type = typeForPath(src);
|
||||
const formattedTitle =
|
||||
title && title.length > TITLE_LENGTH
|
||||
? `${title.slice(0, TITLE_LENGTH + 1)}...`
|
||||
: title;
|
||||
|
||||
switch (type) {
|
||||
case "Video":
|
||||
return (
|
||||
<div className="card-cell media">
|
||||
{title && <h4 title={title}>{formattedTitle}</h4>}
|
||||
<video
|
||||
onMouseEnter={onVideoStart}
|
||||
onMouseLeave={onVideoStop}
|
||||
ref={videoRef}
|
||||
// controls
|
||||
// controlsList="nodownload noremoteplayback"
|
||||
disablePictureInPicture
|
||||
>
|
||||
<source src={src} />
|
||||
</video>
|
||||
</div>
|
||||
);
|
||||
case "Image":
|
||||
return (
|
||||
<div className="card-cell media">
|
||||
{title && <h4 title={title}>{formattedTitle}</h4>}
|
||||
<div className="img-wrapper">
|
||||
<img
|
||||
src={src}
|
||||
alt="an inline photograph for the event card component"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default Media;
|
||||
46
src/components/controls/atoms/Text.js
Normal file
46
src/components/controls/atoms/Text.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
const CardText = ({ title, value, hoverValue = null }) => {
|
||||
const [showHover, setShowHover] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="card-cell">
|
||||
{title ? <h4>{title}</h4> : null}
|
||||
<div
|
||||
style={{
|
||||
width: `fit-content`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
onMouseOver={() => hoverValue && setShowHover(true)}
|
||||
onMouseOut={() => hoverValue && setShowHover(false)}
|
||||
>
|
||||
{showHover ? (
|
||||
<span
|
||||
style={{
|
||||
pointerEvents: `none`,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
>
|
||||
<em>{hoverValue}</em>
|
||||
</span>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
pointerEvents: `none`,
|
||||
display: `inline-block`,
|
||||
height: `1.1rem`,
|
||||
borderBottom: hoverValue && `1px rgb(235, 68, 62) dashed`,
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* {!showHover && value} */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardText;
|
||||
29
src/components/controls/atoms/Time.js
Normal file
29
src/components/controls/atoms/Time.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from "react";
|
||||
|
||||
import copy from "../../../common/data/copy.json";
|
||||
import { isNotNullNorUndefined } from "../../../common/utilities";
|
||||
|
||||
const CardTime = ({ title = "Timestamp", timelabel, language, precision }) => {
|
||||
const unknownLang = copy[language].cardstack.unknown_time;
|
||||
|
||||
if (isNotNullNorUndefined(timelabel)) {
|
||||
return (
|
||||
<div className="card-cell">
|
||||
{/* <i className="material-icons left">today</i> */}
|
||||
<h4>{title}</h4>
|
||||
{timelabel}
|
||||
{precision && precision !== "" ? ` - ${precision}` : null}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="card-cell">
|
||||
{/* <i className="material-icons left">today</i> */}
|
||||
<h4>{title}</h4>
|
||||
{unknownLang}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default CardTime;
|
||||
Reference in New Issue
Block a user