chore(js): update coldwired and react
This commit is contained in:
parent
316a33f97b
commit
1e11ad4ce6
24 changed files with 108 additions and 106 deletions
|
@ -28,3 +28,7 @@ body {
|
|||
.container {
|
||||
@extend %container;
|
||||
}
|
||||
|
||||
react-fragment {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.form [data-react-component-value='MapEditor'] [data-reach-combobox-input] {
|
||||
.form react-fragment[data-component-name='MapEditor'] [data-reach-combobox-input] {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -524,7 +524,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
[data-react-component-value^="ComboMultiple"] {
|
||||
react-fragment[data-component-name^="ComboMultiple"] {
|
||||
margin-bottom: $default-fields-spacer;
|
||||
|
||||
[data-reach-combobox-input] {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
margin-left: 16px;
|
||||
}
|
||||
|
||||
[data-react-component-value^="ComboMultiple"] {
|
||||
react-fragment[data-component-name^="ComboMultiple"] {
|
||||
margin-bottom: 0;
|
||||
|
||||
[data-reach-combobox-token-list] {
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
[data-react-component-value^="ComboMultiple"] {
|
||||
react-fragment[data-component-name^="ComboMultiple"] {
|
||||
margin-bottom: $default-fields-spacer;
|
||||
|
||||
[data-reach-combobox-token-list] {
|
||||
|
|
14
app/components/react_component.rb
Normal file
14
app/components/react_component.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
class ReactComponent < ApplicationComponent
|
||||
erb_template <<-ERB
|
||||
<% if content? %>
|
||||
<react-component name=<%= @name %> props="<%= @props.to_json %>"><%= content %></react-component>
|
||||
<% else %>
|
||||
<react-component name=<%= @name %> props="<%= @props.to_json %>"></react-component>
|
||||
<% end %>
|
||||
ERB
|
||||
|
||||
def initialize(name, **props)
|
||||
@name = name
|
||||
@props = props
|
||||
end
|
||||
end
|
|
@ -59,10 +59,6 @@ module ApplicationHelper
|
|||
'alert'
|
||||
end
|
||||
|
||||
def react_component(name, props = {}, html = {})
|
||||
tag.div(**html.merge(data: { controller: 'react', react_component_value: name, react_props_value: props.to_json }))
|
||||
end
|
||||
|
||||
def current_email
|
||||
current_user&.email ||
|
||||
current_instructeur&.email ||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {
|
||||
import {
|
||||
useMemo,
|
||||
useState,
|
||||
useRef,
|
||||
|
|
12
app/javascript/components/Layout.tsx
Normal file
12
app/javascript/components/Layout.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { I18nProvider } from 'react-aria-components';
|
||||
import { StrictMode, type ReactNode } from 'react';
|
||||
|
||||
export function Layout({ children }: { children: ReactNode }) {
|
||||
const locale = document.documentElement.lang;
|
||||
console.debug(`locale: ${locale}`);
|
||||
return (
|
||||
<I18nProvider locale={locale}>
|
||||
<StrictMode>{children}</StrictMode>
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useCallback, MouseEvent, ChangeEvent } from 'react';
|
||||
import { useState, useCallback, MouseEvent, ChangeEvent } from 'react';
|
||||
import type { FeatureCollection } from 'geojson';
|
||||
import invariant from 'tiny-invariant';
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useId } from 'react';
|
||||
import { useState, useId } from 'react';
|
||||
import { fire } from '@utils';
|
||||
import type { Feature, FeatureCollection } from 'geojson';
|
||||
import CoordinateInput from 'react-coordinate-input';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { Popup, LngLatBoundsLike, LngLatLike } from 'maplibre-gl';
|
||||
import type { Feature, FeatureCollection, Point } from 'geojson';
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import React from 'react';
|
||||
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||
import type { FeatureCollection } from 'geojson';
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import React from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import invariant from 'tiny-invariant';
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {
|
||||
import {
|
||||
useState,
|
||||
useContext,
|
||||
useRef,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useId } from 'react';
|
||||
import { useState, useId } from 'react';
|
||||
import { Popover, RadioGroup } from '@headlessui/react';
|
||||
import { usePopper } from 'react-popper';
|
||||
import { MapIcon } from '@heroicons/react/outline';
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
import { Controller } from '@hotwired/stimulus';
|
||||
import React, { lazy, Suspense, FunctionComponent } from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import invariant from 'tiny-invariant';
|
||||
|
||||
type Props = Record<string, unknown>;
|
||||
type Loader = () => Promise<{ default: FunctionComponent<Props> }>;
|
||||
const componentsRegistry = new Map<string, FunctionComponent<Props>>();
|
||||
const components = import.meta.glob('../components/*.tsx');
|
||||
|
||||
for (const [path, loader] of Object.entries(components)) {
|
||||
const [filename] = path.split('/').reverse();
|
||||
const componentClassName = filename.replace(/\.(ts|tsx)$/, '');
|
||||
console.debug(
|
||||
`Registered lazy default export for "${componentClassName}" component`
|
||||
);
|
||||
componentsRegistry.set(
|
||||
componentClassName,
|
||||
LoadableComponent(loader as Loader)
|
||||
);
|
||||
}
|
||||
|
||||
// Initialize React components when their markup appears into the DOM.
|
||||
//
|
||||
// Example:
|
||||
// <div data-controller="react" data-react-component-value="ComboMultiple" data-react-props-value="{}"></div>
|
||||
//
|
||||
export class ReactController extends Controller {
|
||||
static values = {
|
||||
component: String,
|
||||
props: Object
|
||||
};
|
||||
|
||||
declare readonly componentValue: string;
|
||||
declare readonly propsValue: Props;
|
||||
|
||||
connect(): void {
|
||||
this.mountComponent(this.element as HTMLElement);
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
unmountComponentAtNode(this.element as HTMLElement);
|
||||
}
|
||||
|
||||
private mountComponent(node: HTMLElement): void {
|
||||
const componentName = this.componentValue;
|
||||
const props = this.propsValue;
|
||||
const Component = this.getComponent(componentName);
|
||||
|
||||
invariant(
|
||||
Component,
|
||||
`Cannot find a React component with class "${componentName}"`
|
||||
);
|
||||
render(<Component {...props} />, node);
|
||||
}
|
||||
|
||||
private getComponent(componentName: string): FunctionComponent<Props> | null {
|
||||
return componentsRegistry.get(componentName) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
const Spinner = () => <div className="spinner left" />;
|
||||
|
||||
function LoadableComponent(loader: Loader): FunctionComponent<Props> {
|
||||
const LazyComponent = lazy(loader);
|
||||
const Component: FunctionComponent<Props> = (props: Props) => (
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<LazyComponent {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
return Component;
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
import { Actions } from '@coldwired/actions';
|
||||
import { parseTurboStream } from '@coldwired/turbo-stream';
|
||||
import { createRoot, createReactPlugin, type Root } from '@coldwired/react';
|
||||
import invariant from 'tiny-invariant';
|
||||
import { session as TurboSession, type StreamElement } from '@hotwired/turbo';
|
||||
import type { ComponentType } from 'react';
|
||||
|
||||
import { ApplicationController } from './application_controller';
|
||||
|
||||
|
@ -20,6 +22,7 @@ export class TurboController extends ApplicationController {
|
|||
|
||||
#submitting = false;
|
||||
#actions?: Actions;
|
||||
#root?: Root;
|
||||
|
||||
// `actions` instrface exposes all available actions as methods and also `applyActions` method
|
||||
// wich allows to apply a batch of actions. On top of regular `turbo-stream` actions we also
|
||||
|
@ -32,6 +35,17 @@ export class TurboController extends ApplicationController {
|
|||
}
|
||||
|
||||
connect() {
|
||||
this.#root = createRoot({
|
||||
layoutComponentName: 'Layout/Layout',
|
||||
loader,
|
||||
schema: {
|
||||
fragmentTagName: 'react-fragment',
|
||||
componentTagName: 'react-component',
|
||||
slotTagName: 'react-slot',
|
||||
loadingClassName: 'loading'
|
||||
}
|
||||
});
|
||||
const plugin = createReactPlugin(this.#root);
|
||||
this.#actions = new Actions({
|
||||
element: document.body,
|
||||
schema: {
|
||||
|
@ -40,6 +54,7 @@ export class TurboController extends ApplicationController {
|
|||
focusDirectionAttribute: 'data-turbo-focus-direction',
|
||||
hiddenClassName: 'hidden'
|
||||
},
|
||||
plugins: [plugin],
|
||||
debug: false
|
||||
});
|
||||
|
||||
|
@ -73,6 +88,11 @@ export class TurboController extends ApplicationController {
|
|||
});
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
this.#actions?.disconnect();
|
||||
this.#root?.destroy();
|
||||
}
|
||||
|
||||
private startSpinner() {
|
||||
this.#submitting = true;
|
||||
this.actions.show({ targets: this.spinnerTargets });
|
||||
|
@ -89,3 +109,24 @@ export class TurboController extends ApplicationController {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Loader = (exportName: string) => Promise<ComponentType<unknown>>;
|
||||
const componentsRegistry: Record<string, Loader> = {};
|
||||
const components = import.meta.glob('../components/*.tsx');
|
||||
|
||||
const loader: Loader = (name) => {
|
||||
const [moduleName, exportName] = name.split('/');
|
||||
const loader = componentsRegistry[moduleName];
|
||||
invariant(loader, `Cannot find a React component with name "${name}"`);
|
||||
return loader(exportName ?? 'default');
|
||||
};
|
||||
|
||||
for (const [path, loader] of Object.entries(components)) {
|
||||
const [filename] = path.split('/').reverse();
|
||||
const componentClassName = filename.replace(/\.(ts|tsx)$/, '');
|
||||
console.debug(`Registered lazy export for "${componentClassName}" component`);
|
||||
componentsRegistry[componentClassName] = (exportName) =>
|
||||
loader().then(
|
||||
(m) => (m as Record<string, ComponentType<unknown>>)[exportName]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -59,7 +59,5 @@
|
|||
- else
|
||||
= render 'footer'
|
||||
|
||||
- if Rails.env.development?
|
||||
= vite_typescript_tag 'axe-core'
|
||||
= yield :charts_js
|
||||
= render Attachment::ProgressBarComponent.new
|
||||
|
|
|
@ -11,6 +11,8 @@ by providing a `content_for(:javascript)` block.
|
|||
<%= javascript_include_tag js_path %>
|
||||
<% end %>
|
||||
|
||||
<%= vite_client_tag %>
|
||||
<%= vite_react_refresh_tag %>
|
||||
<%= vite_typescript_tag 'manager' %>
|
||||
|
||||
<%= yield :javascript %>
|
||||
|
|
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
26
package.json
26
package.json
|
@ -1,9 +1,10 @@
|
|||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@coldwired/actions": "^0.11.2",
|
||||
"@coldwired/turbo-stream": "^0.11.1",
|
||||
"@coldwired/utils": "^0.11.4",
|
||||
"@coldwired/actions": "^0.13.0",
|
||||
"@coldwired/react": "^0.15.0",
|
||||
"@coldwired/turbo-stream": "^0.13.0",
|
||||
"@coldwired/utils": "^0.13.0",
|
||||
"@frsource/autoresize-textarea": "^2.0.75",
|
||||
"@gouvfr/dsfr": "^1.11.2",
|
||||
"@graphiql/plugin-explorer": "^3.0.2",
|
||||
|
@ -17,7 +18,6 @@
|
|||
"@rails/actiontext": "^7.1.3-2",
|
||||
"@rails/activestorage": "^7.1.3-2",
|
||||
"@rails/ujs": "^7.1.3-2",
|
||||
"@reach/combobox": "^0.17.0",
|
||||
"@reach/slider": "^0.17.0",
|
||||
"@sentry/browser": "8.7.0",
|
||||
"@tiptap/core": "^2.2.4",
|
||||
|
@ -50,31 +50,32 @@
|
|||
"graphiql": "^3.2.3",
|
||||
"graphql": "^16.8.1",
|
||||
"highcharts": "^10.3.3",
|
||||
"is-hotkey": "^0.2.0",
|
||||
"lightgallery": "^2.7.2",
|
||||
"maplibre-gl": "^1.15.2",
|
||||
"match-sorter": "^6.3.4",
|
||||
"patch-package": "^8.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-aria-components": "^1.2.0",
|
||||
"react-coordinate-input": "^1.0.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"react-popper": "^2.3.0",
|
||||
"react-query": "^3.39.3",
|
||||
"react-use-event-hook": "^0.9.6",
|
||||
"spectaql": "^2.3.1",
|
||||
"stimulus-use": "^0.52.2",
|
||||
"terser": "^5.31.0",
|
||||
"tiny-invariant": "^1.3.3",
|
||||
"tippy.js": "^6.3.7",
|
||||
"trix": "^1.2.3",
|
||||
"use-debounce": "^9.0.4",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"zod": "^3.20.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@esbuild/darwin-arm64": "=0.19.9",
|
||||
"@esbuild/linux-x64": "=0.19.9",
|
||||
"@esbuild/win32-x64": "=0.19.9",
|
||||
"@rollup/rollup-linux-x64-gnu": "=4.9.1",
|
||||
"@react-aria/optimize-locales-plugin": "^1.1.0",
|
||||
"@rollup/rollup-darwin-arm64": "=4.9.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "=4.9.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "=4.9.1",
|
||||
"@types/debounce": "^1.2.4",
|
||||
"@types/geojson": "^7946.0.14",
|
||||
|
@ -82,8 +83,8 @@
|
|||
"@types/mapbox__mapbox-gl-draw": "^1.2.5",
|
||||
"@types/rails__activestorage": "^7.1.1",
|
||||
"@types/rails__ujs": "^6.0.4",
|
||||
"@types/react": "^17.0.43",
|
||||
"@types/react-dom": "^17.0.14",
|
||||
"@types/react": "^18.2.79",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "^7.11.0",
|
||||
"@typescript-eslint/parser": "^7.11.0",
|
||||
|
@ -169,6 +170,7 @@
|
|||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:react/jsx-runtime",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"target": "ES2019",
|
||||
"moduleResolution": "node",
|
||||
"module": "es2020",
|
||||
"jsx": "react",
|
||||
"jsx": "react-jsx",
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"isolatedModules": true,
|
||||
|
@ -13,7 +13,7 @@
|
|||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"types": ["react/next", "react-dom/next", "vite/client"],
|
||||
"types": ["vite/client"],
|
||||
"paths": {
|
||||
"~/*": ["./app/javascript/*"],
|
||||
"@utils": ["./app/javascript/shared/utils.ts"]
|
||||
|
|
|
@ -2,14 +2,21 @@ import { defineConfig } from 'vite';
|
|||
import ViteReact from '@vitejs/plugin-react';
|
||||
import RubyPlugin from 'vite-plugin-ruby';
|
||||
import FullReload from 'vite-plugin-full-reload';
|
||||
import optimizeLocales from '@react-aria/optimize-locales-plugin';
|
||||
|
||||
const plugins = [
|
||||
RubyPlugin(),
|
||||
ViteReact({ jsxRuntime: 'classic' }),
|
||||
ViteReact(),
|
||||
FullReload(
|
||||
['config/routes.rb', 'app/views/**/*', 'app/components/**/*.haml'],
|
||||
{ delay: 200 }
|
||||
)
|
||||
),
|
||||
{
|
||||
...optimizeLocales.vite({
|
||||
locales: ['en-GB', 'fr-FR']
|
||||
}),
|
||||
enforce: 'pre' as const
|
||||
}
|
||||
];
|
||||
|
||||
export default defineConfig({
|
||||
|
|
Loading…
Reference in a new issue