tycho-components 0.0.1

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.
Files changed (67) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc.cjs +28 -0
  3. package/.eslintrc.json +31 -0
  4. package/.gitlab-ci.yml +14 -0
  5. package/.storybook/main.ts +32 -0
  6. package/.storybook/preview-head.html +4 -0
  7. package/.storybook/preview.css +6 -0
  8. package/.storybook/preview.tsx +29 -0
  9. package/README.md +93 -0
  10. package/package.json +66 -0
  11. package/src/AppColorpicker/AppColorpicker.tsx +69 -0
  12. package/src/AppColorpicker/index.tsx +3 -0
  13. package/src/AppColorpicker/style.scss +38 -0
  14. package/src/AppEditable/AppEditable.tsx +280 -0
  15. package/src/AppEditable/AppEditableField.ts +7 -0
  16. package/src/AppEditable/FormField.ts +26 -0
  17. package/src/AppEditable/FormFieldOption.ts +38 -0
  18. package/src/AppEditable/index.tsx +3 -0
  19. package/src/AppEditable/style.scss +94 -0
  20. package/src/AppModal/AppModal.tsx +93 -0
  21. package/src/AppModal/AppModalConfirm.tsx +62 -0
  22. package/src/AppModal/AppModalRemove.tsx +51 -0
  23. package/src/AppModal/index.tsx +3 -0
  24. package/src/AppModal/style.scss +65 -0
  25. package/src/AppToast/AppToast.tsx +94 -0
  26. package/src/AppToast/ToastMessage.ts +9 -0
  27. package/src/AppToast/index.tsx +3 -0
  28. package/src/AppToast/style.scss +0 -0
  29. package/src/Dummy/Dummy.stories.tsx +21 -0
  30. package/src/Dummy/Dummy.tsx +16 -0
  31. package/src/Dummy/index.tsx +3 -0
  32. package/src/Dummy/styles.scss +6 -0
  33. package/src/Participants/ParticipantCreate/ParticipantCreate.tsx +86 -0
  34. package/src/Participants/ParticipantCreate/index.tsx +3 -0
  35. package/src/Participants/ParticipantCreate/style.scss +32 -0
  36. package/src/Participants/ParticipantRemove/ParticipantRemove.tsx +51 -0
  37. package/src/Participants/ParticipantRemove/index.tsx +3 -0
  38. package/src/Participants/ParticipantRemove/style.scss +32 -0
  39. package/src/Participants/Participants.stories.tsx +45 -0
  40. package/src/Participants/Participants.tsx +145 -0
  41. package/src/Participants/index.tsx +3 -0
  42. package/src/Participants/style.scss +44 -0
  43. package/src/Participants/types/Participant.ts +43 -0
  44. package/src/Participants/types/ParticipantService.ts +18 -0
  45. package/src/configs/CommonContext.tsx +23 -0
  46. package/src/configs/CookieStorage.ts +36 -0
  47. package/src/configs/Localization.ts +28 -0
  48. package/src/configs/MessageUtils.ts +60 -0
  49. package/src/configs/Storage.ts +21 -0
  50. package/src/configs/api.ts +49 -0
  51. package/src/configs/localization/CommonTexts.ts +26 -0
  52. package/src/configs/localization/ParticipantsTexts.ts +40 -0
  53. package/src/configs/store/actions.ts +12 -0
  54. package/src/configs/store/reducer.ts +22 -0
  55. package/src/configs/store/store.ts +9 -0
  56. package/src/configs/store/types.ts +16 -0
  57. package/src/index.ts +4 -0
  58. package/src/react-app-env.d.ts +5 -0
  59. package/src/styles/_variables.scss +67 -0
  60. package/src/styles/bootstrap.min.css +9871 -0
  61. package/src/styles/main.scss +57 -0
  62. package/src/vite-env.d.ts +13 -0
  63. package/stories/Configure.mdx +171 -0
  64. package/stories/StorybookUtils.tsx +40 -0
  65. package/tsconfig.json +31 -0
  66. package/tsconfig.node.json +10 -0
  67. package/vite.config.ts +30 -0
@@ -0,0 +1,43 @@
1
+ import FormField from '@/AppEditable/FormField';
2
+
3
+ type Participant = {
4
+ order: number;
5
+ code: string;
6
+ name: string;
7
+ color?: string;
8
+ role?: string;
9
+ ses?: string;
10
+ education?: string;
11
+ gender?: string;
12
+ custom?: string;
13
+ corpus?: string;
14
+ language?: string;
15
+ age?: string;
16
+ group?: string;
17
+ };
18
+
19
+ export type ParticipantCreateRequest = {
20
+ code: string;
21
+ name: string;
22
+ };
23
+
24
+ export const EMPTY_PARTICIPANT = {
25
+ order: 1,
26
+ code: '',
27
+ name: '',
28
+ };
29
+
30
+ export const PARTICIPANT_FIELDS: FormField[] = [
31
+ { name: 'order', type: 'text', required: true },
32
+ { name: 'code', type: 'text', required: true },
33
+ { name: 'name', type: 'text', required: false },
34
+ { name: 'separator', type: 'text', required: false },
35
+ {
36
+ name: 'color',
37
+ type: 'color',
38
+ required: false,
39
+ tooltip: 'field.color.tooltip',
40
+ },
41
+ ];
42
+
43
+ export default Participant;
@@ -0,0 +1,18 @@
1
+ import AppEditableField from '@/AppEditable/AppEditableField';
2
+ import api from '@/configs/api';
3
+ import Participant, { ParticipantCreateRequest } from './Participant';
4
+
5
+ function add(uid: string, request: ParticipantCreateRequest) {
6
+ return api.post<Participant>(`/participant/${uid}`, request);
7
+ }
8
+
9
+ function update(field: AppEditableField) {
10
+ return api.patch('/participant/update', field);
11
+ }
12
+
13
+ function remove(document: string, code: string) {
14
+ return api.delete(`/participant/${document}/${code}`);
15
+ }
16
+
17
+ const ParticipantService = { add, update, remove };
18
+ export default ParticipantService;
@@ -0,0 +1,23 @@
1
+ import { createContext, useReducer } from 'react';
2
+ import reducer from './store/reducer';
3
+ import store from './store/store';
4
+ import { StoreAction, UserStore } from './store/types';
5
+
6
+ type ContextType = {
7
+ state: UserStore;
8
+ dispatch: React.Dispatch<StoreAction>;
9
+ };
10
+
11
+ const CommonContext = createContext<ContextType>({} as ContextType);
12
+
13
+ export function CommonProvider({ children }: { children: React.ReactNode }) {
14
+ const [state, dispatch] = useReducer(reducer, store);
15
+
16
+ return (
17
+ <CommonContext.Provider value={{ state, dispatch }}>
18
+ {children}
19
+ </CommonContext.Provider>
20
+ );
21
+ }
22
+
23
+ export default CommonContext;
@@ -0,0 +1,36 @@
1
+ import Cookies from 'js-cookie';
2
+
3
+ const REDIRECT_URI = 'redirect_uri_tycho';
4
+ const JWT_TOKEN = 'jwt_token_tycho';
5
+ const expireDays = 7;
6
+
7
+ const setJwtToken = (jwtToken: string) => {
8
+ Cookies.set(JWT_TOKEN, jwtToken, { expires: expireDays });
9
+ };
10
+
11
+ const getJwtToken = () => {
12
+ const cookie = Cookies.get(JWT_TOKEN);
13
+ return cookie === 'undefined' ? '' : cookie;
14
+ };
15
+
16
+ const removeJwtToken = () => {
17
+ return Cookies.remove(JWT_TOKEN);
18
+ };
19
+
20
+ function setRedirectUri(uri: string) {
21
+ Cookies.set(REDIRECT_URI, uri);
22
+ }
23
+
24
+ function removeRedirectUri() {
25
+ Cookies.remove(REDIRECT_URI);
26
+ }
27
+
28
+ const CookieStorage = {
29
+ setJwtToken,
30
+ getJwtToken,
31
+ removeJwtToken,
32
+ setRedirectUri,
33
+ removeRedirectUri,
34
+ };
35
+
36
+ export default CookieStorage;
@@ -0,0 +1,28 @@
1
+ import i18n from 'i18next';
2
+ import languageDetector from 'i18next-browser-languagedetector';
3
+ import { initReactI18next } from 'react-i18next';
4
+ import { ParticipantsTexts } from './localization/ParticipantsTexts';
5
+ import { CommonTexts } from './localization/CommonTexts';
6
+
7
+ export default function configLocalization() {
8
+ i18n
9
+ .use(initReactI18next)
10
+ .use(languageDetector)
11
+ .init({
12
+ resources: {
13
+ en: {
14
+ common: CommonTexts.en,
15
+ participants: ParticipantsTexts.en,
16
+ },
17
+ 'pt-BR': {
18
+ common: CommonTexts['pt-BR'],
19
+ participants: ParticipantsTexts['pt-BR'],
20
+ },
21
+ },
22
+ fallbackLng: 'en',
23
+ defaultNS: 'message',
24
+ interpolation: {
25
+ escapeValue: false,
26
+ },
27
+ });
28
+ }
@@ -0,0 +1,60 @@
1
+ import { message } from '@/configs/store/actions';
2
+ import { StoreAction } from '@/configs/store/types';
3
+ import { TFunction } from 'i18next';
4
+
5
+ type MessageDispatcher = {
6
+ key: string;
7
+ ns?: string;
8
+ dispatch: React.Dispatch<StoreAction>;
9
+ t: TFunction;
10
+ };
11
+
12
+ type ErrorDispatcher = {
13
+ err: any;
14
+ dispatch: React.Dispatch<StoreAction>;
15
+ t: TFunction;
16
+ };
17
+
18
+ // TODO: improve
19
+ export const dispatchError = ({ err, dispatch, t }: ErrorDispatcher) => {
20
+ if (!err.response) {
21
+ dispatch(message({ value: t(err), type: 'error' }));
22
+ return;
23
+ }
24
+
25
+ if (err.response.status && err.response.status === 403) {
26
+ dispatch(
27
+ message({
28
+ value: t('error.access.authorization'),
29
+ type: 'error',
30
+ })
31
+ );
32
+ return;
33
+ }
34
+
35
+ let errorKey = 'generic.error.message';
36
+ if (err.response.data && err.response.data.description)
37
+ errorKey = err.response.data.description;
38
+
39
+ if (err.response.data.errors && err.response.data.errors.length > 0)
40
+ errorKey = err.response.data.errors[0].description;
41
+
42
+ dispatch(message({ value: t(`message:${errorKey}`), type: 'error' }));
43
+ };
44
+
45
+ export const dispatchMessage = ({
46
+ key,
47
+ ns,
48
+ dispatch,
49
+ t,
50
+ }: MessageDispatcher) => {
51
+ const entry = ns ? `${ns}:${key}` : `message:${key}`;
52
+ dispatch(message({ value: t(entry), type: 'success' }));
53
+ };
54
+
55
+ const MessageUtils = {
56
+ dispatchError,
57
+ dispatchMessage,
58
+ };
59
+
60
+ export default MessageUtils;
@@ -0,0 +1,21 @@
1
+ const ACTIVE_CORPUS = '@Tycho:corpus';
2
+
3
+ function getActiveCorpus() {
4
+ return localStorage.getItem(ACTIVE_CORPUS);
5
+ }
6
+
7
+ function setActiveCorpus(corpus: string) {
8
+ localStorage.setItem(ACTIVE_CORPUS, corpus);
9
+ }
10
+
11
+ function removeActiveCorpus() {
12
+ localStorage.removeItem(ACTIVE_CORPUS);
13
+ }
14
+
15
+ const Storage = {
16
+ getActiveCorpus,
17
+ setActiveCorpus,
18
+ removeActiveCorpus,
19
+ };
20
+
21
+ export default Storage;
@@ -0,0 +1,49 @@
1
+ import axios, { AxiosError } from 'axios';
2
+ import CookieStorage from './CookieStorage';
3
+
4
+ const api = axios.create({
5
+ headers: {
6
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
7
+ Pragma: 'no-cache',
8
+ 'Content-Type': 'application/json',
9
+ Accept: 'application/json',
10
+ },
11
+
12
+ baseURL: import.meta.env.VITE_APP_URL_API,
13
+ });
14
+
15
+ api.interceptors.response.use(
16
+ (response) => response,
17
+ async (error: AxiosError) => {
18
+ if (error.response?.status === 401) {
19
+ CookieStorage.removeJwtToken();
20
+ sessionStorage.clear();
21
+ localStorage.clear();
22
+ window.location.replace(import.meta.env.VITE_APP_AUTH_URL as string);
23
+ } else if (error.response?.status === 403) {
24
+ window.location.href = `${
25
+ import.meta.env.VITE_APP_PUBLIC_URL
26
+ }/unauthorized`;
27
+ } else if (error.status === null) {
28
+ console.log(JSON.stringify(error));
29
+ }
30
+
31
+ return Promise.reject(error);
32
+ }
33
+ );
34
+
35
+ api.interceptors.request.use((config) => {
36
+ if (!config?.headers) {
37
+ throw new Error('no header available');
38
+ }
39
+
40
+ const token = CookieStorage.getJwtToken();
41
+ if (!token) return config;
42
+
43
+ // eslint-disable-next-line no-param-reassign
44
+ config.headers.Authorization = `Bearer ${token}`;
45
+
46
+ return config;
47
+ });
48
+
49
+ export default api;
@@ -0,0 +1,26 @@
1
+ export const CommonTexts = {
2
+ en: {
3
+ 'button.confirm': 'Confirm',
4
+ 'button.cancel': 'Cancel',
5
+ 'pagination.label.showing': 'Showing',
6
+ 'pagination.label.results': 'results per page',
7
+ 'pagination.label.total': 'Total',
8
+ 'generic.placeholder': 'Type here',
9
+ 'generic.placeholder.select': 'Select one here',
10
+ 'validation.required': 'Required field.',
11
+ 'placeholder.check': 'click to choose',
12
+ 'placeholder.input': 'click to add',
13
+ },
14
+ 'pt-BR': {
15
+ 'button.confirm': 'Confirmar',
16
+ 'button.cancel': 'Cancelar',
17
+ 'pagination.label.showing': 'Exibindo',
18
+ 'pagination.label.results': 'resultados por página',
19
+ 'pagination.label.total': 'Total',
20
+ 'generic.placeholder': 'Digite aqui',
21
+ 'generic.placeholder.select': 'Selecione um aqui',
22
+ 'validation.required': 'Campo obrigatório.',
23
+ 'placeholder.check': 'clique para selecionar',
24
+ 'placeholder.input': 'clique para adicionar',
25
+ },
26
+ };
@@ -0,0 +1,40 @@
1
+ export const ParticipantsTexts = {
2
+ en: {
3
+ 'label.title': 'Participants',
4
+ 'button.label.add': 'Add participant',
5
+ 'participant.field.order': 'Order',
6
+ 'participant.field.code': 'Code',
7
+ 'participant.field.name': 'Name',
8
+ 'participant.field.color': 'Color',
9
+ 'participant.field.separator': 'Separator',
10
+ 'participant.field.color.tooltip':
11
+ 'A color for transcription visual identifier',
12
+ 'modal.remove.title': 'Remove participant',
13
+ 'modal.remove.description':
14
+ 'Are you sure you want to delete this participant? This action is irreversible, but the sentences are not deleted.',
15
+ 'modal.remove.participant':
16
+ 'You can choose a participant to transfer sentences related to ',
17
+ 'modal.add.title': 'Add new participant',
18
+ 'modal.input.code': 'ID',
19
+ 'modal.input.name': 'Name',
20
+ },
21
+ 'pt-BR': {
22
+ 'label.title': 'Participantes',
23
+ 'button.label.add': 'Adicionar participante',
24
+ 'participant.field.order': 'Ordem',
25
+ 'participant.field.code': 'Código',
26
+ 'participant.field.name': 'Nome',
27
+ 'participant.field.color': 'Cor',
28
+ 'participant.field.separator': 'Separador',
29
+ 'participant.field.color.tooltip':
30
+ 'Cor para identificação visual da sentença na transcrição',
31
+ 'modal.remove.title': 'Remover participante',
32
+ 'modal.remove.description':
33
+ 'Você tem certeza que deseja remover este participante? Esta ação é irreversível, porém as sentenças não serão excluídas.',
34
+ 'modal.remove.participant':
35
+ 'Você pode escolher um participante para transferir frases relacionadas a ',
36
+ 'modal.add.title': 'Adicionar novo participante',
37
+ 'modal.input.code': 'ID',
38
+ 'modal.input.name': 'Nome',
39
+ },
40
+ };
@@ -0,0 +1,12 @@
1
+ import ToastMessage from '@/AppToast/ToastMessage';
2
+ import { StoreAction, types } from './types';
3
+
4
+ export const message = (data: ToastMessage): StoreAction => ({
5
+ type: types.MESSAGE,
6
+ payload: data,
7
+ });
8
+
9
+ export const toastLoading = (data: boolean): StoreAction => ({
10
+ type: types.TOAST_LOADING,
11
+ payload: data,
12
+ });
@@ -0,0 +1,22 @@
1
+ /* eslint-disable */
2
+ import { types, UserStore } from './types';
3
+
4
+ function reducer(state: UserStore, action: any): UserStore {
5
+ switch (action.type) {
6
+ case types.MESSAGE:
7
+ return {
8
+ ...state,
9
+ message: action.payload,
10
+ };
11
+ case types.TOAST_LOADING:
12
+ return {
13
+ ...state,
14
+ toastLoading: action.payload,
15
+ };
16
+
17
+ default:
18
+ return state;
19
+ }
20
+ }
21
+
22
+ export default reducer;
@@ -0,0 +1,9 @@
1
+ import { EMPTY_TOAST } from '@/AppToast/ToastMessage';
2
+ import { UserStore } from './types';
3
+
4
+ const store: UserStore = {
5
+ message: EMPTY_TOAST,
6
+ toastLoading: false,
7
+ };
8
+
9
+ export default store;
@@ -0,0 +1,16 @@
1
+ import ToastMessage from '@/AppToast/ToastMessage';
2
+
3
+ export type UserStore = {
4
+ message: ToastMessage;
5
+ toastLoading: boolean;
6
+ };
7
+
8
+ export type StoreAction = {
9
+ type: string;
10
+ payload?: ToastMessage | boolean;
11
+ };
12
+
13
+ export const types = {
14
+ MESSAGE: 'MESSAGE',
15
+ TOAST_LOADING: 'TOAST_LOADING',
16
+ };
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { default as AppEditable } from './AppEditable';
2
+ export { default as AppToast } from './AppToast';
3
+ export { default as AppColorpicker } from './AppColorpicker';
4
+ export { default as Participants } from './Participants';
@@ -0,0 +1,5 @@
1
+ declare module '*.png';
2
+ declare module '*.svg';
3
+ declare module '*.jpeg';
4
+ declare module '*.jpg';
5
+ declare module 'react-easy-edit';
@@ -0,0 +1,67 @@
1
+ :root {
2
+ --font-weight-light: 300;
3
+ --font-weight-regular: 400;
4
+ --font-weight-large: 700;
5
+
6
+ --font-size-xxsmall: 10px;
7
+ --font-size-xsmall: 12px;
8
+ --font-size-small: 14px;
9
+ --font-size-body: 16px;
10
+ --font-size-button: 16px;
11
+ --font-size-medium: 18px;
12
+ --font-size-regular: 20px;
13
+ --font-size-subtitle: 24px;
14
+ --font-size-large: 32px;
15
+ --font-size-xlarge: 40px;
16
+ --font-size-xxlarge: 48px;
17
+ --font-size-xxxlarge: 56px;
18
+ --font-size-jumbo: 96px;
19
+
20
+ --spacing-xxsmall: calc(var(--spacing-xsmall) / 2);
21
+ --spacing-xsmall: 8px;
22
+ --spacing-small: calc(var(--spacing-xsmall) * 2);
23
+ --spacing-medium: 24px;
24
+ --spacing-large: 32px;
25
+ --spacing-xlarge: 40px;
26
+ --spacing-xxlarge: 48px;
27
+ --spacing-xxxlarge: 64px;
28
+ --spacing-jumbo: 96px;
29
+
30
+ --color-primary: #185abd;
31
+ --color-primary-alternative: #7c7c80;
32
+ --color-secondary: #f3f2f1;
33
+ --color-complementary: #14cc80;
34
+ --color-complementary-light: #dafbed;
35
+ --color-complementary-dark: #031d12;
36
+ --color-background: #f9f9f9;
37
+ --color-surface: #ffffff;
38
+ --color-error: #e25c5a;
39
+ --color-warning: #ffb40d;
40
+ --color-confirmed: #14cc80;
41
+ --color-disabled: #c8c6c4;
42
+ --color-button-hover: #e1e0de;
43
+ --color-dark: #000000;
44
+ --color-highlight: #bca3c2;
45
+
46
+ --color-warning-20: rgba(255, 180, 13, 0.2);
47
+ --color-confirmed-20: rgba(20, 204, 128, 0.2);
48
+ --color-error-20: rgba(239, 99, 97, 0.2);
49
+
50
+ --opacity-black-62: rgba(18, 18, 18, 0.62);
51
+ --opacity-black-53: rgba(18, 18, 18, 0.53);
52
+ --opacity-black-32: rgba(18, 18, 18, 0.32);
53
+ --opacity-black-15: rgba(18, 18, 18, 0.15);
54
+ --opacity-black-08: rgba(18, 18, 18, 0.08);
55
+
56
+ --opacity-white-62: rgba(255, 255, 255, 0.62);
57
+ --opacity-white-53: rgba(255, 255, 255, 0.53);
58
+ --opacity-white-32: rgba(255, 255, 255, 0.32);
59
+ --opacity-white-15: rgba(255, 255, 255, 0.15);
60
+ --opacity-white-08: rgba(255, 255, 255, 0.08);
61
+
62
+ --shadow: 0 1px 12px rgba(0, 0, 0, 0.1);
63
+
64
+ --border-default: 1px solid var(--color-disabled);
65
+ --border-radius: var(--spacing-xsmall);
66
+ --border-radius-btn: calc(var(--spacing-xsmall) / 2);
67
+ }