Skip to content

cmfcmf/ReactS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React/S

WIP: Modern ReactJS, including React Hooks, implemented in Squeak/Smalltalk.

Implementation Status

Installation

Metacello new
  baseline: 'CmfcmfReact';
  repository: 'github://cmfcmf/ReactS:main/packages';
  load: #default.

Demo

CMFReactComponentDemo openInHand

Examples

Simple Click Counter

A simple example component that provides a button to increment a number is shown below:

MyExampleComponent >> render: props

	| clicks |
	clicks := self useState: 0.
	
	^ CMFReactComponentContainer asReactNodeBuilder children: {
		clicks get.
		CMFReactComponentContainer asReactNodeBuilder props: {#direction -> #leftToRight}; children: {
			CMFReactComponentButton asReactNodeBuilder props: {
				#onClick -> [clicks set: [:oldClicks | oldClicks + 1]].
				#label -> 'increment'}; build.
			CMFReactComponentButton asReactNodeBuilder props: {
				#onClick -> [clicks set: 0].
				#label -> 'reset'}; build.
		}; build.
	}; build

MyExampleComponent must inherit from CMFReactComponent. To render and open it, execute MyExampleComponent openInHand

TODO Notes App

A window that manages a list of TODO items (CMFReactExampleAppTodoNotes).

CMFReactExampleAppTodoNotes >> render: props

	| todos nextId selectedId selectedTodo |
	nextId := self useRef: 1.
	todos := self
		useReducer: [:state :action |
			action first caseOf: {
				[#add] -> [ | id title |
					title := (UIManager default request: 'Title') ifNil: [''].
					title 
						ifEmpty: [state] 
						ifNotEmpty: [| content |
							content := (UIManager default request: 'Content') ifNil: [''].
							id := nextId get.
							nextId set: id + 1.
							state copyWith: (Dictionary newFrom: {#id -> id. #title -> title. #content -> content})]].
				[#delete] -> [ | id |
					id := action second.
					state copyWithout: (state detect: [:oldTodo | (oldTodo at: #id) == id])]}]
		initialState: {}.
	
	selectedId := self useState: 0.
	selectedTodo := todos get detect: [:each | (each at: #id) == selectedId get] ifNone: [nil].
	
	^ CMFReactComponentWindow asReactNodeBuilder
		props: {#label -> 'TODO Notes'. #defaultExtent -> (600 @ 300)};
		children: {
			(#Sidebar asReactNodeBuilder: self)
				props: {#todos -> todos get. #todosDispatch -> todos dispatch. #setSelectedId -> selectedId setter};
				build.
			CMFReactComponentContainer asReactNodeBuilder
				props: {#scrollable -> true. #layoutFrame -> (LayoutFrame fractions: (0.3 @ 0 corner: 1 @ 1))};
				children: {
					selectedTodo
						ifNil: ['-- nothing selected --']
						ifNotNil: [{selectedTodo at: #title. selectedTodo at: #content}]};
				build};
		build


CMFReactExampleAppTodoNotes >> Sidebar: props
	
	<memoize>
	
	| todos todosDispatch setSelectedId |
	todos := props at: #todos.
	todosDispatch := props at: #todosDispatch.
	setSelectedId := props at: #setSelectedId.
	
	^ CMFReactComponentContainer asReactNodeBuilder
		props: {
			#layoutFrame -> (LayoutFrame fractions: (0@0 extent: 0.3@1))};
		children: {
			CMFReactComponentContainer asReactNodeBuilder
				props: {#layoutFrame -> (LayoutFrame fractions: (0@0 extent: 1@0) offsets: (0@0 extent: 0@20))};
				children: {
					CMFReactComponentButton asReactNodeBuilder
						props: {#label -> 'Add TODO Note'. #onClick -> [todosDispatch value: {#add}]};
						build};
				build.
			CMFReactComponentContainer asReactNodeBuilder
				props: {
					#scrollable -> true.
					#layoutFrame -> (LayoutFrame fractions: (0@0 extent: 1@1) offsets: (0@20 extent: 0@ -20))};
				children: {
					todos collect: [:each |
						(#TODOSidebarItem asReactNodeBuilder: self)
							props: {#key -> (each at: #id). #todo -> each. #dispatch -> todosDispatch. #setSelectedId -> setSelectedId};
							build]};
				build};
		build.


CMFReactExampleAppTodoNotes >> TODOSidebarItem: props

	<memoize>
	
	| todo |
	todo := props at: #todo.

	^ CMFReactComponentContainer asReactNodeBuilder
		children: {
			todo at: #title.
			CMFReactComponentButton asReactNodeBuilder
				props: {#label -> 'select'. #onClick -> [(props at: #setSelectedId) value: (todo at: #id)]};
				build.
			CMFReactComponentButton asReactNodeBuilder
				props: {#label -> 'delete'. #onClick -> [(props at: #dispatch) value: {#delete. todo at: #id}]};
				build};
		build