@uniai-fe/uds-templates 0.5.8 → 0.5.10
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/README.md +3 -0
- package/package.json +1 -1
- package/src/cctv/components/pagination/list/Item.tsx +9 -0
- package/src/modal/components/core/Root.tsx +38 -1
- package/src/weather/apis/korea/client.ts +50 -15
- package/src/weather/apis/korea/server.ts +2 -2
- package/src/weather/apis/open-weather-map/client.ts +45 -15
- package/src/weather/apis/open-weather-map/server.ts +8 -2
- package/src/weather/components/icon/Address.tsx +7 -6
- package/src/weather/components/icon/Weather.tsx +4 -5
- package/src/weather/components/page-header/Address.tsx +36 -2
- package/src/weather/components/page-header/Alert.tsx +43 -16
- package/src/weather/components/page-header/Container.tsx +33 -5
- package/src/weather/components/page-header/Forecast.tsx +48 -7
- package/src/weather/components/page-header/NextDays.tsx +25 -22
- package/src/weather/components/page-header/Today.tsx +134 -91
- package/src/weather/hooks/useOpenWeatherMap.ts +22 -3
- package/src/weather/hooks/useWeatherKorea.ts +16 -4
- package/src/weather/hooks/useWeatherKoreaAlert.ts +13 -4
- package/src/weather/img/marker.svg +4 -0
- package/src/weather/styles/weather.scss +107 -108
- package/src/weather/types/base.ts +20 -0
- package/src/weather/types/index.ts +1 -0
- package/src/weather/types/page-header.ts +277 -0
- package/src/weather/utils/index.ts +1 -0
- package/src/weather/utils/locale.ts +73 -0
- package/src/weather/utils/weather.ts +112 -0
|
@@ -1,32 +1,35 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lengthFormat } from "@uniai-fe/util-functions";
|
|
2
|
+
|
|
3
|
+
import type { WeatherPageHeaderNextDaysProps } from "../../types";
|
|
4
|
+
import { resolveWeatherPageHeaderTexts } from "../../utils";
|
|
2
5
|
import WeatherIcon from "../icon/Weather";
|
|
3
6
|
|
|
4
7
|
export default function WeatherPageHeaderNextDays({
|
|
5
8
|
title,
|
|
6
9
|
data,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
texts: textOverrides,
|
|
11
|
+
}: WeatherPageHeaderNextDaysProps) {
|
|
12
|
+
const texts = resolveWeatherPageHeaderTexts(textOverrides);
|
|
13
|
+
|
|
11
14
|
return (
|
|
12
15
|
<div className="weather-next-days-container">
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
</
|
|
16
|
+
<WeatherIcon
|
|
17
|
+
code={data?.condition}
|
|
18
|
+
name={data?.conditionName}
|
|
19
|
+
alt={texts.weatherIconAlt}
|
|
20
|
+
/>
|
|
21
|
+
<p className="weather-forecast-text">
|
|
22
|
+
<span className="weather-label">{title}</span>
|
|
23
|
+
<span className="weather-value">
|
|
24
|
+
{lengthFormat(data?.min_temperature, 0)}
|
|
25
|
+
</span>
|
|
26
|
+
<span className="weather-unit">℃</span>
|
|
27
|
+
<span className="weather-range">~</span>
|
|
28
|
+
<span className="weather-value">
|
|
29
|
+
{lengthFormat(data?.max_temperature, 0)}
|
|
30
|
+
</span>
|
|
31
|
+
<span className="weather-unit">℃</span>
|
|
32
|
+
</p>
|
|
30
33
|
</div>
|
|
31
34
|
);
|
|
32
35
|
}
|
|
@@ -6,127 +6,170 @@ import { lengthFormat } from "@uniai-fe/util-functions";
|
|
|
6
6
|
import { Alternate } from "@uniai-fe/uds-primitives";
|
|
7
7
|
import WeatherIcon from "../icon/Weather";
|
|
8
8
|
import WeatherPageHeaderAlert from "./Alert";
|
|
9
|
+
import type { WeatherPageHeaderTodayProps } from "../../types";
|
|
9
10
|
import { useOpenWeatherMap, useWeatherKorea } from "../../hooks";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
import {
|
|
12
|
+
getOpenWeatherMapConditionCode,
|
|
13
|
+
isKoreaWeatherLocale,
|
|
14
|
+
resolveWeatherPageHeaderTexts,
|
|
15
|
+
} from "../../utils";
|
|
16
|
+
|
|
17
|
+
export default function WeatherPageHeaderToday({
|
|
18
|
+
locale,
|
|
19
|
+
texts: textOverrides,
|
|
20
|
+
koreaWeather,
|
|
21
|
+
openWeatherMapWeather,
|
|
22
|
+
}: WeatherPageHeaderTodayProps = {}) {
|
|
23
|
+
const isKoreaProvider = isKoreaWeatherLocale(locale);
|
|
24
|
+
const koreaWeatherFallback = useWeatherKorea({
|
|
25
|
+
enabled: !koreaWeather && isKoreaProvider,
|
|
26
|
+
});
|
|
27
|
+
const openWeatherMapWeatherFallback = useOpenWeatherMap({
|
|
28
|
+
locale,
|
|
29
|
+
enabledNow: !openWeatherMapWeather && !isKoreaProvider,
|
|
30
|
+
enabledForecast: false,
|
|
31
|
+
});
|
|
32
|
+
const korea = koreaWeather ?? koreaWeatherFallback;
|
|
33
|
+
const openWeatherMap = openWeatherMapWeather ?? openWeatherMapWeatherFallback;
|
|
34
|
+
const texts = resolveWeatherPageHeaderTexts(textOverrides);
|
|
14
35
|
|
|
15
36
|
const isFetchingAlternate = useMemo(() => {
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
const kma_forecast = forecast?.today?.condition;
|
|
19
|
-
if (
|
|
20
|
-
!kma_now ||
|
|
21
|
-
kma_now.endsWith("null") ||
|
|
22
|
-
!kma_forecast ||
|
|
23
|
-
kma_forecast.endsWith("null")
|
|
24
|
-
) {
|
|
25
|
-
return isFetchingAlt;
|
|
26
|
-
}
|
|
37
|
+
if (!isKoreaProvider) return openWeatherMap.isFetchingNow;
|
|
38
|
+
if (korea.isFetchingNow) return true;
|
|
27
39
|
return false;
|
|
28
|
-
}, [
|
|
29
|
-
forecast?.today?.condition,
|
|
30
|
-
isFetchingAlt,
|
|
31
|
-
isFetchingNow,
|
|
32
|
-
now?.today?.condition,
|
|
33
|
-
]);
|
|
40
|
+
}, [isKoreaProvider, korea.isFetchingNow, openWeatherMap.isFetchingNow]);
|
|
34
41
|
|
|
35
42
|
const iconCode = useMemo(() => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const alt_now = nowAlt?.weather?.[0]?.icon;
|
|
43
|
-
switch (alt_now) {
|
|
44
|
-
case "01d":
|
|
45
|
-
return "sky-1";
|
|
46
|
-
case "02d":
|
|
47
|
-
case "03d":
|
|
48
|
-
case "04d":
|
|
49
|
-
return "sky-2";
|
|
50
|
-
case "09d":
|
|
51
|
-
return "drop-rain-3";
|
|
52
|
-
case "10d":
|
|
53
|
-
return "drop-rain-1";
|
|
54
|
-
case "11d":
|
|
55
|
-
return "drop-rain-thunder";
|
|
56
|
-
case "13d":
|
|
57
|
-
return "drop-snow";
|
|
58
|
-
case "50d":
|
|
59
|
-
return "sky-4";
|
|
60
|
-
default:
|
|
61
|
-
return "sky-1";
|
|
43
|
+
if (isKoreaProvider) {
|
|
44
|
+
const kma_now = korea.now?.today?.condition;
|
|
45
|
+
if (kma_now && !kma_now.endsWith("null")) return kma_now;
|
|
46
|
+
|
|
47
|
+
const kma_forecast = korea.forecast?.today?.condition;
|
|
48
|
+
if (kma_forecast && !kma_forecast.endsWith("null")) return kma_forecast;
|
|
62
49
|
}
|
|
63
|
-
}, [forecast?.today?.condition, now?.today?.condition, nowAlt?.weather]);
|
|
64
50
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
51
|
+
return getOpenWeatherMapConditionCode(
|
|
52
|
+
openWeatherMap.now?.weather?.[0]?.icon,
|
|
53
|
+
);
|
|
54
|
+
}, [
|
|
55
|
+
isKoreaProvider,
|
|
56
|
+
korea.forecast?.today?.condition,
|
|
57
|
+
korea.now?.today?.condition,
|
|
58
|
+
openWeatherMap.now?.weather,
|
|
59
|
+
]);
|
|
68
60
|
|
|
69
|
-
|
|
70
|
-
if (
|
|
61
|
+
const weatherStateName = useMemo(() => {
|
|
62
|
+
if (isKoreaProvider) {
|
|
63
|
+
const kma_now = korea.now?.today?.conditionName;
|
|
64
|
+
if (kma_now && !kma_now.endsWith("null"))
|
|
65
|
+
return kma_now || texts.conditionFallbackLabel;
|
|
66
|
+
|
|
67
|
+
const kma_forecast = korea.forecast?.today?.conditionName;
|
|
68
|
+
if (kma_forecast && !kma_forecast.endsWith("null"))
|
|
69
|
+
return kma_forecast || texts.conditionFallbackLabel;
|
|
70
|
+
}
|
|
71
71
|
|
|
72
|
-
return
|
|
72
|
+
return (
|
|
73
|
+
openWeatherMap.now?.weather?.[0]?.description ||
|
|
74
|
+
texts.conditionFallbackLabel
|
|
75
|
+
);
|
|
73
76
|
}, [
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
isKoreaProvider,
|
|
78
|
+
korea.forecast?.today?.conditionName,
|
|
79
|
+
korea.now?.today?.conditionName,
|
|
80
|
+
openWeatherMap.now?.weather,
|
|
81
|
+
texts.conditionFallbackLabel,
|
|
77
82
|
]);
|
|
78
83
|
|
|
79
84
|
const temperature = useMemo(() => {
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
if (isKoreaProvider) {
|
|
86
|
+
const kma_now = korea.now?.today?.temperature;
|
|
87
|
+
if (kma_now && !Number.isNaN(Number(kma_now))) return Number(kma_now);
|
|
82
88
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
const kma_forecast = korea.forecast?.today?.temperature;
|
|
90
|
+
if (kma_forecast && !Number.isNaN(Number(kma_forecast)))
|
|
91
|
+
return Number(kma_forecast);
|
|
92
|
+
}
|
|
86
93
|
|
|
87
|
-
const alt_now =
|
|
94
|
+
const alt_now = openWeatherMap.now?.main?.temp;
|
|
88
95
|
return typeof alt_now === "number" ? Math.round(alt_now) : null;
|
|
89
96
|
}, [
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
isKoreaProvider,
|
|
98
|
+
korea.forecast?.today?.temperature,
|
|
99
|
+
korea.now?.today?.temperature,
|
|
100
|
+
openWeatherMap.now?.main?.temp,
|
|
93
101
|
]);
|
|
94
102
|
|
|
95
103
|
const humidity = useMemo(() => {
|
|
96
|
-
|
|
97
|
-
|
|
104
|
+
if (isKoreaProvider) {
|
|
105
|
+
const kma_now = korea.now?.today?.humidity;
|
|
106
|
+
if (kma_now && !Number.isNaN(Number(kma_now))) return Number(kma_now);
|
|
98
107
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
const kma_forecast = korea.forecast?.today?.humidity;
|
|
109
|
+
if (kma_forecast && !Number.isNaN(Number(kma_forecast)))
|
|
110
|
+
return Number(kma_forecast);
|
|
111
|
+
}
|
|
102
112
|
|
|
103
|
-
const alt_now =
|
|
113
|
+
const alt_now = openWeatherMap.now?.main?.humidity;
|
|
104
114
|
return typeof alt_now === "number" ? Math.round(alt_now) : null;
|
|
105
|
-
}, [
|
|
115
|
+
}, [
|
|
116
|
+
isKoreaProvider,
|
|
117
|
+
korea.forecast?.today?.humidity,
|
|
118
|
+
korea.now?.today?.humidity,
|
|
119
|
+
openWeatherMap.now?.main?.humidity,
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
const maxTemperature = null;
|
|
123
|
+
const minTemperature = null;
|
|
124
|
+
const alert = (
|
|
125
|
+
<WeatherPageHeaderAlert locale={locale} texts={textOverrides} />
|
|
126
|
+
);
|
|
106
127
|
|
|
107
128
|
return (
|
|
108
129
|
<div className="weather-today-container">
|
|
109
130
|
{isFetchingAlternate ? (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
<>
|
|
132
|
+
{alert}
|
|
133
|
+
<Alternate.LoadingDefault direction="horizontal">
|
|
134
|
+
{texts.currentWeatherLoading}
|
|
135
|
+
</Alternate.LoadingDefault>
|
|
136
|
+
</>
|
|
113
137
|
) : (
|
|
114
138
|
<>
|
|
115
|
-
<WeatherIcon
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
<
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
139
|
+
<WeatherIcon
|
|
140
|
+
code={iconCode}
|
|
141
|
+
name={weatherStateName}
|
|
142
|
+
alt={texts.weatherIconAlt}
|
|
143
|
+
/>
|
|
144
|
+
<div className="weather-today-text">
|
|
145
|
+
{alert}
|
|
146
|
+
<p className="weather-temperature-text">
|
|
147
|
+
<span className="weather-label">{texts.currentLabel}</span>
|
|
148
|
+
<span className="weather-value">
|
|
149
|
+
{lengthFormat(temperature, 0)}
|
|
150
|
+
</span>
|
|
151
|
+
<span className="weather-unit">℃</span>
|
|
152
|
+
</p>
|
|
153
|
+
<p className="weather-temperature-text">
|
|
154
|
+
<span className="weather-label">{texts.maxTemperatureLabel}</span>
|
|
155
|
+
<span className="weather-value">
|
|
156
|
+
{lengthFormat(maxTemperature, 0)}
|
|
157
|
+
</span>
|
|
158
|
+
<span className="weather-unit">℃</span>
|
|
159
|
+
</p>
|
|
160
|
+
<p className="weather-temperature-text">
|
|
161
|
+
<span className="weather-label">{texts.minTemperatureLabel}</span>
|
|
162
|
+
<span className="weather-value">
|
|
163
|
+
{lengthFormat(minTemperature, 0)}
|
|
164
|
+
</span>
|
|
165
|
+
<span className="weather-unit">℃</span>
|
|
166
|
+
</p>
|
|
167
|
+
<p className="weather-humidity-text">
|
|
168
|
+
<span className="weather-label">{texts.humidityLabel}</span>
|
|
169
|
+
<span className="weather-value">
|
|
170
|
+
{lengthFormat(humidity, 0)}%
|
|
171
|
+
</span>
|
|
172
|
+
</p>
|
|
130
173
|
</div>
|
|
131
174
|
</>
|
|
132
175
|
)}
|
|
@@ -6,14 +6,33 @@ import {
|
|
|
6
6
|
useQueryWeatherOpenWeatherMapForecast,
|
|
7
7
|
useQueryWeatherOpenWeatherMapNow,
|
|
8
8
|
} from "../apis/open-weather-map/client";
|
|
9
|
+
import type {
|
|
10
|
+
WeatherApiLocaleOptions,
|
|
11
|
+
WeatherOpenWeatherMapParams,
|
|
12
|
+
} from "../types";
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
type UseOpenWeatherMapOptions = WeatherApiLocaleOptions & {
|
|
15
|
+
enabledNow?: boolean;
|
|
16
|
+
enabledForecast?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default function useOpenWeatherMap({
|
|
20
|
+
locale,
|
|
21
|
+
enabledNow = true,
|
|
22
|
+
enabledForecast = true,
|
|
23
|
+
}: UseOpenWeatherMapOptions = {}) {
|
|
11
24
|
const coordinate = useAtomValue(weatherCoordinate);
|
|
25
|
+
const params: WeatherOpenWeatherMapParams = {
|
|
26
|
+
...coordinate,
|
|
27
|
+
...(locale ? { locale } : {}),
|
|
28
|
+
};
|
|
12
29
|
|
|
13
30
|
const { data: now, isFetching: isFetchingNow } =
|
|
14
|
-
useQueryWeatherOpenWeatherMapNow(
|
|
31
|
+
useQueryWeatherOpenWeatherMapNow(params, { enabled: enabledNow });
|
|
15
32
|
const { data: forecast, isFetching: isFetchingForecast } =
|
|
16
|
-
useQueryWeatherOpenWeatherMapForecast(
|
|
33
|
+
useQueryWeatherOpenWeatherMapForecast(params, {
|
|
34
|
+
enabled: enabledForecast,
|
|
35
|
+
});
|
|
17
36
|
|
|
18
37
|
return {
|
|
19
38
|
coordinate,
|
|
@@ -11,7 +11,13 @@ import { weatherCoordinate } from "../jotai/coordinate";
|
|
|
11
11
|
import { getWeatherBaseMoments } from "../utils/date-time";
|
|
12
12
|
import { getWeatherGridLocation } from "../utils/location";
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
type UseWeatherKoreaOptions = {
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default function useWeatherKorea({
|
|
19
|
+
enabled = true,
|
|
20
|
+
}: UseWeatherKoreaOptions = {}) {
|
|
15
21
|
const coordinate = useAtomValue(weatherCoordinate);
|
|
16
22
|
const [params, setParams] = useState<{
|
|
17
23
|
[categoryKey: string]: API_Req_WeatherKorea;
|
|
@@ -21,7 +27,8 @@ export default function useWeatherKorea() {
|
|
|
21
27
|
});
|
|
22
28
|
|
|
23
29
|
useEffect(() => {
|
|
24
|
-
if (typeof window === "undefined") return;
|
|
30
|
+
if (typeof window === "undefined" || !enabled) return;
|
|
31
|
+
let isActive = true;
|
|
25
32
|
|
|
26
33
|
const hasInjectedCoordinate =
|
|
27
34
|
typeof coordinate.address === "string" ||
|
|
@@ -38,6 +45,7 @@ export default function useWeatherKorea() {
|
|
|
38
45
|
|
|
39
46
|
async function updater() {
|
|
40
47
|
const grid = await getWeatherGridLocation(coordinate);
|
|
48
|
+
if (!isActive) return;
|
|
41
49
|
setParams({
|
|
42
50
|
now: {
|
|
43
51
|
...grid,
|
|
@@ -51,14 +59,18 @@ export default function useWeatherKorea() {
|
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
updater();
|
|
54
|
-
|
|
62
|
+
return () => {
|
|
63
|
+
isActive = false;
|
|
64
|
+
};
|
|
65
|
+
}, [coordinate, enabled]);
|
|
55
66
|
|
|
56
67
|
const { data: now, isFetching: isFetchingNow } = useQueryWeatherKoreaNow(
|
|
57
68
|
params.now,
|
|
69
|
+
{ enabled },
|
|
58
70
|
);
|
|
59
71
|
|
|
60
72
|
const { data: forecast, isFetching: isFetchingForecast } =
|
|
61
|
-
useQueryWeatherKoreaForecast(params.forecast);
|
|
73
|
+
useQueryWeatherKoreaForecast(params.forecast, { enabled });
|
|
62
74
|
|
|
63
75
|
return {
|
|
64
76
|
params,
|
|
@@ -5,13 +5,22 @@ import { useAtomValue } from "jotai";
|
|
|
5
5
|
import { useQueryWeatherKoreaAlert } from "../apis/korea/client"; // server 번들을 피하기 위해 client 모듈에서 직접 import한다.
|
|
6
6
|
import { weatherFarmIdx } from "../jotai/farm-idx";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
type UseWeatherKoreaAlertOptions = {
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default function useWeatherKoreaAlert({
|
|
13
|
+
enabled = true,
|
|
14
|
+
}: UseWeatherKoreaAlertOptions = {}) {
|
|
9
15
|
const farm_idx = useAtomValue(weatherFarmIdx);
|
|
10
16
|
|
|
11
17
|
// 특보 조회 식별자는 템플릿이 보정하지 않고, 서비스가 주입한 값만 그대로 사용한다.
|
|
12
|
-
const { data, isFetching } = useQueryWeatherKoreaAlert(
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
const { data, isFetching } = useQueryWeatherKoreaAlert(
|
|
19
|
+
{
|
|
20
|
+
farm_idx,
|
|
21
|
+
},
|
|
22
|
+
{ enabled },
|
|
23
|
+
);
|
|
15
24
|
|
|
16
25
|
const alert = useMemo(() => data?.alerts || [], [data]);
|
|
17
26
|
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M7.99996 1.4668C10.6941 1.4668 12.8665 3.47682 12.8665 6.58854C12.8664 8.54149 11.6838 10.5163 10.5898 11.9421C10.0325 12.6683 9.47625 13.28 9.05986 13.7096C8.8515 13.9246 8.67753 14.095 8.55465 14.2122C8.49317 14.2709 8.44389 14.3165 8.41012 14.3477C8.39335 14.3631 8.38018 14.3752 8.37105 14.3835C8.36655 14.3876 8.36318 14.3909 8.36064 14.3932C8.35938 14.3944 8.35814 14.3952 8.35738 14.3958L8.35608 14.3965C8.17889 14.5552 7.92171 14.576 7.72457 14.457L7.64384 14.3971L7.64254 14.3958C7.64178 14.3952 7.64053 14.3944 7.63928 14.3932C7.63674 14.3909 7.63337 14.3876 7.62887 14.3835C7.61974 14.3752 7.60657 14.3631 7.5898 14.3477C7.55603 14.3165 7.50675 14.2709 7.44527 14.2122C7.32239 14.095 7.14842 13.9246 6.94006 13.7096C6.52367 13.28 5.96741 12.6683 5.41012 11.9421C4.3161 10.5163 3.13354 8.54149 3.13342 6.58854C3.13342 3.47682 5.30586 1.4668 7.99996 1.4668Z" fill="#313235"/>
|
|
3
|
+
<path d="M9.33329 6.66671C9.33329 7.40309 8.73634 8.00004 7.99996 8.00004C7.26358 8.00004 6.66663 7.40309 6.66663 6.66671C6.66663 5.93033 7.26358 5.33337 7.99996 5.33337C8.73634 5.33337 9.33329 5.93033 9.33329 6.66671Z" fill="white"/>
|
|
4
|
+
</svg>
|