ar-design 0.4.21 → 0.4.22

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.
@@ -210,6 +210,71 @@
210
210
  }
211
211
  }
212
212
 
213
+ .filters {
214
+ position: relative;
215
+ display: flex;
216
+ flex-direction: row;
217
+ justify-content: space-between;
218
+ gap: 1rem;
219
+ background-color: var(--white);
220
+ margin-bottom: 1rem;
221
+ border-radius: var(--border-radius-lg);
222
+ z-index: 6;
223
+
224
+ > :first-child {
225
+ flex: 1;
226
+ }
227
+
228
+ > ul {
229
+ display: flex;
230
+ flex-direction: row;
231
+ gap: 0.5rem;
232
+
233
+ > li {
234
+ position: relative;
235
+ padding: 0.5rem;
236
+ cursor: pointer;
237
+ transition: background-color 150ms ease-in-out;
238
+
239
+ &:hover {
240
+ background-color: var(--gray-100);
241
+
242
+ > ul {
243
+ display: block;
244
+ }
245
+ }
246
+
247
+ > div {
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 0.5rem;
251
+
252
+ > span {
253
+ color: var(--gray-500);
254
+ font-weight: 500;
255
+ }
256
+ }
257
+
258
+ > ul {
259
+ display: none;
260
+ position: absolute;
261
+ top: 100%;
262
+ right: 0;
263
+ background-color: var(--gray-100);
264
+ width: max-content;
265
+ padding: 1rem;
266
+ border-bottom-left-radius: var(--border-radius-sm);
267
+ border-bottom-right-radius: var(--border-radius-sm);
268
+ box-shadow: 0px 5px 5px -2px rgba(var(--black-rgb), 0.1);
269
+
270
+ > li {
271
+ cursor: auto;
272
+ }
273
+ }
274
+ }
275
+ }
276
+ }
277
+
213
278
  /* --- [KÜÇÜK MOBİL - IPHONE SE VB.] --- */
214
279
  /* Çok dar ekranlar için özel düzeltmeler */
215
280
  @media (max-width: 375px) {
@@ -0,0 +1,17 @@
1
+ import React, { Dispatch, SetStateAction } from "react";
2
+ interface IProps {
3
+ states: {
4
+ dateFilters: {
5
+ get: Record<string, {
6
+ from: Date | null;
7
+ to: Date | null;
8
+ }>;
9
+ set: Dispatch<SetStateAction<Record<string, {
10
+ from: Date | null;
11
+ to: Date | null;
12
+ }>>>;
13
+ };
14
+ };
15
+ }
16
+ declare const DateFilters: ({ states }: IProps) => React.JSX.Element[];
17
+ export default DateFilters;
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ import DatePicker from "../../form/date-picker";
3
+ import { ARIcon } from "../../icons";
4
+ import Grid from "../grid-system";
5
+ const { Row, Column } = Grid;
6
+ const DateFilters = ({ states }) => {
7
+ return Object.entries(states.dateFilters.get).map(([name, range]) => (React.createElement("li", { key: name, className: "filter-box" },
8
+ React.createElement("div", null,
9
+ React.createElement("span", null, name),
10
+ React.createElement(ARIcon, { icon: "ChevronDown" })),
11
+ React.createElement("ul", null,
12
+ React.createElement(Row, null,
13
+ React.createElement(Column, null,
14
+ React.createElement(DatePicker, { value: range.from?.toISOString(), onChange: (value) => {
15
+ states.dateFilters.set((prev) => ({
16
+ ...prev,
17
+ [name]: {
18
+ ...prev[name],
19
+ from: new Date(value),
20
+ },
21
+ }));
22
+ } }))),
23
+ React.createElement(Row, null,
24
+ React.createElement(Column, null,
25
+ React.createElement(DatePicker, { value: range.to?.toISOString(), onChange: (value) => {
26
+ states.dateFilters.set((prev) => ({
27
+ ...prev,
28
+ [name]: {
29
+ ...prev[name],
30
+ to: new Date(value),
31
+ },
32
+ }));
33
+ } })))))));
34
+ };
35
+ export default DateFilters;
@@ -10,6 +10,14 @@ interface IProps<T, TColumnProperties> {
10
10
  bottom?: number;
11
11
  left?: number;
12
12
  };
13
+ filter?: {
14
+ search?: (item: T, value: string) => boolean;
15
+ keys: (item: T) => {
16
+ name: string;
17
+ key: string;
18
+ type: "select" | "date";
19
+ }[];
20
+ };
13
21
  };
14
22
  }
15
23
  export default IProps;
@@ -0,0 +1,19 @@
1
+ import React, { Dispatch, SetStateAction } from "react";
2
+ interface IProps {
3
+ states: {
4
+ selectFilters: {
5
+ get: {
6
+ [key: string]: (string | null)[];
7
+ };
8
+ set: Dispatch<SetStateAction<{
9
+ [key: string]: (string | null)[];
10
+ }>>;
11
+ };
12
+ selectedFilters: {
13
+ get: Record<string, Set<string | null>>;
14
+ set: Dispatch<SetStateAction<Record<string, Set<string | null>>>>;
15
+ };
16
+ };
17
+ }
18
+ declare const SelectFilters: ({ states }: IProps) => React.JSX.Element[];
19
+ export default SelectFilters;
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import { ARIcon } from "../../icons";
3
+ import Checkbox from "../../form/checkbox";
4
+ const SelectFilters = ({ states }) => {
5
+ return Object.entries(states.selectFilters.get).map(([name, values]) => (React.createElement("li", null,
6
+ React.createElement("div", null,
7
+ React.createElement("span", null, name),
8
+ React.createElement(ARIcon, { icon: "ChevronDown" })),
9
+ React.createElement("ul", null, values.map((val) => (React.createElement("li", null,
10
+ React.createElement(Checkbox, { label: String(val ?? "-"), checked: states.selectedFilters.get[name]?.has(val) ?? false, onChange: () => {
11
+ states.selectedFilters.set((prev) => {
12
+ const next = { ...prev };
13
+ const set = new Set(next[name] ?? []);
14
+ set.has(val) ? set.delete(val) : set.add(val);
15
+ next[name] = set;
16
+ return next;
17
+ });
18
+ } }))))))));
19
+ };
20
+ export default SelectFilters;
@@ -3,6 +3,9 @@ import React, { useEffect, useRef, useState } from "react";
3
3
  import "../../../assets/css/components/data-display/kanban-board/styles.css";
4
4
  import DnD from "../dnd";
5
5
  import { ARIcon } from "../../icons";
6
+ import Input from "../../form/input";
7
+ import DateFilters from "./DateFilters";
8
+ import SelectFilters from "./SelectFilters";
6
9
  const KanbanBoard = function ({ trackBy, columns, onChange, config, }) {
7
10
  // refs
8
11
  const _kanbanWrapper = useRef(null);
@@ -12,6 +15,11 @@ const KanbanBoard = function ({ trackBy, columns, onChange, config, }) {
12
15
  const _scrollSpeedRef = useRef(0); // px/frame
13
16
  // states
14
17
  const [data, setData] = useState([]);
18
+ // states -> Filters
19
+ const [search, setSearch] = useState("");
20
+ const [selectFilters, setSelectFilters] = useState({});
21
+ const [selectedFilters, setSelectedFilters] = useState({});
22
+ const [dateFilters, setDateFilters] = useState({});
15
23
  // methods
16
24
  const handleBoardDragOver = (event) => {
17
25
  event.preventDefault();
@@ -130,33 +138,129 @@ const KanbanBoard = function ({ trackBy, columns, onChange, config, }) {
130
138
  .slice(1));
131
139
  };
132
140
  // useEffects
133
- useEffect(() => setData(columns), [columns]);
134
- return (React.createElement("div", { ref: _kanbanWrapper, className: "ar-kanban-board", style: {
135
- height: `calc(100dvh - (${_kanbanWrapper.current?.getBoundingClientRect().top}px + ${config?.safeAreaOffset?.bottom ?? 0}px))`,
136
- }, onDragOver: handleBoardDragOver, onDragEnd: stopScrolling, onDrop: stopScrolling },
137
- React.createElement("div", { className: "buttons" },
138
- React.createElement("div", { className: "button left", onMouseDown: () => handleStartScroll("left"), onMouseUp: handleStopScroll, onMouseLeave: handleStopScroll },
139
- React.createElement(ARIcon, { icon: "ArrowLeft" })),
140
- React.createElement("div", { className: "button right", onMouseDown: () => handleStartScroll("right"), onMouseUp: handleStopScroll, onMouseLeave: handleStopScroll },
141
- React.createElement(ARIcon, { icon: "ArrowRight" }))),
142
- React.createElement("div", { className: "titles" }, data.map((board, index) => (React.createElement("div", { key: index, className: "title" },
143
- React.createElement("h4", null,
144
- React.createElement("span", { style: {
145
- backgroundColor: darkenColor(board.titleColor ?? "", 1),
146
- borderColor: darkenColor(board.titleColor ?? "", 1),
141
+ useEffect(() => {
142
+ const selectMap = new Map();
143
+ const dateMap = new Map();
144
+ columns.forEach((col) => {
145
+ col.items.forEach((item) => {
146
+ const keys = config?.filter?.keys(item);
147
+ keys?.forEach((k) => {
148
+ if (k.type === "select") {
149
+ if (!selectMap.has(k.name))
150
+ selectMap.set(k.name, new Set());
151
+ selectMap.get(k.name).add(k.key ?? null);
152
+ }
153
+ if (k.type === "date")
154
+ dateMap.set(k.name, true);
155
+ });
156
+ });
157
+ });
158
+ // checkbox filtre seçenekleri
159
+ const nextSelectFilters = Object.fromEntries(Array.from(selectMap.entries()).map(([name, set]) => [name, Array.from(set)]));
160
+ setSelectFilters(nextSelectFilters);
161
+ // date filtreler (sadece isim + boş range)
162
+ setDateFilters((prev) => {
163
+ const next = { ...prev };
164
+ Array.from(dateMap.keys()).forEach((name) => {
165
+ if (!next[name]) {
166
+ next[name] = { from: null, to: null };
167
+ }
168
+ });
169
+ return next;
170
+ });
171
+ }, [columns]);
172
+ useEffect(() => {
173
+ let nextData = columns.map((col) => ({
174
+ ...col,
175
+ items: [...col.items],
176
+ }));
177
+ // Search varsa...
178
+ if (config?.filter?.search && search?.trim()) {
179
+ const q = search.trim();
180
+ nextData = nextData.map((col) => ({
181
+ ...col,
182
+ items: col.items.filter((item) => config.filter.search(item, q)),
183
+ }));
184
+ }
185
+ // Select ve Date varsa...
186
+ nextData = nextData.map((col) => ({
187
+ ...col,
188
+ items: col.items.filter((item) => {
189
+ const keys = config?.filter?.keys(item) ?? [];
190
+ // Select (checkbox)
191
+ const selectOk = Object.entries(selectedFilters).every(([filterName, selectedSet]) => {
192
+ if (!selectedSet || selectedSet.size === 0)
193
+ return true;
194
+ const value = keys.find((k) => k.name === filterName)?.key ?? null;
195
+ return selectedSet.has(value);
196
+ });
197
+ if (!selectOk)
198
+ return false;
199
+ // Date (range)
200
+ const dateOk = Object.entries(dateFilters).every(([filterName, range]) => {
201
+ if (!range.from && !range.to)
202
+ return true;
203
+ const raw = keys.find((k) => k.name === filterName)?.key;
204
+ if (!raw)
205
+ return false;
206
+ const d = new Date(raw);
207
+ if (range.from && d < range.from)
208
+ return false;
209
+ if (range.to && d > range.to)
210
+ return false;
211
+ return true;
212
+ });
213
+ return dateOk;
214
+ }),
215
+ }));
216
+ setData(nextData);
217
+ }, [columns, search, selectedFilters, dateFilters]);
218
+ return (React.createElement(React.Fragment, null,
219
+ React.createElement("div", { className: "filters" },
220
+ React.createElement(Input, { variant: "borderless", placeholder: "Filter by keyword", onChange: (event) => setSearch(event.target.value.toLocaleLowerCase()) }),
221
+ React.createElement("ul", null,
222
+ React.createElement(DateFilters, { states: {
223
+ dateFilters: {
224
+ get: dateFilters,
225
+ set: setDateFilters,
226
+ },
147
227
  } }),
148
- board.title.toLocaleUpperCase("tr")),
149
- board.description && React.createElement("span", null, board.description))))),
150
- React.createElement("div", { className: "columns" }, data.map((board, index) => (React.createElement("div", { key: index, className: "column", onDrop: handleDrop(board.key) },
151
- React.createElement("div", { className: "items", onDragOver: handleItemsDragOver, onDragLeave: handleItemsDragLeave, onDrop: handleItemsDrop },
152
- React.createElement(DnD, { key: board.key, data: board.items, renderItem: (item, index) => {
153
- return (React.createElement("div", { key: index, className: "item", onDragOver: (event) => {
154
- event.preventDefault();
155
- const rect = event.currentTarget.getBoundingClientRect();
156
- const mouseY = event.clientY;
157
- const isBelow = mouseY > rect.top + rect.height / 2;
158
- _hoverItemIndex.current = isBelow ? index + 1 : index;
159
- } }, board.renderItem(item, index)));
160
- }, columnKey: board.key, confing: { isMoveIcon: false } }))))))));
228
+ React.createElement(SelectFilters, { states: {
229
+ selectFilters: {
230
+ get: selectFilters,
231
+ set: setSelectFilters,
232
+ },
233
+ selectedFilters: {
234
+ get: selectedFilters,
235
+ set: setSelectedFilters,
236
+ },
237
+ } }))),
238
+ React.createElement("div", { ref: _kanbanWrapper, className: "ar-kanban-board", style: {
239
+ height: `calc(100dvh - (${_kanbanWrapper.current?.getBoundingClientRect().top}px + ${config?.safeAreaOffset?.bottom ?? 0}px))`,
240
+ }, onDragOver: handleBoardDragOver, onDragEnd: stopScrolling, onDrop: stopScrolling },
241
+ React.createElement("div", { className: "buttons" },
242
+ React.createElement("div", { className: "button left", onMouseDown: () => handleStartScroll("left"), onMouseUp: handleStopScroll, onMouseLeave: handleStopScroll },
243
+ React.createElement(ARIcon, { icon: "ArrowLeft" })),
244
+ React.createElement("div", { className: "button right", onMouseDown: () => handleStartScroll("right"), onMouseUp: handleStopScroll, onMouseLeave: handleStopScroll },
245
+ React.createElement(ARIcon, { icon: "ArrowRight" }))),
246
+ React.createElement("div", { className: "titles" }, data.map((board, index) => (React.createElement("div", { key: index, className: "title" },
247
+ React.createElement("h4", null,
248
+ React.createElement("span", { style: {
249
+ backgroundColor: darkenColor(board.titleColor ?? "", 1),
250
+ borderColor: darkenColor(board.titleColor ?? "", 1),
251
+ } }),
252
+ board.title.toLocaleUpperCase("tr")),
253
+ board.description && React.createElement("span", null, board.description))))),
254
+ React.createElement("div", { className: "columns" }, data.map((board, index) => (React.createElement("div", { key: index, className: "column", onDrop: handleDrop(board.key) },
255
+ React.createElement("div", { className: "items", onDragOver: handleItemsDragOver, onDragLeave: handleItemsDragLeave, onDrop: handleItemsDrop },
256
+ React.createElement(DnD, { key: board.key, data: board.items, renderItem: (item, dndIndex) => {
257
+ return (React.createElement("div", { key: dndIndex, className: "item", onDragOver: (event) => {
258
+ event.preventDefault();
259
+ const rect = event.currentTarget.getBoundingClientRect();
260
+ const mouseY = event.clientY;
261
+ const isBelow = mouseY > rect.top + rect.height / 2;
262
+ _hoverItemIndex.current = isBelow ? index + 1 : index;
263
+ } }, board.renderItem(item, index)));
264
+ }, columnKey: board.key, confing: { isMoveIcon: false } })))))))));
161
265
  };
162
266
  export default KanbanBoard;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ar-design",
3
- "version": "0.4.21",
3
+ "version": "0.4.22",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",