2024-05-20 22:18:18 +02:00

141 lines
3.4 KiB
TypeScript

/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import type React from "react";
import { type ComponentProps, type ComponentType, type ForwardedRef, forwardRef } from "react";
import { Platform, type PressableProps } from "react-native";
import type { SvgProps } from "react-native-svg";
import type { YoshikiStyle } from "yoshiki";
import { type Stylable, type Theme, px, useYoshiki } from "yoshiki/native";
import { PressableFeedback } from "./links";
import { P } from "./text";
import { type Breakpoint, focusReset, ts } from "./utils";
declare module "react" {
function forwardRef<T, P = {}>(
render: (props: P, ref: React.ForwardedRef<T>) => React.ReactElement | null,
): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
}
export type Icon = ComponentType<SvgProps>;
type IconProps = {
icon: Icon;
color?: Breakpoint<string>;
size?: YoshikiStyle<number | string>;
};
export const Icon = ({ icon: Icon, color, size = 24, ...props }: IconProps) => {
const { css, theme } = useYoshiki();
const computed = css(
{ width: size, height: size, fill: color ?? theme.contrast, flexShrink: 0 } as any,
props,
) as any;
return (
<Icon
{...Platform.select<SvgProps>({
web: computed,
default: {
height: computed.style[0]?.height,
width: computed.style[0]?.width,
fill: computed.style[0]?.fill,
...computed,
},
})}
/>
);
};
export const IconButton = forwardRef(function IconButton<AsProps = PressableProps>(
{
icon,
size,
color,
as,
...asProps
}: IconProps & {
as?: ComponentType<AsProps>;
} & AsProps,
ref: ForwardedRef<unknown>,
) {
const { css, theme } = useYoshiki();
const Container = as ?? PressableFeedback;
return (
<Container
ref={ref as any}
focusRipple
{...(css(
{
alignSelf: "center",
p: ts(1),
m: px(2),
overflow: "hidden",
borderRadius: 9999,
fover: {
self: {
...focusReset,
bg: (theme: Theme) => theme.overlay0,
},
},
},
asProps,
) as AsProps)}
>
<Icon
icon={icon}
size={size}
color={"disabled" in asProps && asProps.disabled ? theme.overlay1 : color}
/>
</Container>
);
});
export const IconFab = <AsProps = PressableProps>(
props: ComponentProps<typeof IconButton<AsProps>>,
) => {
const { css, theme } = useYoshiki();
return (
<IconButton
color={theme.colors.black}
{...(css(
{
bg: (theme) => theme.accent,
fover: {
self: {
transform: "scale(1.3)" as any,
bg: (theme: Theme) => theme.accent,
},
},
},
props,
) as any)}
/>
);
};
export const DottedSeparator = (props: Stylable) => {
const { css } = useYoshiki();
return <P {...css({ mX: ts(1) }, props)}>{String.fromCharCode(0x2022)}</P>;
};