@flowtomic/ui 0.1.2 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blocks/developer-panel/page.d.ts.map +1 -1
- package/dist/blocks/developer-panel/page.js +2 -2
- package/dist/components/atoms/actions/badge/badge.d.ts +1 -1
- package/dist/components/atoms/actions/button/button.d.ts +2 -2
- package/dist/components/atoms/animation/encrypted-text/encrypted-text.d.ts +57 -0
- package/dist/components/atoms/animation/encrypted-text/encrypted-text.d.ts.map +1 -0
- package/dist/components/atoms/animation/encrypted-text/encrypted-text.js +104 -0
- package/dist/components/atoms/animation/encrypted-text/index.d.ts +3 -0
- package/dist/components/atoms/animation/encrypted-text/index.d.ts.map +1 -0
- package/dist/components/atoms/animation/encrypted-text/index.js +1 -0
- package/dist/components/atoms/animation/index.d.ts +2 -0
- package/dist/components/atoms/animation/index.d.ts.map +1 -1
- package/dist/components/atoms/animation/index.js +1 -0
- package/dist/components/atoms/data-display/index.d.ts +2 -0
- package/dist/components/atoms/data-display/index.d.ts.map +1 -1
- package/dist/components/atoms/data-display/index.js +1 -0
- package/dist/components/atoms/data-display/qr-code/index.d.ts +3 -0
- package/dist/components/atoms/data-display/qr-code/index.d.ts.map +1 -0
- package/dist/components/atoms/data-display/qr-code/index.js +1 -0
- package/dist/components/atoms/data-display/qr-code/qr-code.d.ts +18 -0
- package/dist/components/atoms/data-display/qr-code/qr-code.d.ts.map +1 -0
- package/dist/components/atoms/data-display/qr-code/qr-code.js +79 -0
- package/dist/components/atoms/feedback/tooltip/index.d.ts +1 -1
- package/dist/components/atoms/feedback/tooltip/index.d.ts.map +1 -1
- package/dist/components/atoms/feedback/tooltip/index.js +1 -1
- package/dist/components/atoms/feedback/tooltip/tooltip.d.ts +42 -5
- package/dist/components/atoms/feedback/tooltip/tooltip.d.ts.map +1 -1
- package/dist/components/atoms/feedback/tooltip/tooltip.js +188 -5
- package/dist/components/atoms/forms/autocomplete/autocomplete-context.d.ts +29 -0
- package/dist/components/atoms/forms/autocomplete/autocomplete-context.d.ts.map +1 -0
- package/dist/components/atoms/forms/autocomplete/autocomplete-context.js +10 -0
- package/dist/components/atoms/forms/autocomplete/autocomplete-item.d.ts +16 -0
- package/dist/components/atoms/forms/autocomplete/autocomplete-item.d.ts.map +1 -0
- package/dist/components/atoms/forms/autocomplete/autocomplete-item.js +48 -0
- package/dist/components/atoms/forms/autocomplete/autocomplete-section.d.ts +14 -0
- package/dist/components/atoms/forms/autocomplete/autocomplete-section.d.ts.map +1 -0
- package/dist/components/atoms/forms/autocomplete/autocomplete-section.js +13 -0
- package/dist/components/atoms/forms/autocomplete/autocomplete.d.ts +13 -9
- package/dist/components/atoms/forms/autocomplete/autocomplete.d.ts.map +1 -1
- package/dist/components/atoms/forms/autocomplete/autocomplete.js +295 -87
- package/dist/components/atoms/forms/autocomplete/index.d.ts +6 -0
- package/dist/components/atoms/forms/autocomplete/index.d.ts.map +1 -1
- package/dist/components/atoms/forms/autocomplete/index.js +3 -0
- package/dist/components/atoms/forms/index.d.ts +0 -2
- package/dist/components/atoms/forms/index.d.ts.map +1 -1
- package/dist/components/atoms/forms/index.js +0 -1
- package/dist/components/atoms/forms/input/input.d.ts +1 -1
- package/dist/components/atoms/forms/toggle/toggle.d.ts +1 -1
- package/dist/components/atoms/layout/sidebar/sidebar.d.ts +1 -1
- package/dist/components/atoms/navigation/command/command.d.ts +13 -13
- package/dist/components/atoms/navigation/tabs/tabs.js +2 -2
- package/dist/components/molecules/auth/auth-navigation-link/auth-navigation-link.d.ts +1 -11
- package/dist/components/molecules/auth/auth-navigation-link/auth-navigation-link.d.ts.map +1 -1
- package/dist/components/molecules/auth/auth-navigation-link/auth-navigation-link.js +2 -3
- package/dist/components/molecules/forms/autocomplete/autocomplete-context.d.ts +16 -0
- package/dist/components/molecules/forms/autocomplete/autocomplete-context.d.ts.map +1 -0
- package/dist/components/molecules/forms/autocomplete/autocomplete-context.js +14 -0
- package/dist/components/molecules/forms/autocomplete/autocomplete-item.d.ts +23 -0
- package/dist/components/molecules/forms/autocomplete/autocomplete-item.d.ts.map +1 -0
- package/dist/components/molecules/forms/autocomplete/autocomplete-item.js +29 -0
- package/dist/components/molecules/forms/autocomplete/autocomplete-section.d.ts +19 -0
- package/dist/components/molecules/forms/autocomplete/autocomplete-section.d.ts.map +1 -0
- package/dist/components/molecules/forms/autocomplete/autocomplete-section.js +13 -0
- package/dist/components/molecules/forms/autocomplete/autocomplete.d.ts +33 -0
- package/dist/components/molecules/forms/autocomplete/autocomplete.d.ts.map +1 -0
- package/dist/components/molecules/forms/autocomplete/autocomplete.js +101 -0
- package/dist/components/molecules/forms/autocomplete/index.d.ts +9 -0
- package/dist/components/molecules/forms/autocomplete/index.d.ts.map +1 -0
- package/dist/components/molecules/forms/autocomplete/index.js +4 -0
- package/dist/components/molecules/forms/item/item.d.ts +1 -1
- package/dist/components/molecules/forms/text-editor/index.d.ts +3 -0
- package/dist/components/molecules/forms/text-editor/index.d.ts.map +1 -0
- package/dist/components/molecules/forms/text-editor/index.js +1 -0
- package/dist/components/molecules/forms/text-editor/text-editor.d.ts +33 -0
- package/dist/components/molecules/forms/text-editor/text-editor.d.ts.map +1 -0
- package/dist/components/molecules/forms/text-editor/text-editor.js +211 -0
- package/dist/components/molecules/index.d.ts +4 -2
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/index.js +2 -1
- package/dist/components/molecules/navigation/menu-dock/menu-dock.d.ts +5 -0
- package/dist/components/molecules/navigation/menu-dock/menu-dock.d.ts.map +1 -1
- package/dist/components/molecules/navigation/menu-dock/menu-dock.js +103 -10
- package/dist/components/molecules/typography/index.d.ts +0 -2
- package/dist/components/molecules/typography/index.d.ts.map +1 -1
- package/dist/components/molecules/typography/index.js +1 -1
- package/dist/components/organisms/context/context.d.ts +4 -4
- package/dist/components/organisms/document-editor/document-editor.d.ts +43 -0
- package/dist/components/organisms/document-editor/document-editor.d.ts.map +1 -0
- package/dist/components/organisms/document-editor/document-editor.js +144 -0
- package/dist/components/organisms/document-editor/index.d.ts +3 -0
- package/dist/components/organisms/document-editor/index.d.ts.map +1 -0
- package/dist/components/organisms/document-editor/index.js +1 -0
- package/dist/components/organisms/form-layout/form-layout.d.ts +111 -0
- package/dist/components/organisms/form-layout/form-layout.d.ts.map +1 -0
- package/dist/components/organisms/form-layout/form-layout.js +83 -0
- package/dist/components/organisms/form-layout/index.d.ts +2 -0
- package/dist/components/organisms/form-layout/index.d.ts.map +1 -0
- package/dist/components/organisms/form-layout/index.js +1 -0
- package/dist/components/organisms/index.d.ts +4 -0
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/organisms/index.js +2 -0
- package/dist/components/organisms/model-selector/model-selector.d.ts +1 -1
- package/dist/index.js +623 -526
- package/dist/styles/globals.css +489 -0
- package/dist/styles/theme.css +1364 -0
- package/dist/styles/typography.css +430 -0
- package/package.json +45 -18
|
@@ -33,21 +33,11 @@ export interface AuthNavigationLinkProps {
|
|
|
33
33
|
* Classe CSS adicional
|
|
34
34
|
*/
|
|
35
35
|
className?: string;
|
|
36
|
-
/**
|
|
37
|
-
* Se true, aplica efeito de texto animado (AnimatedShinyText) no linkText
|
|
38
|
-
* @default false
|
|
39
|
-
*/
|
|
40
|
-
animated?: boolean;
|
|
41
|
-
/**
|
|
42
|
-
* Largura do shimmer para o efeito animado (apenas quando animated=true)
|
|
43
|
-
* @default 100
|
|
44
|
-
*/
|
|
45
|
-
shimmerWidth?: number;
|
|
46
36
|
}
|
|
47
37
|
/**
|
|
48
38
|
* Componente reutilizável para links de navegação entre páginas de autenticação
|
|
49
39
|
*
|
|
50
40
|
* Usado em Login e Register para navegação entre as páginas
|
|
51
41
|
*/
|
|
52
|
-
export declare function AuthNavigationLink({ text, linkText, to, LinkComponent, className,
|
|
42
|
+
export declare function AuthNavigationLink({ text, linkText, to, LinkComponent, className, }: AuthNavigationLinkProps): import("react/jsx-runtime").JSX.Element;
|
|
53
43
|
//# sourceMappingURL=auth-navigation-link.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-navigation-link.d.ts","sourceRoot":"","sources":["../../../../../src/components/molecules/auth/auth-navigation-link/auth-navigation-link.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-navigation-link.d.ts","sourceRoot":"","sources":["../../../../../src/components/molecules/auth/auth-navigation-link/auth-navigation-link.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IACX;;;OAGG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAClC,EAAE,EAAE,MAAM,CAAC;QACX,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;KAC3B,CAAC,CAAC;IACH;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,QAAQ,EACR,EAAE,EACF,aAAa,EACb,SAAc,GACf,EAAE,uBAAuB,2CAkBzB"}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { AnimatedShinyText } from "../../typography/animated-shiny-text";
|
|
3
2
|
/**
|
|
4
3
|
* Componente reutilizável para links de navegação entre páginas de autenticação
|
|
5
4
|
*
|
|
6
5
|
* Usado em Login e Register para navegação entre as páginas
|
|
7
6
|
*/
|
|
8
|
-
export function AuthNavigationLink({ text, linkText, to, LinkComponent, className = "",
|
|
7
|
+
export function AuthNavigationLink({ text, linkText, to, LinkComponent, className = "", }) {
|
|
9
8
|
const linkClassName = "text-[#5B5FED] dark:text-[#7B7FFF] hover:underline font-semibold";
|
|
10
|
-
const linkContent =
|
|
9
|
+
const linkContent = LinkComponent ? (_jsx(LinkComponent, { to: to, className: linkClassName, children: linkText })) : (_jsx("a", { href: to, className: linkClassName, children: linkText }));
|
|
11
10
|
return (_jsxs("p", { className: `text-center text-sm text-gray-600 dark:text-gray-300 ${className}`, children: [text, " ", linkContent] }));
|
|
12
11
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutocompleteContext - Context para Compound Components
|
|
3
|
+
*
|
|
4
|
+
* Fornece estado e helpers do hook useAutocomplete para subcomponentes
|
|
5
|
+
*/
|
|
6
|
+
import { type UseAutocompleteReturn } from "flowtomic/logic";
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
export interface AutocompleteContextValue extends UseAutocompleteReturn {
|
|
9
|
+
size?: "sm" | "default" | "lg";
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
maxListboxHeight?: string;
|
|
12
|
+
emptyMessage?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const AutocompleteContext: React.Context<AutocompleteContextValue | undefined>;
|
|
15
|
+
export declare function useAutocompleteContext(): AutocompleteContextValue;
|
|
16
|
+
//# sourceMappingURL=autocomplete-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete-context.d.ts","sourceRoot":"","sources":["../../../../../src/components/molecules/forms/autocomplete/autocomplete-context.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,wBAAyB,SAAQ,qBAAqB;IACrE,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,mBAAmB,qDAEpB,CAAC;AAEb,wBAAgB,sBAAsB,IAAI,wBAAwB,CAQjE"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutocompleteContext - Context para Compound Components
|
|
3
|
+
*
|
|
4
|
+
* Fornece estado e helpers do hook useAutocomplete para subcomponentes
|
|
5
|
+
*/
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
export const AutocompleteContext = React.createContext(undefined);
|
|
8
|
+
export function useAutocompleteContext() {
|
|
9
|
+
const context = React.useContext(AutocompleteContext);
|
|
10
|
+
if (!context) {
|
|
11
|
+
throw new Error("useAutocompleteContext must be used within Autocomplete component");
|
|
12
|
+
}
|
|
13
|
+
return context;
|
|
14
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutocompleteItem - Item da lista de opções
|
|
3
|
+
*
|
|
4
|
+
* Subcomponente para modo composição
|
|
5
|
+
*/
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
export interface AutocompleteItemProps extends React.HTMLAttributes<HTMLLIElement> {
|
|
8
|
+
/**
|
|
9
|
+
* Valor do item
|
|
10
|
+
*/
|
|
11
|
+
value: string;
|
|
12
|
+
/**
|
|
13
|
+
* Se o item está desabilitado
|
|
14
|
+
*/
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Conteúdo do item
|
|
18
|
+
*/
|
|
19
|
+
children?: React.ReactNode;
|
|
20
|
+
}
|
|
21
|
+
declare const AutocompleteItem: React.ForwardRefExoticComponent<AutocompleteItemProps & React.RefAttributes<HTMLLIElement>>;
|
|
22
|
+
export { AutocompleteItem };
|
|
23
|
+
//# sourceMappingURL=autocomplete-item.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete-item.d.ts","sourceRoot":"","sources":["../../../../../src/components/molecules/forms/autocomplete/autocomplete-item.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,qBACf,SAAQ,KAAK,CAAC,cAAc,CAAC,aAAa,CAAC;IAC3C;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,QAAA,MAAM,gBAAgB,6FAiDrB,CAAC;AAIF,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import { useAutocompleteContext } from "./autocomplete-context";
|
|
5
|
+
const AutocompleteItem = React.forwardRef(({ className, value, disabled, children, ...props }, ref) => {
|
|
6
|
+
const context = useAutocompleteContext();
|
|
7
|
+
// Encontrar o item correspondente
|
|
8
|
+
const item = context.filteredItems.find((i) => i.value === value);
|
|
9
|
+
if (!item) {
|
|
10
|
+
// Se não encontrou, criar um item temporário para composição
|
|
11
|
+
const tempItem = {
|
|
12
|
+
value,
|
|
13
|
+
label: typeof children === "string" ? children : value,
|
|
14
|
+
disabled,
|
|
15
|
+
};
|
|
16
|
+
const itemProps = context.getItemProps(tempItem, {
|
|
17
|
+
...props,
|
|
18
|
+
className,
|
|
19
|
+
});
|
|
20
|
+
return (_jsx("li", { ...itemProps, ref: ref, className: cn("cursor-pointer px-2 py-1.5", className), children: children || value }));
|
|
21
|
+
}
|
|
22
|
+
const itemProps = context.getItemProps(item, {
|
|
23
|
+
...props,
|
|
24
|
+
className,
|
|
25
|
+
});
|
|
26
|
+
return (_jsx("li", { ...itemProps, ref: ref, className: cn("cursor-pointer rounded-sm px-2 py-1.5 text-sm outline-none transition-colors", "hover:bg-accent focus:bg-accent", item.disabled && "pointer-events-none opacity-50", className), children: children || item.label }));
|
|
27
|
+
});
|
|
28
|
+
AutocompleteItem.displayName = "AutocompleteItem";
|
|
29
|
+
export { AutocompleteItem };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutocompleteSection - Seção de agrupamento de items
|
|
3
|
+
*
|
|
4
|
+
* Subcomponente para modo composição
|
|
5
|
+
*/
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
export interface AutocompleteSectionProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
/**
|
|
9
|
+
* Título da seção
|
|
10
|
+
*/
|
|
11
|
+
title?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Conteúdo da seção (items)
|
|
14
|
+
*/
|
|
15
|
+
children?: React.ReactNode;
|
|
16
|
+
}
|
|
17
|
+
declare const AutocompleteSection: React.ForwardRefExoticComponent<AutocompleteSectionProps & React.RefAttributes<HTMLDivElement>>;
|
|
18
|
+
export { AutocompleteSection };
|
|
19
|
+
//# sourceMappingURL=autocomplete-section.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete-section.d.ts","sourceRoot":"","sources":["../../../../../src/components/molecules/forms/autocomplete/autocomplete-section.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,wBACf,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC5C;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,QAAA,MAAM,mBAAmB,iGAsBvB,CAAC;AAIH,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* AutocompleteSection - Seção de agrupamento de items
|
|
4
|
+
*
|
|
5
|
+
* Subcomponente para modo composição
|
|
6
|
+
*/
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { cn } from "@/lib/utils";
|
|
9
|
+
const AutocompleteSection = React.forwardRef(({ className, title, children, ...props }, ref) => {
|
|
10
|
+
return (_jsxs("div", { ref: ref, ...props, className: cn("space-y-1", className), role: "group", "aria-label": title, children: [title && (_jsx("div", { className: "px-2 py-1.5 text-xs font-semibold text-muted-foreground", children: title })), _jsx("ul", { role: "group", className: "space-y-0.5", children: children })] }));
|
|
11
|
+
});
|
|
12
|
+
AutocompleteSection.displayName = "AutocompleteSection";
|
|
13
|
+
export { AutocompleteSection };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autocomplete Component - Flowtomic UI
|
|
3
|
+
*
|
|
4
|
+
* Componente de autocomplete usando hook headless useAutocomplete
|
|
5
|
+
* Suporta API antiga (options) e composição (Compound Components)
|
|
6
|
+
*/
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { type AutocompleteOption } from "flowtomic/logic";
|
|
9
|
+
export interface AutocompleteProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size" | "value" | "onChange" | "defaultValue"> {
|
|
10
|
+
options?: AutocompleteOption[];
|
|
11
|
+
value?: string;
|
|
12
|
+
defaultValue?: string;
|
|
13
|
+
onValueChange?: (value: string | undefined) => void;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
size?: "sm" | "default" | "lg";
|
|
17
|
+
className?: string;
|
|
18
|
+
inputClassName?: string;
|
|
19
|
+
emptyMessage?: string;
|
|
20
|
+
filterFunction?: (option: AutocompleteOption, searchTerm: string) => boolean;
|
|
21
|
+
allowCustomValue?: boolean;
|
|
22
|
+
isLoading?: boolean;
|
|
23
|
+
maxListboxHeight?: string;
|
|
24
|
+
children?: React.ReactNode;
|
|
25
|
+
}
|
|
26
|
+
export declare const Autocomplete: React.ForwardRefExoticComponent<AutocompleteProps & React.RefAttributes<HTMLInputElement>> & {
|
|
27
|
+
List: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLUListElement> & React.RefAttributes<HTMLUListElement>>;
|
|
28
|
+
Empty: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
29
|
+
Loading: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
30
|
+
Item: React.ForwardRefExoticComponent<import("./autocomplete-item").AutocompleteItemProps & React.RefAttributes<HTMLLIElement>>;
|
|
31
|
+
Section: React.ForwardRefExoticComponent<import("./autocomplete-section").AutocompleteSectionProps & React.RefAttributes<HTMLDivElement>>;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=autocomplete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../../../../../src/components/molecules/forms/autocomplete/autocomplete.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAoC3E,MAAM,WAAW,iBACf,SAAQ,IAAI,CACV,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAC3C,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,cAAc,CAC/C;IAED,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAEpD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAoPD,eAAO,MAAM,YAAY;;;;;;CAMvB,CAAC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Autocomplete Component - Flowtomic UI
|
|
4
|
+
*
|
|
5
|
+
* Componente de autocomplete usando hook headless useAutocomplete
|
|
6
|
+
* Suporta API antiga (options) e composição (Compound Components)
|
|
7
|
+
*/
|
|
8
|
+
// React e types
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
// Hooks Flowtomic
|
|
11
|
+
import { useAutocomplete } from "flowtomic/logic";
|
|
12
|
+
// Ícones
|
|
13
|
+
import { ChevronDownIcon, XIcon } from "lucide-react";
|
|
14
|
+
// Utils
|
|
15
|
+
import { cn } from "@/lib/utils";
|
|
16
|
+
import { inputVariants } from "../../../atoms/forms/input/input";
|
|
17
|
+
import { Spinner } from "../../../atoms/animation/spinner/spinner";
|
|
18
|
+
// Feedback/Overlay
|
|
19
|
+
import { Popover, PopoverContent, PopoverTrigger, } from "../../../atoms/feedback/popover/popover";
|
|
20
|
+
// Context/Composição
|
|
21
|
+
import { AutocompleteContext, } from "./autocomplete-context";
|
|
22
|
+
import { AutocompleteItem } from "./autocomplete-item";
|
|
23
|
+
import { AutocompleteSection } from "./autocomplete-section";
|
|
24
|
+
const AutocompleteRoot = React.forwardRef(({ options = [], value: controlledValue, defaultValue, onValueChange, placeholder = "Selecione uma opção...", disabled = false, size = "default", className, inputClassName, emptyMessage = "Nenhum resultado encontrado.", filterFunction, allowCustomValue = false, isLoading = false, maxListboxHeight = "300px", children, id, ...props }, ref) => {
|
|
25
|
+
// Determinar se está usando composição
|
|
26
|
+
const useComposition = children !== undefined;
|
|
27
|
+
// Hook headless
|
|
28
|
+
const hookReturn = useAutocomplete({
|
|
29
|
+
options: useComposition ? undefined : options,
|
|
30
|
+
value: controlledValue,
|
|
31
|
+
defaultValue,
|
|
32
|
+
onValueChange,
|
|
33
|
+
filterFunction,
|
|
34
|
+
allowCustomValue,
|
|
35
|
+
isLoading,
|
|
36
|
+
emptyMessage,
|
|
37
|
+
children: useComposition ? children : undefined,
|
|
38
|
+
});
|
|
39
|
+
const { inputValue, selectedValue, isOpen, filteredItems, getInputProps, getPopoverProps, getListProps, getItemProps, open, close, clear, } = hookReturn;
|
|
40
|
+
// Input props
|
|
41
|
+
const inputProps = getInputProps({
|
|
42
|
+
...props,
|
|
43
|
+
id,
|
|
44
|
+
placeholder,
|
|
45
|
+
disabled,
|
|
46
|
+
className: cn(inputVariants({ size }), "pr-8", inputClassName),
|
|
47
|
+
});
|
|
48
|
+
// Context value
|
|
49
|
+
const contextValue = {
|
|
50
|
+
...hookReturn,
|
|
51
|
+
size,
|
|
52
|
+
disabled,
|
|
53
|
+
maxListboxHeight,
|
|
54
|
+
emptyMessage,
|
|
55
|
+
};
|
|
56
|
+
return (_jsx(AutocompleteContext.Provider, { value: contextValue, children: _jsx("div", { className: cn("relative w-full", className), children: _jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("div", { className: "relative", children: [_jsx("input", { ...inputProps, ref: ref }), _jsxs("div", { className: "absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1 pointer-events-none", children: [selectedValue && !disabled && (_jsxs("button", { type: "button", onClick: (e) => {
|
|
57
|
+
e.stopPropagation();
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
clear();
|
|
60
|
+
}, onMouseDown: (e) => {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
e.stopPropagation();
|
|
63
|
+
}, className: "text-muted-foreground hover:text-foreground rounded-sm p-1 opacity-70 hover:opacity-100 transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring pointer-events-auto", children: [_jsx(XIcon, { className: "size-4" }), _jsx("span", { className: "sr-only", children: "Limpar" })] })), _jsx(ChevronDownIcon, { className: cn("size-4 text-muted-foreground transition-transform", isOpen && "rotate-180") })] })] }) }), _jsx(PopoverContent, { ...getPopoverProps(), className: cn("bg-popover text-popover-foreground", "data-[state=open]:animate-in data-[state=closed]:animate-out", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "data-[side=bottom]:slide-in-from-top-2", "data-[side=left]:slide-in-from-right-2", "data-[side=right]:slide-in-from-left-2", "data-[side=top]:slide-in-from-bottom-2", "relative z-50", "min-w-[var(--radix-popover-trigger-width)] w-full", "overflow-hidden rounded-md border shadow-md p-0"), align: "start", sideOffset: 4, onOpenAutoFocus: (e) => {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
}, children: _jsx("div", { style: { maxHeight: maxListboxHeight }, className: "overflow-auto", children: isLoading ? (_jsxs("div", { className: "py-6 text-center text-sm text-muted-foreground", children: [_jsx(Spinner, { className: "mx-auto mb-2" }), "Carregando..."] })) : useComposition ? (children) : filteredItems.length > 0 ? (_jsx("ul", { ...getListProps(), className: "p-1 outline-none", children: filteredItems.map((item) => {
|
|
66
|
+
const itemProps = getItemProps(item);
|
|
67
|
+
return (_jsx("li", { ...itemProps, className: cn("cursor-pointer rounded-sm px-2 py-1.5 text-sm outline-none transition-colors", "hover:bg-accent focus:bg-accent", item.disabled && "pointer-events-none opacity-50", itemProps.className), children: item.label }, item.value));
|
|
68
|
+
}) })) : (_jsx("div", { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage })) }) })] }) }) }));
|
|
69
|
+
});
|
|
70
|
+
AutocompleteRoot.displayName = "Autocomplete";
|
|
71
|
+
// Subcomponentes para composição
|
|
72
|
+
const AutocompleteList = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
73
|
+
const context = React.useContext(AutocompleteContext);
|
|
74
|
+
if (!context) {
|
|
75
|
+
throw new Error("AutocompleteList must be used within Autocomplete component");
|
|
76
|
+
}
|
|
77
|
+
const { getListProps } = context;
|
|
78
|
+
return (_jsx("ul", { ...getListProps(), ...props, ref: ref, className: cn("p-1 outline-none", className), children: children }));
|
|
79
|
+
});
|
|
80
|
+
AutocompleteList.displayName = "AutocompleteList";
|
|
81
|
+
const AutocompleteEmpty = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
82
|
+
const context = React.useContext(AutocompleteContext);
|
|
83
|
+
if (!context) {
|
|
84
|
+
throw new Error("AutocompleteEmpty must be used within Autocomplete component");
|
|
85
|
+
}
|
|
86
|
+
const { emptyMessage } = context;
|
|
87
|
+
return (_jsx("div", { ref: ref, ...props, className: cn("py-6 text-center text-sm text-muted-foreground", className), children: children || emptyMessage }));
|
|
88
|
+
});
|
|
89
|
+
AutocompleteEmpty.displayName = "AutocompleteEmpty";
|
|
90
|
+
const AutocompleteLoading = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
91
|
+
return (_jsx("div", { ref: ref, ...props, className: cn("py-6 text-center text-sm text-muted-foreground", className), children: children || (_jsxs(_Fragment, { children: [_jsx(Spinner, { className: "mx-auto mb-2" }), "Carregando..."] })) }));
|
|
92
|
+
});
|
|
93
|
+
AutocompleteLoading.displayName = "AutocompleteLoading";
|
|
94
|
+
// Exportar componente composto
|
|
95
|
+
export const Autocomplete = Object.assign(AutocompleteRoot, {
|
|
96
|
+
List: AutocompleteList,
|
|
97
|
+
Empty: AutocompleteEmpty,
|
|
98
|
+
Loading: AutocompleteLoading,
|
|
99
|
+
Item: AutocompleteItem,
|
|
100
|
+
Section: AutocompleteSection,
|
|
101
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type { AutocompleteProps } from "./autocomplete";
|
|
2
|
+
export { Autocomplete } from "./autocomplete";
|
|
3
|
+
export type { AutocompleteItemProps } from "./autocomplete-item";
|
|
4
|
+
export { AutocompleteItem } from "./autocomplete-item";
|
|
5
|
+
export type { AutocompleteSectionProps } from "./autocomplete-section";
|
|
6
|
+
export { AutocompleteSection } from "./autocomplete-section";
|
|
7
|
+
export type { AutocompleteContextValue } from "./autocomplete-context";
|
|
8
|
+
export { useAutocompleteContext } from "./autocomplete-context";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/molecules/forms/autocomplete/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,YAAY,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,YAAY,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -24,7 +24,7 @@ declare namespace Item {
|
|
|
24
24
|
var displayName: string;
|
|
25
25
|
}
|
|
26
26
|
declare const itemMediaVariants: (props?: ({
|
|
27
|
-
variant?: "
|
|
27
|
+
variant?: "image" | "default" | "icon" | null | undefined;
|
|
28
28
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
29
29
|
export interface ItemMediaProps extends React.ComponentProps<"div">, VariantProps<typeof itemMediaVariants> {
|
|
30
30
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/molecules/forms/text-editor/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAC9F,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TextEditor } from "./text-editor";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TextEditor - Flowtomic UI
|
|
3
|
+
*
|
|
4
|
+
* Editor de texto rico com TipTap + modos opcionais Markdown e Preview.
|
|
5
|
+
* Regras de modo:
|
|
6
|
+
* - Somente 'rich' => sem abas, apenas editor rico
|
|
7
|
+
* - Somente 'markdown' => textarea markdown + (preview opcional)
|
|
8
|
+
* - Somente 'preview' => somente visualização
|
|
9
|
+
* - Múltiplos modos => abas para cada modo disponível
|
|
10
|
+
*/
|
|
11
|
+
import * as React from "react";
|
|
12
|
+
export type TextEditorMode = "rich" | "markdown" | "preview";
|
|
13
|
+
export type TextEditorToolbarAction = "bold" | "italic" | "strike" | "code" | "h1" | "h2" | "h3" | "bulletList" | "orderedList" | "blockquote" | "alignLeft" | "alignCenter" | "alignRight" | "textColor" | "image";
|
|
14
|
+
declare const DEFAULT_ACTIONS: TextEditorToolbarAction[];
|
|
15
|
+
type BaseDivProps = Omit<React.HTMLAttributes<HTMLDivElement>, "onChange">;
|
|
16
|
+
export interface TextEditorProps extends BaseDivProps {
|
|
17
|
+
value?: string;
|
|
18
|
+
defaultValue?: string;
|
|
19
|
+
onChange?: (value: string) => void;
|
|
20
|
+
placeholder?: string;
|
|
21
|
+
editable?: boolean;
|
|
22
|
+
mode?: TextEditorMode;
|
|
23
|
+
onModeChange?: (mode: TextEditorMode) => void;
|
|
24
|
+
toolbar?: boolean;
|
|
25
|
+
allowedActions?: TextEditorToolbarAction[];
|
|
26
|
+
onUploadImage?: (file: File) => Promise<string> | string;
|
|
27
|
+
outputFormat?: "markdown" | "text";
|
|
28
|
+
/** Modos disponíveis. 'markdown' sempre inclui 'preview' automaticamente */
|
|
29
|
+
availableModes?: ("rich" | "markdown")[];
|
|
30
|
+
}
|
|
31
|
+
export declare const TextEditor: React.ForwardRefExoticComponent<TextEditorProps & React.RefAttributes<HTMLDivElement>>;
|
|
32
|
+
export { DEFAULT_ACTIONS };
|
|
33
|
+
//# sourceMappingURL=text-editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-editor.d.ts","sourceRoot":"","sources":["../../../../../src/components/molecules/forms/text-editor/text-editor.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA+C/B,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;AAE7D,MAAM,MAAM,uBAAuB,GAC/B,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GACrC,IAAI,GAAG,IAAI,GAAG,IAAI,GAClB,YAAY,GAAG,aAAa,GAAG,YAAY,GAC3C,WAAW,GAAG,aAAa,GAAG,YAAY,GAC1C,WAAW,GACX,OAAO,CAAC;AAEZ,QAAA,MAAM,eAAe,EAAE,uBAAuB,EAgB7C,CAAC;AA2DF,KAAK,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,CAAC;AAE3E,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,uBAAuB,EAAE,CAAC;IAC3C,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IACzD,YAAY,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACnC,4EAA4E;IAC5E,cAAc,CAAC,EAAE,CAAC,MAAM,GAAG,UAAU,CAAC,EAAE,CAAC;CAC1C;AAED,eAAO,MAAM,UAAU,wFAmUtB,CAAC;AA8BF,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* TextEditor - Flowtomic UI
|
|
4
|
+
*
|
|
5
|
+
* Editor de texto rico com TipTap + modos opcionais Markdown e Preview.
|
|
6
|
+
* Regras de modo:
|
|
7
|
+
* - Somente 'rich' => sem abas, apenas editor rico
|
|
8
|
+
* - Somente 'markdown' => textarea markdown + (preview opcional)
|
|
9
|
+
* - Somente 'preview' => somente visualização
|
|
10
|
+
* - Múltiplos modos => abas para cada modo disponível
|
|
11
|
+
*/
|
|
12
|
+
import * as React from "react";
|
|
13
|
+
import { useEditor, EditorContent } from "@tiptap/react";
|
|
14
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
15
|
+
import Placeholder from "@tiptap/extension-placeholder";
|
|
16
|
+
import TextStyle from "@tiptap/extension-text-style";
|
|
17
|
+
import Color from "@tiptap/extension-color";
|
|
18
|
+
import TextAlign from "@tiptap/extension-text-align";
|
|
19
|
+
import Image from "@tiptap/extension-image";
|
|
20
|
+
import { Markdown } from "tiptap-markdown";
|
|
21
|
+
import { Bold, Code, Heading1, Heading2, Heading3, ImageIcon, Italic, ListOrdered, List as ListIcon, Quote, Strikethrough, AlignLeft, AlignCenter, AlignRight, Eye, FileText, Palette, } from "lucide-react";
|
|
22
|
+
import { Streamdown } from "streamdown";
|
|
23
|
+
import { cn } from "@/lib/utils";
|
|
24
|
+
import { Button, Separator, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Popover, PopoverContent, PopoverTrigger, } from "../../../atoms";
|
|
25
|
+
const DEFAULT_ACTIONS = [
|
|
26
|
+
"bold",
|
|
27
|
+
"italic",
|
|
28
|
+
"strike",
|
|
29
|
+
"code",
|
|
30
|
+
"h1",
|
|
31
|
+
"h2",
|
|
32
|
+
"h3",
|
|
33
|
+
"bulletList",
|
|
34
|
+
"orderedList",
|
|
35
|
+
"blockquote",
|
|
36
|
+
"alignLeft",
|
|
37
|
+
"alignCenter",
|
|
38
|
+
"alignRight",
|
|
39
|
+
"textColor",
|
|
40
|
+
"image",
|
|
41
|
+
];
|
|
42
|
+
const PRESET_COLORS = [
|
|
43
|
+
{ label: "Preto", value: "#000000" },
|
|
44
|
+
{ label: "Cinza escuro", value: "#374151" },
|
|
45
|
+
{ label: "Cinza", value: "#6B7280" },
|
|
46
|
+
{ label: "Cinza claro", value: "#9CA3AF" },
|
|
47
|
+
{ label: "Vermelho", value: "#EF4444" },
|
|
48
|
+
{ label: "Laranja", value: "#F97316" },
|
|
49
|
+
{ label: "Amarelo", value: "#EAB308" },
|
|
50
|
+
{ label: "Verde", value: "#10B981" },
|
|
51
|
+
{ label: "Azul", value: "#3B82F6" },
|
|
52
|
+
{ label: "Índigo", value: "#6366F1" },
|
|
53
|
+
{ label: "Roxo", value: "#A855F7" },
|
|
54
|
+
{ label: "Rosa", value: "#EC4899" },
|
|
55
|
+
{ label: "Vermelho escuro", value: "#991B1B" },
|
|
56
|
+
{ label: "Laranja escuro", value: "#9A3412" },
|
|
57
|
+
{ label: "Amarelo escuro", value: "#854D0E" },
|
|
58
|
+
{ label: "Verde escuro", value: "#065F46" },
|
|
59
|
+
{ label: "Azul escuro", value: "#1E40AF" },
|
|
60
|
+
{ label: "Índigo escuro", value: "#3730A3" },
|
|
61
|
+
{ label: "Roxo escuro", value: "#6B21A8" },
|
|
62
|
+
{ label: "Rosa escuro", value: "#9F1239" },
|
|
63
|
+
{ label: "Vermelho claro", value: "#FCA5A5" },
|
|
64
|
+
{ label: "Laranja claro", value: "#FDBA74" },
|
|
65
|
+
{ label: "Amarelo claro", value: "#FDE047" },
|
|
66
|
+
{ label: "Verde claro", value: "#6EE7B7" },
|
|
67
|
+
{ label: "Azul claro", value: "#93C5FD" },
|
|
68
|
+
{ label: "Índigo claro", value: "#A5B4FC" },
|
|
69
|
+
{ label: "Roxo claro", value: "#D8B4FE" },
|
|
70
|
+
{ label: "Rosa claro", value: "#F9A8D4" },
|
|
71
|
+
];
|
|
72
|
+
function useMarkdownState(controlled, initial) {
|
|
73
|
+
const isControlled = controlled !== undefined;
|
|
74
|
+
const [value, setValue] = React.useState(initial);
|
|
75
|
+
React.useEffect(() => {
|
|
76
|
+
if (isControlled)
|
|
77
|
+
setValue(controlled);
|
|
78
|
+
}, [controlled, isControlled]);
|
|
79
|
+
return { value, setValue, isControlled };
|
|
80
|
+
}
|
|
81
|
+
function stripMarkdown(md) {
|
|
82
|
+
return md
|
|
83
|
+
.replace(/`{1,3}[^`]*`/g, (m) => m.replace(/`/g, ""))
|
|
84
|
+
.replace(/^#{1,6}\s+/gm, "")
|
|
85
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
86
|
+
.replace(/\*([^*]+)\*/g, "$1")
|
|
87
|
+
.replace(/~~([^~]+)~~/g, "$1")
|
|
88
|
+
.replace(/>\s+/g, "")
|
|
89
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
90
|
+
.replace(/^\s*[-*+]\s+/gm, "")
|
|
91
|
+
.replace(/^\s*\d+\.\s+/gm, "")
|
|
92
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, "")
|
|
93
|
+
.replace(/\[[^\]]*\]\([^)]*\)/g, (m) => m.match(/\[([^\]]+)\]/)?.[1] || "")
|
|
94
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
95
|
+
.trim();
|
|
96
|
+
}
|
|
97
|
+
export const TextEditor = React.forwardRef(({ className, value: controlledValue, defaultValue, onChange, placeholder = "Escreva algo...", editable = true, mode: controlledMode, onModeChange, toolbar = true, allowedActions = DEFAULT_ACTIONS, onUploadImage, outputFormat = "markdown", availableModes = ["rich", "markdown"], ...props }, ref) => {
|
|
98
|
+
const initial = controlledValue ?? defaultValue ?? "";
|
|
99
|
+
const { value: internalMarkdown, setValue: setInternalMarkdown, isControlled } = useMarkdownState(controlledValue, initial);
|
|
100
|
+
// Expandir modos: se markdown está presente, adicionar preview automaticamente
|
|
101
|
+
const effectiveModes = React.useMemo(() => {
|
|
102
|
+
const unique = availableModes.filter((m, i, arr) => arr.indexOf(m) === i);
|
|
103
|
+
const expanded = [];
|
|
104
|
+
if (unique.includes("rich"))
|
|
105
|
+
expanded.push("rich");
|
|
106
|
+
if (unique.includes("markdown")) {
|
|
107
|
+
expanded.push("markdown");
|
|
108
|
+
expanded.push("preview"); // markdown sempre vem com preview
|
|
109
|
+
}
|
|
110
|
+
return expanded.length > 0 ? expanded : ["rich"];
|
|
111
|
+
}, [availableModes]);
|
|
112
|
+
const [mode, setMode] = React.useState(controlledMode && effectiveModes.includes(controlledMode) ? controlledMode : effectiveModes[0]);
|
|
113
|
+
const setModeSafe = React.useCallback((m) => {
|
|
114
|
+
if (!effectiveModes.includes(m))
|
|
115
|
+
return;
|
|
116
|
+
setMode(m);
|
|
117
|
+
onModeChange?.(m);
|
|
118
|
+
}, [effectiveModes, onModeChange]);
|
|
119
|
+
const editor = useEditor({
|
|
120
|
+
editable,
|
|
121
|
+
extensions: [
|
|
122
|
+
Markdown.configure({ html: false, transformPastedText: true, transformCopiedText: true }),
|
|
123
|
+
StarterKit.configure({ heading: { levels: [1, 2, 3] } }),
|
|
124
|
+
TextStyle,
|
|
125
|
+
Color,
|
|
126
|
+
TextAlign.configure({ types: ["heading", "paragraph"] }),
|
|
127
|
+
Placeholder.configure({ placeholder }),
|
|
128
|
+
Image,
|
|
129
|
+
],
|
|
130
|
+
content: internalMarkdown ? undefined : "",
|
|
131
|
+
onUpdate: ({ editor }) => {
|
|
132
|
+
try {
|
|
133
|
+
// @ts-expect-error acesso ao storage markdown da extensão
|
|
134
|
+
const md = editor?.storage?.markdown?.getMarkdown?.() ?? editor.commands.getMarkdown?.();
|
|
135
|
+
if (typeof md === "string") {
|
|
136
|
+
if (!isControlled)
|
|
137
|
+
setInternalMarkdown(md);
|
|
138
|
+
if (outputFormat === "text") {
|
|
139
|
+
onChange?.(editor.getText());
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
onChange?.(md);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
/* silencioso */
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
React.useEffect(() => {
|
|
152
|
+
if (!editor)
|
|
153
|
+
return;
|
|
154
|
+
if (controlledValue === undefined)
|
|
155
|
+
return;
|
|
156
|
+
const nextMarkdown = controlledValue; // já é markdown ou texto; se texto não temos conversão -> usar direto
|
|
157
|
+
// @ts-expect-error comando da extensão markdown
|
|
158
|
+
if (editor.commands?.setMarkdown) {
|
|
159
|
+
// @ts-expect-error
|
|
160
|
+
editor.commands.setMarkdown(nextMarkdown);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
editor.commands.setContent(nextMarkdown);
|
|
164
|
+
}
|
|
165
|
+
}, [editor, controlledValue]);
|
|
166
|
+
const handleImageInsert = React.useCallback(async () => {
|
|
167
|
+
if (!editor)
|
|
168
|
+
return;
|
|
169
|
+
const input = document.createElement("input");
|
|
170
|
+
input.type = "file";
|
|
171
|
+
input.accept = "image/*";
|
|
172
|
+
input.onchange = async () => {
|
|
173
|
+
const file = input.files?.[0];
|
|
174
|
+
if (!file)
|
|
175
|
+
return;
|
|
176
|
+
const url = onUploadImage ? await onUploadImage(file) : URL.createObjectURL(file);
|
|
177
|
+
editor.chain().focus().setImage({ src: url, alt: file.name }).run();
|
|
178
|
+
};
|
|
179
|
+
input.click();
|
|
180
|
+
}, [editor, onUploadImage]);
|
|
181
|
+
const Toolbar = React.useMemo(() => {
|
|
182
|
+
if (!toolbar)
|
|
183
|
+
return null;
|
|
184
|
+
const e = editor;
|
|
185
|
+
const run = (cb) => () => e && cb(e);
|
|
186
|
+
return (_jsxs("div", { className: "flex flex-wrap items-center gap-1 rounded-md border bg-muted/40 p-1", children: [allowedActions.includes("bold") && (_jsx(ToolbarButton, { label: "Negrito", active: e?.isActive("bold"), onClick: run((ed) => ed.chain().focus().toggleBold().run()), children: _jsx(Bold, { size: 14 }) })), allowedActions.includes("italic") && (_jsx(ToolbarButton, { label: "It\u00E1lico", active: e?.isActive("italic"), onClick: run((ed) => ed.chain().focus().toggleItalic().run()), children: _jsx(Italic, { size: 14 }) })), allowedActions.includes("strike") && (_jsx(ToolbarButton, { label: "Tachado", active: e?.isActive("strike"), onClick: run((ed) => ed.chain().focus().toggleStrike().run()), children: _jsx(Strikethrough, { size: 14 }) })), allowedActions.includes("code") && (_jsx(ToolbarButton, { label: "C\u00F3digo inline", active: e?.isActive("code"), onClick: run((ed) => ed.chain().focus().toggleCode().run()), children: _jsx(Code, { size: 14 }) })), _jsx(Separator, { orientation: "vertical", className: "mx-1 h-6" }), allowedActions.includes("h1") && (_jsx(ToolbarButton, { label: "T\u00EDtulo H1", active: e?.isActive("heading", { level: 1 }), onClick: run((ed) => ed.chain().focus().toggleHeading({ level: 1 }).run()), children: _jsx(Heading1, { size: 14 }) })), allowedActions.includes("h2") && (_jsx(ToolbarButton, { label: "T\u00EDtulo H2", active: e?.isActive("heading", { level: 2 }), onClick: run((ed) => ed.chain().focus().toggleHeading({ level: 2 }).run()), children: _jsx(Heading2, { size: 14 }) })), allowedActions.includes("h3") && (_jsx(ToolbarButton, { label: "T\u00EDtulo H3", active: e?.isActive("heading", { level: 3 }), onClick: run((ed) => ed.chain().focus().toggleHeading({ level: 3 }).run()), children: _jsx(Heading3, { size: 14 }) })), _jsx(Separator, { orientation: "vertical", className: "mx-1 h-6" }), allowedActions.includes("bulletList") && (_jsx(ToolbarButton, { label: "Lista n\u00E3o ordenada", active: e?.isActive("bulletList"), onClick: run((ed) => ed.chain().focus().toggleBulletList().run()), children: _jsx(ListIcon, { size: 14 }) })), allowedActions.includes("orderedList") && (_jsx(ToolbarButton, { label: "Lista ordenada", active: e?.isActive("orderedList"), onClick: run((ed) => ed.chain().focus().toggleOrderedList().run()), children: _jsx(ListOrdered, { size: 14 }) })), allowedActions.includes("blockquote") && (_jsx(ToolbarButton, { label: "Cita\u00E7\u00E3o", active: e?.isActive("blockquote"), onClick: run((ed) => ed.chain().focus().toggleBlockquote().run()), children: _jsx(Quote, { size: 14 }) })), _jsx(Separator, { orientation: "vertical", className: "mx-1 h-6" }), allowedActions.includes("alignLeft") && (_jsx(ToolbarButton, { label: "Alinhar \u00E0 esquerda", active: e?.isActive({ textAlign: "left" }), onClick: run((ed) => ed.chain().focus().setTextAlign("left").run()), children: _jsx(AlignLeft, { size: 14 }) })), allowedActions.includes("alignCenter") && (_jsx(ToolbarButton, { label: "Centralizar", active: e?.isActive({ textAlign: "center" }), onClick: run((ed) => ed.chain().focus().setTextAlign("center").run()), children: _jsx(AlignCenter, { size: 14 }) })), allowedActions.includes("alignRight") && (_jsx(ToolbarButton, { label: "Alinhar \u00E0 direita", active: e?.isActive({ textAlign: "right" }), onClick: run((ed) => ed.chain().focus().setTextAlign("right").run()), children: _jsx(AlignRight, { size: 14 }) })), _jsx(Separator, { orientation: "vertical", className: "mx-1 h-6" }), allowedActions.includes("textColor") && (_jsxs(Popover, { children: [_jsx(TooltipProvider, { delayDuration: 300, skipDelayDuration: 0, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 p-0", children: _jsx(Palette, { size: 14 }) }) }) }), _jsx(TooltipContent, { sideOffset: 4, children: "Cor do texto" })] }) }), _jsx(PopoverContent, { className: "w-auto p-2", children: _jsxs("div", { className: "grid grid-cols-6 gap-1", children: [PRESET_COLORS.map((color) => (_jsx("button", { type: "button", onClick: () => e?.chain().focus().setColor(color.value).run(), className: "size-6 rounded border border-border hover:scale-110 transition-transform", style: { backgroundColor: color.value }, title: color.label }, color.value))), _jsx("button", { type: "button", onClick: () => e?.chain().focus().unsetColor().run(), className: "size-6 rounded border border-border hover:scale-110 transition-transform bg-background flex items-center justify-center text-xs", title: "Remover cor", children: "\u2715" })] }) })] })), _jsx(Separator, { orientation: "vertical", className: "mx-1 h-6" }), allowedActions.includes("image") && (_jsx(ToolbarButton, { label: "Inserir imagem", onClick: handleImageInsert, children: _jsx(ImageIcon, { size: 14 }) }))] }));
|
|
187
|
+
}, [editor, toolbar, allowedActions, handleImageInsert]);
|
|
188
|
+
const richView = (_jsxs("div", { className: "grid gap-2", children: [Toolbar, _jsx("div", { className: cn("prose dark:prose-invert prose-sm max-w-none rounded-md border bg-background p-3", "focus-within:ring-ring/50 focus-within:ring-[3px]"), children: _jsx(EditorContent, { editor: editor, className: "min-h-40" }) })] }));
|
|
189
|
+
const markdownEditor = (_jsx(Textarea, { className: "font-mono text-sm", rows: 14, value: internalMarkdown, onChange: (e) => {
|
|
190
|
+
const next = e.target.value;
|
|
191
|
+
setInternalMarkdown(next);
|
|
192
|
+
if (outputFormat === "text") {
|
|
193
|
+
onChange?.(stripMarkdown(next));
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
onChange?.(next);
|
|
197
|
+
}
|
|
198
|
+
}, placeholder: placeholder }));
|
|
199
|
+
const previewView = (_jsx("div", { className: "rounded-md border bg-background p-3", children: _jsx(Streamdown, { children: internalMarkdown }) }));
|
|
200
|
+
// Renderização sem abas (apenas rich sozinho)
|
|
201
|
+
if (effectiveModes.length === 1 && effectiveModes[0] === "rich") {
|
|
202
|
+
return (_jsx("div", { ref: ref, className: cn("grid gap-2", className), ...props, children: richView }));
|
|
203
|
+
}
|
|
204
|
+
// Com abas (markdown sempre inclui preview automaticamente)
|
|
205
|
+
return (_jsx("div", { ref: ref, className: cn("grid gap-2", className), ...props, children: _jsxs(Tabs, { value: mode, onValueChange: (v) => setModeSafe(v), children: [_jsxs(TabsList, { className: "w-fit", children: [effectiveModes.includes("rich") && (_jsx(TabsTrigger, { value: "rich", children: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx(Eye, { className: "size-3.5" }), " Rich"] }) })), effectiveModes.includes("markdown") && (_jsx(TabsTrigger, { value: "markdown", children: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx(FileText, { className: "size-3.5" }), " Markdown"] }) })), effectiveModes.includes("preview") && (_jsx(TabsTrigger, { value: "preview", children: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx(Eye, { className: "size-3.5" }), " Preview"] }) }))] }), effectiveModes.includes("rich") && (_jsx(TabsContent, { value: "rich", className: "mt-2", children: richView })), effectiveModes.includes("markdown") && (_jsx(TabsContent, { value: "markdown", className: "mt-2", children: markdownEditor })), effectiveModes.includes("preview") && (_jsx(TabsContent, { value: "preview", className: "mt-2", children: previewView }))] }) }));
|
|
206
|
+
});
|
|
207
|
+
TextEditor.displayName = "TextEditor";
|
|
208
|
+
function ToolbarButton({ label, active, className, children, ...rest }) {
|
|
209
|
+
return (_jsx(TooltipProvider, { delayDuration: 300, skipDelayDuration: 0, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", className: cn("h-7 w-7 p-0", active && "bg-accent text-accent-foreground", className), ...rest, children: children }) }), _jsx(TooltipContent, { sideOffset: 4, children: label })] }) }));
|
|
210
|
+
}
|
|
211
|
+
export { DEFAULT_ACTIONS };
|