323 lines
10 KiB
TypeScript
323 lines
10 KiB
TypeScript
// A tiny geometric icon set drawn from plain Views.
|
|
//
|
|
// The app ships no vector-icon dependency and emoji render differently on
|
|
// every platform (and can't take the accent colour), so these are built from
|
|
// borders and rectangles instead: monochrome, tintable, pixel-identical on
|
|
// Android and web, and zero bytes of new dependency.
|
|
import { StyleSheet, View } from "react-native";
|
|
import { C } from "../lib/theme";
|
|
|
|
export type IconName =
|
|
| "today"
|
|
| "next"
|
|
| "waiting"
|
|
| "someday"
|
|
| "projects"
|
|
| "review"
|
|
| "settings"
|
|
| "flag"
|
|
| "repeat"
|
|
| "tag"
|
|
| "plus"
|
|
| "close"
|
|
| "chevron";
|
|
|
|
type Props = {
|
|
name: IconName;
|
|
size?: number;
|
|
color?: string;
|
|
/** Used to carve the crescent out of the "someday" moon. */
|
|
bg?: string;
|
|
};
|
|
|
|
export default function Icon({ name, size = 22, color = C.textSecondary, bg = C.bg }: Props) {
|
|
const box = { width: size, height: size };
|
|
const w = 1.7; // one consistent stroke weight across the whole set
|
|
|
|
switch (name) {
|
|
case "today":
|
|
return (
|
|
<View style={[box, s.center]}>
|
|
<View
|
|
style={{
|
|
width: size * 0.86,
|
|
height: size * 0.82,
|
|
borderWidth: w,
|
|
borderColor: color,
|
|
borderRadius: 5,
|
|
overflow: "hidden",
|
|
}}
|
|
>
|
|
<View style={{ height: size * 0.2, backgroundColor: color }} />
|
|
<View style={[s.center, { flex: 1 }]}>
|
|
<View
|
|
style={{ width: w * 2, height: w * 2, borderRadius: 999, backgroundColor: color }}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
|
|
case "next":
|
|
return (
|
|
<View style={[box, s.center]}>
|
|
<View
|
|
style={{
|
|
width: 0,
|
|
height: 0,
|
|
borderTopWidth: size * 0.32,
|
|
borderBottomWidth: size * 0.32,
|
|
borderLeftWidth: size * 0.52,
|
|
borderTopColor: "transparent",
|
|
borderBottomColor: "transparent",
|
|
borderLeftColor: color,
|
|
marginLeft: size * 0.12,
|
|
}}
|
|
/>
|
|
</View>
|
|
);
|
|
|
|
case "waiting":
|
|
return (
|
|
<View style={[box, s.center]}>
|
|
<View
|
|
style={{
|
|
width: size * 0.84,
|
|
height: size * 0.84,
|
|
borderRadius: 999,
|
|
borderWidth: w,
|
|
borderColor: color,
|
|
}}
|
|
>
|
|
{/* hour hand, then minute hand — anchored at the dial centre */}
|
|
<View
|
|
style={{
|
|
position: "absolute",
|
|
left: size * 0.42 - w / 2,
|
|
top: size * 0.2,
|
|
width: w,
|
|
height: size * 0.22,
|
|
backgroundColor: color,
|
|
borderRadius: w,
|
|
}}
|
|
/>
|
|
<View
|
|
style={{
|
|
position: "absolute",
|
|
left: size * 0.42 - w / 2,
|
|
top: size * 0.42 - w / 2,
|
|
width: size * 0.24,
|
|
height: w,
|
|
backgroundColor: color,
|
|
borderRadius: w,
|
|
}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
);
|
|
|
|
case "someday":
|
|
return (
|
|
<View style={[box, s.center]}>
|
|
<View
|
|
style={{
|
|
width: size * 0.84,
|
|
height: size * 0.84,
|
|
borderRadius: 999,
|
|
backgroundColor: color,
|
|
overflow: "hidden",
|
|
}}
|
|
>
|
|
<View
|
|
style={{
|
|
position: "absolute",
|
|
right: -size * 0.22,
|
|
top: -size * 0.26,
|
|
width: size * 0.84,
|
|
height: size * 0.84,
|
|
borderRadius: 999,
|
|
backgroundColor: bg,
|
|
}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
);
|
|
|
|
case "projects":
|
|
return (
|
|
<View style={[box, s.center]}>
|
|
<View style={{ width: size * 0.86, height: size * 0.78 }}>
|
|
<View
|
|
style={{
|
|
width: size * 0.4,
|
|
height: size * 0.16,
|
|
borderTopLeftRadius: 3,
|
|
borderTopRightRadius: 3,
|
|
backgroundColor: color,
|
|
}}
|
|
/>
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
borderWidth: w,
|
|
borderColor: color,
|
|
borderRadius: 5,
|
|
borderTopLeftRadius: 0,
|
|
}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
);
|
|
|
|
case "review":
|
|
return (
|
|
<View style={[box, s.center]}>
|
|
{/* an open ring plus an arrowhead reads as "cycle" without an SVG path */}
|
|
<View
|
|
style={{
|
|
width: size * 0.82,
|
|
height: size * 0.82,
|
|
borderRadius: 999,
|
|
borderWidth: w,
|
|
borderColor: color,
|
|
borderTopColor: "transparent",
|
|
transform: [{ rotate: "45deg" }],
|
|
}}
|
|
/>
|
|
<View
|
|
style={{
|
|
position: "absolute",
|
|
top: size * 0.06,
|
|
right: size * 0.12,
|
|
width: 0,
|
|
height: 0,
|
|
borderLeftWidth: size * 0.16,
|
|
borderRightWidth: size * 0.16,
|
|
borderBottomWidth: size * 0.2,
|
|
borderLeftColor: "transparent",
|
|
borderRightColor: "transparent",
|
|
borderBottomColor: color,
|
|
transform: [{ rotate: "115deg" }],
|
|
}}
|
|
/>
|
|
</View>
|
|
);
|
|
|
|
case "settings":
|
|
// Sliders, not a cog: three strokes with offset knobs stays legible at 20px.
|
|
return (
|
|
<View style={[box, s.center]}>
|
|
<View style={{ width: size * 0.88, gap: size * 0.16 }}>
|
|
{[0.62, 0.3, 0.48].map((knob, i) => (
|
|
<View key={i} style={s.sliderRow}>
|
|
<View style={{ flex: 1, height: w, backgroundColor: color, borderRadius: w }} />
|
|
<View
|
|
style={{
|
|
position: "absolute",
|
|
left: `${knob * 100}%`,
|
|
width: w * 3,
|
|
height: w * 3,
|
|
marginLeft: -w * 1.5,
|
|
marginTop: -w,
|
|
borderRadius: 999,
|
|
borderWidth: w,
|
|
borderColor: color,
|
|
backgroundColor: bg,
|
|
}}
|
|
/>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</View>
|
|
);
|
|
|
|
case "flag":
|
|
return (
|
|
<View style={box}>
|
|
{/* Pole plus a pennant — reads as a priority flag at 20px where a
|
|
detailed banner outline would smear into a blob. */}
|
|
<View style={{ position: "absolute", left: size * 0.26, top: size * 0.12, width: w, height: size * 0.76, borderRadius: 1, backgroundColor: color }} />
|
|
<View
|
|
style={{
|
|
position: "absolute",
|
|
left: size * 0.26 + w,
|
|
top: size * 0.14,
|
|
width: size * 0.44,
|
|
height: size * 0.34,
|
|
borderTopRightRadius: 2,
|
|
borderBottomRightRadius: 2,
|
|
backgroundColor: color,
|
|
}}
|
|
/>
|
|
</View>
|
|
);
|
|
|
|
case "repeat":
|
|
return (
|
|
<View style={box}>
|
|
{/* Two offset bars with opposing arrowheads: a loop, without the
|
|
anti-aliasing mush a thin circular arc gets at this size. */}
|
|
<View style={{ position: "absolute", left: size * 0.2, top: size * 0.3, width: size * 0.5, height: w, backgroundColor: color }} />
|
|
<View style={{ position: "absolute", left: size * 0.62, top: size * 0.3 - size * 0.09 + w / 2, width: size * 0.18, height: size * 0.18, borderRightWidth: w, borderTopWidth: w, borderColor: color, transform: [{ rotate: "45deg" }] }} />
|
|
<View style={{ position: "absolute", left: size * 0.3, top: size * 0.66, width: size * 0.5, height: w, backgroundColor: color }} />
|
|
<View style={{ position: "absolute", left: size * 0.2, top: size * 0.66 - size * 0.09 + w / 2, width: size * 0.18, height: size * 0.18, borderLeftWidth: w, borderBottomWidth: w, borderColor: color, transform: [{ rotate: "45deg" }] }} />
|
|
</View>
|
|
);
|
|
|
|
case "tag":
|
|
return (
|
|
<View style={[box, s.center]}>
|
|
<View
|
|
style={{
|
|
width: size * 0.6,
|
|
height: size * 0.6,
|
|
borderWidth: w,
|
|
borderColor: color,
|
|
borderRadius: 3,
|
|
transform: [{ rotate: "45deg" }],
|
|
}}
|
|
/>
|
|
<View style={{ position: "absolute", left: size * 0.3, top: size * 0.3, width: w + 0.6, height: w + 0.6, borderRadius: 999, backgroundColor: color }} />
|
|
</View>
|
|
);
|
|
|
|
case "close":
|
|
return (
|
|
<View style={[box, s.center]}>
|
|
<View style={{ position: "absolute", width: size * 0.6, height: w, borderRadius: 2, backgroundColor: color, transform: [{ rotate: "45deg" }] }} />
|
|
<View style={{ position: "absolute", width: size * 0.6, height: w, borderRadius: 2, backgroundColor: color, transform: [{ rotate: "-45deg" }] }} />
|
|
</View>
|
|
);
|
|
|
|
case "plus":
|
|
return (
|
|
<View style={[box, s.center]}>
|
|
<View style={{ position: "absolute", width: size * 0.7, height: w + 0.5, borderRadius: 2, backgroundColor: color }} />
|
|
<View style={{ position: "absolute", width: w + 0.5, height: size * 0.7, borderRadius: 2, backgroundColor: color }} />
|
|
</View>
|
|
);
|
|
|
|
case "chevron":
|
|
return (
|
|
<View style={[box, s.center]}>
|
|
<View
|
|
style={{
|
|
width: size * 0.34,
|
|
height: size * 0.34,
|
|
borderRightWidth: w,
|
|
borderTopWidth: w,
|
|
borderColor: color,
|
|
transform: [{ rotate: "45deg" }],
|
|
marginLeft: -size * 0.06,
|
|
}}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
const s = StyleSheet.create({
|
|
center: { alignItems: "center", justifyContent: "center" },
|
|
sliderRow: { justifyContent: "center" },
|
|
});
|