Compare commits

...

17 Commits

Author SHA1 Message Date
honzapatCZ
59aa96507a Fixed up checkbox 2025-09-26 20:08:56 +02:00
honzapatCZ
5859820f2e add masking 2025-06-23 10:38:00 +02:00
honzapatCZ
b46be19a22 change props children in input 2025-03-31 11:24:49 +02:00
honzapatCZ
f082308d5d add up types 2025-01-14 19:20:45 +01:00
honzapatCZ
4c17fb9488 badge 2025-01-13 19:04:56 +01:00
honzapatCZ
c39f9eff8b udpate badge 2025-01-13 18:57:16 +01:00
honzapatCZ
db786e3cd3 move flag 2024-10-18 19:15:51 +02:00
honzapatCZ
84868b3d52 move to nru 2024-10-17 23:19:38 +02:00
honzapatCZ
7c0c57015e nrc 2024-10-12 15:41:27 +02:00
honzapatCZ
24693e25f1 change dropdown to tsx 2024-10-12 15:30:19 +02:00
honzapatCZ
407d4c1215 some tsx 2024-10-08 14:11:40 +02:00
honzapatCZ
1e9f25e61d add Flag 2024-10-07 15:39:11 +02:00
honzapatCZ
6ff38df1ce revert a weird formik change 2024-09-18 19:55:54 +02:00
honzapatCZ
15e51d5e59 optional base styles 2024-09-15 18:49:54 +02:00
honzapatCZ
b194e5c4fa small fixups for fuckups 2024-09-14 18:39:05 +02:00
honzapatCZ
46f43159cd mrege 2024-09-14 18:38:18 +02:00
honzapatCZ
d72f46721b accounting table changes 2024-09-14 18:36:31 +02:00
11 changed files with 281 additions and 223 deletions

View File

18
Parts/Badge.tsx Normal file
View File

@ -0,0 +1,18 @@
import { useTranslation } from "react-i18next";
import tw, { TwStyle } from "twin.macro";
import "styled-components/macro";
import { ComponentProps, forwardRef } from "react";
export const Badge = forwardRef<HTMLParagraphElement, { color: string | TwStyle, text: string | number }>(({ color, text, ...props }, ref) => {
const colors = {
yellow: tw`bg-yellow-500`,
gray: tw`bg-gray-500`,
red: tw`bg-red-700`,
};
const realColor = (typeof (color) === "string" && color in colors) ? colors[color] : color;
return (
<p ref={ref} css={[tw`px-2 py-1 inline text-primary text-xs font-semibold uppercase rounded-full `, realColor]} {...props}>{text}</p>
);
});

View File

@ -1,8 +1,9 @@
import React, { cloneElement, isValidElement, useEffect, useImperativeHandle, useRef, useState } from "react";
import { createPopper } from "@popperjs/core";
import { createPopper, Placement } from "@popperjs/core";
import { usePopper } from 'react-popper';
import tw, { styled } from "twin.macro"
import styled from "styled-components";
import tw from "twin.macro"
import { colors, colorsDisabled, colorsHover, textColors, textColorsHover } from "./Colors";
@ -16,7 +17,16 @@ const DropDownItem = styled.button(({ type }) => [
])
export { DropDownItem }
let Dropdown = React.forwardRef(({ children, dropdownCss, onValueChanged, button, buttonProps, popper, placement, hover, ...props }, ref) => {
let Dropdown = React.forwardRef(({ children, dropdownCss = null, onValueChanged = null, button, buttonProps = null, popper = null, placement = null, hover = null, ...props }: {
children: React.ReactNode,
dropdownCss?: any,
onValueChanged?: (value: boolean) => void,
button?: React.ReactNode,
buttonProps?: any,
popper?: any,
placement?: Placement,
hover?: boolean,
}, ref) => {
// dropdown props
const [overButton, setOverButton] = React.useState(false);
const [overDropDown, setOverDropDown] = React.useState(false);
@ -85,11 +95,14 @@ let Dropdown = React.forwardRef(({ children, dropdownCss, onValueChanged, button
let childrenWithProps = React.Children.map(children, (item) => {
if (!item)
return;
//check that item is not only ReactNode
if (!isValidElement(item))
return;
let click = item.props?.onClick;
let close = item.props?.closeOnClick ?? true;
return cloneElement(item, { key: children.indexOf ? children.indexOf(item) : 0, onClick: () => { close && closeDropdownPopover(); click && click() } });
});
return cloneElement(item as any, { key: Array.isArray(children) ? children.indexOf(item) : 0, onClick: () => { close && closeDropdownPopover(); click && click() } });
});
return (
<>
<button
@ -116,7 +129,7 @@ let Dropdown = React.forwardRef(({ children, dropdownCss, onValueChanged, button
dropdownPopoverShow ? tw`block` : tw`hidden`,
tw`bg-trinary text-base z-50 float-left rounded-xl overflow-hidden text-left shadow-lg`,
dropdownCss,
props.css
(props as any).css
]}
>
{childrenWithProps}

12
Parts/Flag.tsx Normal file
View File

@ -0,0 +1,12 @@
import { Country } from "@services/accounting-api";
import { lazy, Suspense } from 'react';
export default function Flag({ country, ...props }: { country: Country | "EU" }) {
const FlagIcon = lazy(() => import(`country-flag-icons/react/3x2`).then(module => ({ default: module[country] })));
return (
<Suspense fallback={<div {...props}>...</div>}>
<FlagIcon {...props}/>
</Suspense>
);
}

View File

@ -1,111 +0,0 @@
import React from "react";
import tw, { styled } from "twin.macro";
;
//import { useField as useFormikField, useFormikContext } from "formik";
const useFormikField = null;
const useFormikContext = ()=>{};
export function useField(props){
let getField = useFormikField;
const context = useFormikContext();
if(!context){
getField = (props)=>{
return [props, null, null];
}
}
return getField(props);
}
export default function Input({ label, title, children, ...props }) {
/*name, id*/
const [field, meta, helpers] = useField(props);
return (
<div css={[title && tw`my-1`]} >
{title && (
<label
{...label}
css={[tw`block text-secondary text-sm font-bold mb-2`, label?.css]}
>
{/*htmlFor={id ?? name ?? title}*/}
{title}
</label>
)}
{/*name={name || title} id={id || name || title}*/}
<input
{...field}
{...props}
tw="bg-primary appearance-none border-2 border-secondary rounded w-full py-2 px-4 text-primary leading-tight focus:outline-none focus:bg-secondary focus:border-accent transition duration-150 "
/>
{children}
{meta?.touched && meta.error ? (
<div tw="text-[#c23c3c]">{meta.error}</div>
) : null}
</div>
);
}
export function TextArea({
label,
title,
children,
...props
}) {
const [field, meta, helpers] = useField(props);
return (
<div css={[title && tw`my-1`]} >
{title && (
<label
{...label}
css={[tw`block text-secondary text-sm font-bold mb-2`, label?.css]}
>
{title}
</label>
)}
<textarea
{...props}
{...field}
tw="bg-primary appearance-none border-2 border-secondary rounded w-full py-2 px-4 text-primary leading-tight focus:outline-none focus:bg-secondary focus:border-accent transition duration-150 "
/>
{children}
{meta?.touched && meta.error ? (
<div tw="text-[#c23c3c]">{meta.error}</div>
) : null}
</div>
);
}
export function CheckBox({
label,
title,
children,
...props
}) {
const [field, meta, helpers] = useField(props);
return (
<div tw="my-1 flex text-secondary">
<input
{...props}
{...field}
type="checkbox"
tw="checked:bg-accent w-6 h-6 rounded-full bg-secondary border-secondary border-4 appearance-none cursor-pointer "
/>
{title != null ? (
<label
{...label}
css={[tw`block font-bold px-2`, label?.css]}
>
{title}
</label>
) : null}
{children}
</div>
);
}

187
Parts/Input.tsx Normal file
View File

@ -0,0 +1,187 @@
import React from "react";
import tw from "twin.macro";
import { useField as useFormikField, useFormikContext, FieldHookConfig } from "formik";
import { IMaskInput, IMaskInputProps, IMaskMixinProps } from 'react-imask';
//const useFormikField = null;
//const useFormikContext = ()=>{};
export function useField<Val=any>(props) {
let getField = useFormikField<Val>;
const context = useFormikContext();
if (!context) {
getField = (propsOrFieldName: FieldHookConfig<any>) => {
return [props, null, null];
}
}
return getField(props);
}
export default function Input(
props: React.InputHTMLAttributes<HTMLInputElement> & FieldHookConfig<any> & {
label?: React.ComponentPropsWithoutRef<"label">;
title?: string;
children?: React.ReactNode;
className?: string;
}
) {
/*name, id*/
let { children, ...otherProps } = props;
const [field, meta, helpers] = useField(otherProps);
return (
<div css={[props.title && tw`my-1`, tw`relative`]} className={props.className}>
{props.title && (
<label
{...props.label}
htmlFor={props.id ?? props.name ?? props.title}
css={[tw`block text-secondary text-sm font-bold mb-2`, props.label?.css]}
>
{/*htmlFor={id ?? name ?? title}*/}
{props.title}
</label>
)}
{/*name={name || title} id={id || name || title}*/}
<input
id={props.id ?? props.name ?? props.title}
{...field}
{...otherProps as any}
tw="bg-primary appearance-none border-2 border-secondary rounded w-full py-2 px-4 text-primary leading-tight focus:outline-none focus:bg-secondary focus:border-accent transition duration-150 "
/>
{props.children}
{meta?.touched && meta.error ? (
<div tw="text-[#c23c3c]">{meta.error}</div>
) : null}
</div>
);
}
export function MaskedInput(
props: Omit<IMaskInputProps<HTMLInputElement>, "validate" | "label"> & FieldHookConfig<any> & {
definitions: { [k: string]: RegExp };
label?: React.ComponentPropsWithoutRef<"label">;
title?: string;
children?: React.ReactNode;
className?: string;
}
) {
/*name, id*/
let { children, validate, ...otherProps } = props;
const [field, meta, helpers] = useField({ ...otherProps, validate });
return (
<div css={[props.title && tw`my-1`, tw`relative`]} className={props.className}>
{props.title && (
<label
{...props.label}
//htmlFor={props.id ?? props.name ?? props.title}
css={[tw`block text-secondary text-sm font-bold mb-2`, props.label?.css]}
>
{/*htmlFor={id ?? name ?? title}*/}
{props.title}
</label>
)}
{/*name={name || title} id={id || name || title}*/}
<IMaskInput
//id={props.id ?? props.name ?? props.title}
{...otherProps as any}
value={field.value}
unmask="typed"
onAccept={
(value, mask) => field.onChange({ target: { value, name: field.name } })
}
onBlur={field.onBlur}
tw="bg-primary appearance-none border-2 border-secondary rounded w-full py-2 px-4 text-primary leading-tight focus:outline-none focus:bg-secondary focus:border-accent transition duration-150 "
/>
{props.children}
{meta?.touched && meta.error ? (
<div tw="text-[#c23c3c]">{meta.error}</div>
) : null}
</div>
);
}
export function TextArea({
title,
children = null,
label = null,
...props
}) {
const [field, meta, helpers] = useField(props);
return (
<div css={[title && tw`my-1`]} >
{title && (
<label
{...label}
css={[tw`block text-secondary text-sm font-bold mb-2`, label?.css]}
>
{title}
</label>
)}
<textarea
{...props}
{...field}
tw="bg-primary appearance-none border-2 border-secondary rounded w-full py-2 px-4 text-primary leading-tight focus:outline-none focus:bg-secondary focus:border-accent transition duration-150 "
/>
{children}
{meta?.touched && meta.error ? (
<div tw="text-[#c23c3c]">{meta.error}</div>
) : null}
</div>
);
}
/**
* Renders a checkbox input with optional label and error message.
*
* @param {Object} props - The properties for the CheckBox component.
* @param {string} props.label - The label for the checkbox.
* @param {string} props.title - The title for the checkbox.
* @param {string} props.className - The class name for the container div.
* @param {ReactNode} props.children - The children components.
* @param {Object} props... - Additional properties for the checkbox input.
* @return {ReactElement} The rendered CheckBox component.
*/
export function CheckBox(
props: (Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "checked"> | Omit<FieldHookConfig<boolean>, "value">) & {
label?: React.ComponentPropsWithoutRef<"label">;
title?: string;
children?: React.ReactNode;
className?: string;
value?: boolean
}
) {
/*name, id*/
const { children, ...otherProps } = props;
const [field, meta, helpers] = useField<boolean>(otherProps);
return (
<div tw="my-1 flex text-secondary">
<input
id={props.id ?? props.name ?? props.title}
checked={otherProps?.value == undefined ? field.value : otherProps?.value}
{...field}
{...otherProps as any}
type="checkbox"
tw="checked:bg-accent w-6 h-6 rounded-full bg-secondary border-secondary border-4 appearance-none cursor-pointer "
/>
{props.title && (
<label
{...props.label}
htmlFor={props.id ?? props.name ?? props.title}
css={[tw`block text-secondary text-sm font-bold mb-2`, props.label?.css]}
>
{/*htmlFor={id ?? name ?? title}*/}
{props.title}
</label>
)}
{children}
</div>
);
}

View File

@ -1,6 +1,5 @@
import Button from "./Button";
import tw, { styled } from "twin.macro";
;
const Btn = styled(Button)(({ active }) => [tw`rounded-none mx-0`, active && tw`bg-accent`]);

37
Parts/PreMaskedInput.tsx Normal file
View File

@ -0,0 +1,37 @@
import { MaskedInput } from "./Input";
export type PreMaskedProps = Omit<React.ComponentPropsWithoutRef<typeof MaskedInput>, "mask" | "definitions">
export function IbanInput(props: PreMaskedProps) {
return <MaskedInput {...props}
mask="XXXX XXXX XXXX XXXX [XXXX] [XXXX] [XXXX] [XX]"
definitions={{ 'X': /[0-9A-Z]/ }}
prepare={(str: string) => str.toUpperCase()}
validate={(iban: string) => {
console.log(iban);
if (iban.length < 15 || iban.length > 34)
return "Invalid length";
// Rearranged IBAN
const rearranged = iban.slice(4) + iban.slice(0, 4);
// Convert letters to numbers (A=10 ... Z=35)
const converted = rearranged.replace(/[A-Z]/g, ch => (ch.charCodeAt(0) - 55).toString());
// Perform mod-97 check
let remainder = converted;
let block;
while (remainder.length > 2) {
block = remainder.slice(0, 9);
remainder = (parseInt(block, 10) % 97).toString() + remainder.slice(block.length);
}
if (parseInt(remainder, 10) % 97 !== 1)
return "Invalid checksum";
return null;
}}
/>
}

View File

@ -4,22 +4,25 @@ import tw, {styled} from "twin.macro"
export function Table(props) {
return <div tw="block w-full overflow-x-auto text-secondary">
<table tw="items-center w-full bg-transparent border-collapse">
<table tw="items-center w-full bg-transparent border-collapse ">
{props.children}
</table>
</div>;
}
const TablePart = styled.td(tw`border-t-0 px-6 align-middle border-l-0 border-r-0 text-xs whitespace-nowrap p-4`)
const TablePart = styled.td(tw`border-t-0 px-6 align-middle border-l-0 border-r-0 text-xs p-4`)
export {TablePart}
const TableHeader = styled.th(tw`px-6 py-3 align-middle text-xs uppercase whitespace-nowrap font-bold text-left text-secondary border-b-2 border-secondary`)
const TableRow = styled.tr(tw`odd:bg-black/10 hover:bg-black/15`)
export {TableRow}
const TableHeader = styled.th(tw`px-6 py-3 align-middle text-xs uppercase font-bold text-left text-secondary border-b-2 border-secondary`)
export {TableHeader}
const TableNote = styled.p(tw`text-xs font-normal italic normal-case`)
export {TableNote}
const TableSidePart = styled.td(tw`border-t-0 px-8 align-middle border-l-0 border-r-0 text-xs whitespace-nowrap p-4 pl-6 py-3 align-middle text-xs uppercase whitespace-nowrap font-bold text-left w-1/3 text-secondary border-r-2 border-secondary`)
const TableSidePart = styled.td(tw`border-t-0 px-8 align-middle border-l-0 border-r-0 text-xs p-4 pl-6 py-3 align-middle text-xs uppercase whitespace-nowrap font-bold text-left w-1/3 text-secondary border-r-2 border-secondary`)
export {TableSidePart}

View File

@ -1,75 +0,0 @@
import { createGlobalStyle } from "styled-components"
import tw, { theme, GlobalStyles as BaseStyles } from "twin.macro"
const CustomStyles = createGlobalStyle`
body {
-webkit-tap-highlight-color: ${theme`colors.accent`};
}
:root{
--primary: #3d3d3d;
--secondary: #535353;
--trinary: #2c2c2c;
--primary-text: #ffffff;
--secondary-text: #a0a0a0;
--primary-invert: #f3f3f3;
--secondary-invert: #dbdbdb;
--trinary-invert: #ffffff;
--primary-invert-text: #3d3d3d;
--secondary-invert-text: #535353;
--accent: #00C800;
--accent-dark: #008f00;
--accent-light: #00ed00;
--accent2: #3080FF;
--accent2-dark: #225ab4;
--accent3: #804000;
--accent3-dark: #472400;
--accent4: #F8B02C;
--accent4-dark: #bd8724;
--accent5: #9E3086;
--accent5-dark: #6b215b;
}
.light\-theme{
--primary: #f3f3f3;
--secondary: #dbdbdb;
--trinary: #ffffff;
--primary-text: #3d3d3d;
--secondary-text: #535353;
--primary-invert: #3d3d3d;
--secondary-invert: #535353;
--trinary-invert: #2c2c2c;
--primary-text-invert: #ffffff;
--secondary-text-invert: #a0a0a0;
}
::-webkit-scrollbar {
width: 0.5rem;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--secondary);
border-radius: 0.25rem;
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent-dark);
}
`
const GlobalStyles = () => {
return <>
<BaseStyles />
<CustomStyles />
</>
}
export default GlobalStyles

View File

@ -1,25 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
export default function StyledComponentsRegistry({ children }) {
// Only create stylesheet once with lazy initial state
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())
useServerInsertedHTML(() => {
const styles = styledComponentsStyleSheet.getStyleElement()
styledComponentsStyleSheet.instance.clearTag()
return <>{styles}</>
})
if (typeof window !== 'undefined') return <>{children}</>
return (
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
{children}
</StyleSheetManager>
)
}