Skip to content

Commit

Permalink
Merge branch 'FZ-88-FZ-89-select' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
TPReal committed Oct 29, 2023
2 parents f4bb535 + f86e54a commit bc53eec
Show file tree
Hide file tree
Showing 24 changed files with 888 additions and 81 deletions.
38 changes: 19 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions resources/js/components/felte-form/ValidationMessages.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ValidationMessage} from "@felte/reporter-solid";
import {useFormContext} from "components/felte-form";
import {useFormContextIfInForm} from "components/felte-form";
import {cx} from "components/utils";
import {Index, VoidComponent, createMemo, on} from "solid-js";
import {HideableSection} from "../ui/HideableSection";
Expand All @@ -20,7 +20,13 @@ export const ValidationMessages: VoidComponent<Props> = (props) => {
{(messages) => <Index each={messages || []}>{(message) => <li>{message()}</li>}</Index>}
</ValidationMessage>
);
const {form} = useFormContext();
const formContext = useFormContextIfInForm();
if (!formContext) {
// Being inside a form or not is not something that can change dynamically, so it's fine to return early.
// eslint-disable-next-line solid/components-return-once
return undefined;
}
const {form} = formContext;
const hasErrors = createMemo(
// For some reason, the "on" part is required for reaction to errors and warnings change.
// Depending directly on form.errors(props.fieldName) does not work reliably for some fields.
Expand Down
50 changes: 50 additions & 0 deletions resources/js/components/ui/InfoIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {A, AnchorProps} from "@solidjs/router";
import {ImInfo} from "solid-icons/im";
import {Match, Switch, VoidComponent} from "solid-js";
import {htmlAttributes, useLangFunc} from "../utils";
import {Button} from "./Button";

interface ButtonProps extends htmlAttributes.button {
href?: undefined;
}

interface LinkProps extends AnchorProps {
href: string;
}

type Props = ButtonProps | LinkProps;

/**
* A tiny blue (i) icon providing more information to a control it is next to.
* It can be a button, or an internal or external link.
*/
export const InfoIcon: VoidComponent<Props> = (props) => {
const t = useLangFunc();
const icon = <ImInfo class="inlineIcon !mb-0.5 text-blue-500" size="16" />;
return (
<Switch>
<Match when={props.href && props}>
{(linkProps) => (
<A
title={t("more_info")}
{...(linkProps() as AnchorProps)}
onClick={(e) => {
// If the info icon is on an active element, we generally don't want to pass the click.
// TODO: Investigate why this doesn't work.
e.stopPropagation();
}}
>
{icon}
</A>
)}
</Match>
<Match when={!props.href && props}>
{(buttonProps) => (
<Button title={t("more_info")} {...buttonProps}>
{icon}
</Button>
)}
</Match>
</Switch>
);
};
9 changes: 6 additions & 3 deletions resources/js/components/ui/Table/Pagination.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
[data-part="next-trigger"],
[data-part="item"],
[data-part="ellipsis"] {
@apply px-1 border rounded flex justify-center items-center;
@apply px-1 border border-input-border rounded flex justify-center items-center;

&[data-disabled] svg {
@apply opacity-20;
&[data-disabled] {
@apply border-opacity-50;
svg {
@apply opacity-20;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ export const TableColumnVisibilityController: VoidComponent = () => {
const api = createMemo(() => popover.connect(state, send, normalizeProps));
return (
<div class={s.columnVisibility}>
<Button class="pressedWithShadow" {...api().triggerProps} disabled={!table.getAllLeafColumns().length}>
<Button
class="pressedWithShadow border-input-border"
{...api().triggerProps}
disabled={!table.getAllLeafColumns().length}
>
{t("tables.choose_columns")}
</Button>
<Portal>
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/ui/Table/TableSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const TableSearch: VoidComponent<ParentProps<Props>> = (allProps) => {
return (
<div {...divProps}>
<input
class="w-full h-full px-2 rounded border"
class="w-full h-full px-2 border border-input-border rounded"
name="table_global_search"
type="search"
placeholder={props.placeholder || t("tables.search")}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const IntFilterControl: VoidComponent<Props> = (props) => {
<div
class={s.valuesSyncer}
classList={{[s.inactive!]: !syncActive()}}
title={syncActive() ? t("tables.filter.click_to_sync_decimal_range") : undefined}
title={syncActive() ? t("tables.filter.click_to_sync_number_range") : undefined}
onClick={() => {
if (lower()) {
setUpper(lower());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {debouncedFilterTextAccessor} from "components/utils";
import {Show, VoidComponent, createComputed, createSignal} from "solid-js";
import {InfoIcon} from "components/ui/InfoIcon";
import {Select, SelectItem} from "components/ui/form/Select";
import {debouncedFilterTextAccessor, useLangFunc} from "components/utils";
import {JSX, VoidComponent, createComputed, createSignal} from "solid-js";
import s from "./ColumnFilterController.module.scss";
import {buildFuzzyTextualColumnFilter} from "./fuzzy_filter";
import {FilterControlProps} from "./types";
Expand All @@ -13,6 +15,7 @@ type Mode = "~" | "=" | ".*";
export const TextualFilterControl: VoidComponent<StringColumnProps> = (props) => {
const [mode, setMode] = createSignal<Mode>("~");
const [text, setText] = createSignal("");
const t = useLangFunc();
createComputed(() => {
if (!props.filter) {
setMode("~");
Expand All @@ -35,20 +38,40 @@ export const TextualFilterControl: VoidComponent<StringColumnProps> = (props) =>
return m satisfies never;
}
});
const items = () => {
const items: SelectItem[] = [];
function addItem(mode: Mode, desc: JSX.Element, infoHref?: string) {
items.push({
value: mode,
text: `${mode} ${desc}`,
label: () => <span class="font-semibold">{mode}</span>,
labelOnList: () => (
<div class="flex items-baseline gap-1">
<span class="font-semibold w-4">{mode}</span>
<span class="grow text-sm text-gray-500">{desc}</span>
{infoHref && <InfoIcon href={infoHref} target="_blank" />}
</div>
),
});
}
addItem("~", t("tables.filter.textual.fuzzy"), "/pomoc/dopasowanie");
if (props.columnType === "string") {
addItem("=", t("tables.filter.textual.eq"));
}
addItem(".*", t("tables.filter.textual.regexp"), "https://support.google.com/a/answer/1371415?hl=pl");
return items;
};
return (
<div class={s.filterLine}>
<select
<Select
class="w-10"
name={`table_filter_op_${props.name}`}
class="border rounded"
items={items()}
value={mode()}
onChange={({target: {value}}) => setMode(value as Mode)}
>
<option value="~">~</option>
<Show when={props.columnType === "string"}>
<option value="=">=</option>
</Show>
<option value=".*">.*</option>
</select>
onValueChange={(value) => setMode(value as Mode)}
nullable={false}
small
/>
<div class={s.wideEdit}>
<input
name={`table_filter_val_${props.name}`}
Expand Down
44 changes: 18 additions & 26 deletions resources/js/components/ui/TranslatedText.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {JSX, Show, VoidComponent, createMemo, mergeProps} from "solid-js";
import {JSX, Match, Show, Switch, VoidComponent, createMemo, mergeProps} from "solid-js";
import {LangEntryFunc, LangPrefixFunc, getLangEntryFunc} from "../utils";
import {Capitalize} from "./Capitalize";

Expand Down Expand Up @@ -52,30 +52,22 @@ export const TranslatedText: VoidComponent<Props> = (allProps) => {
return langPrefixFunc && subKey ? getLangEntryFunc(langPrefixFunc, subKey) : undefined;
};
return (
<Show
when={override()}
fallback={
<Show
when={langFunc()}
fallback={
<Show when={props.fallbackCode} fallback={props.wrapIn()}>
{props.wrapIn(<>{props.fallbackCode}</>)}
</Show>
}
>
{(langFunc) => (
<Show when={langFunc()({defaultValue: ""})} fallback={props.wrapIn(<>{langFunc()()}</>)}>
{(text) => props.wrapIn(<Capitalize text={text()} capitalize={props.capitalize} />)}
</Show>
)}
</Show>
}
>
{(override) => (
<Show when={!override().empty} fallback={props.wrapIn()}>
{props.wrapIn(<>{override().value}</>)}
</Show>
)}
</Show>
<Switch fallback={props.wrapIn()}>
<Match when={override()}>
{(override) => (
<Show when={!override().empty} fallback={props.wrapIn()}>
{props.wrapIn(<>{override().value}</>)}
</Show>
)}
</Match>
<Match when={langFunc()}>
{(langFunc) => (
<Show when={langFunc()({defaultValue: ""})} fallback={props.wrapIn(<>{langFunc()()}</>)}>
{(text) => props.wrapIn(<Capitalize text={text()} capitalize={props.capitalize} />)}
</Show>
)}
</Match>
<Match when={props.fallbackCode}>{props.wrapIn(<>{props.fallbackCode}</>)}</Match>
</Switch>
);
};
2 changes: 1 addition & 1 deletion resources/js/components/ui/form/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const Checkbox: VoidComponent<Props> = (props) => (
type="checkbox"
id={props.name}
{...htmlAttributes.merge(props, {
class: "border border-gray-400 p-2 aria-invalid:border-red-400",
class: "border border-input-border p-2 aria-invalid:border-red-400",
})}
aria-labelledby={labelIdForField(props.name)}
/>{" "}
Expand Down
57 changes: 57 additions & 0 deletions resources/js/components/ui/form/DictionarySelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {NON_NULLABLE, htmlAttributes} from "components/utils";
import {Position, useDictionaries} from "data-access/memo-api/dictionaries";
import {VoidComponent, createMemo, mergeProps, splitProps} from "solid-js";
import {MultipleSelectPropsPart, Select, SelectBaseProps, SelectItem, SingleSelectPropsPart} from "./Select";
import {mergeSelectProps} from "./select_helper";

interface BaseProps
extends htmlAttributes.div,
Pick<SelectBaseProps, "name" | "label" | "disabled" | "placeholder" | "small"> {
/** The id or name of the dictionary. */
dictionary: string;
filterable?: boolean;
/** What to do with disabled dictionary positions. Default: hide. */
disabledItemsMode?: "show" | "showAsActive" | "hide";
/** A function creating the items. It can make use of the default item properties provided. */
itemFunc?: (pos: Position, defItem: () => DefaultDictionarySelectItem) => SelectItem | undefined;
}

type DefaultDictionarySelectItem = Required<Pick<SelectItem, "value" | "text" | "disabled">>;

type Props = BaseProps & (SingleSelectPropsPart | MultipleSelectPropsPart);

const DEFAULT_PROPS = {
filterable: true,
disabledItemsMode: "hide",
itemFunc: (pos: Position, defItem: () => DefaultDictionarySelectItem) => defItem(),
} satisfies Partial<BaseProps>;

export const DictionarySelect: VoidComponent<Props> = (allProps) => {
const defProps = mergeProps(DEFAULT_PROPS, allProps);
const [props, selectProps] = splitProps(defProps, ["dictionary", "filterable", "disabledItemsMode", "itemFunc"]);
const dictionaries = useDictionaries();
const items = createMemo(() => {
const dicts = dictionaries();
if (!dicts) {
return undefined;
}
const dict = dicts.get(props.dictionary);
const positions = props.disabledItemsMode === "hide" ? dict.activePositions : dict.allPositions;
return positions
.map<SelectItem | undefined>((pos) => {
const defItem = (): DefaultDictionarySelectItem => ({
value: pos.id,
text: pos.label,
disabled: props.disabledItemsMode === "show" && pos.disabled,
});
return props.itemFunc(pos, defItem);
})
.filter(NON_NULLABLE);
});
const mergedSelectProps = mergeSelectProps<"items" | "isLoading" | "onFilterChange">(selectProps, {
items: () => items() || [],
isLoading: () => !items(),
onFilterChange: () => (props.filterable ? "internal" : undefined),
});
return <Select {...mergedSelectProps} />;
};
Loading

0 comments on commit bc53eec

Please sign in to comment.